clean project
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6dc5bcf8b10f564439a3c7603bf53db7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49e18ec05efde04499be425cfc618529
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed8877737b1570c42b9dafa5f1dcce9f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2e4a61fac5024b4587fff4b305431a4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0502bb2a0224d844b255565fd926a1a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c02bb45a336bd74ba30737c1fcd2ebe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94a7474e6d755af4e88ddde6c0d4e9d3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91c22619060901949b0d01586cff533e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09d0907b32d460349ae989d67adb26ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e905110e6125b442b02d7a80ba72015
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Runtime/Scripts/Grab.meta
Normal file
8
Assets/Oculus/Interaction/Runtime/Scripts/Grab.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6531a4f842518049961d156ddc174be
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
163
Assets/Oculus/Interaction/Runtime/Scripts/Grab/FingerGrabAPI.cs
Normal file
163
Assets/Oculus/Interaction/Runtime/Scripts/Grab/FingerGrabAPI.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df4b2f3ffa2d8a04eb0badc7bfd33a41
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
271
Assets/Oculus/Interaction/Runtime/Scripts/Grab/FingerPinchAPI.cs
Normal file
271
Assets/Oculus/Interaction/Runtime/Scripts/Grab/FingerPinchAPI.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 983d470de27b8db4aae01497168de89f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e600c87acd7d57041b18b00b86842221
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77279ac005404794d8e29e385f50c9c2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 654ba27bf15ed6b40a251d7b6baafd79
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
157
Assets/Oculus/Interaction/Runtime/Scripts/Grab/GrabbingRule.cs
Normal file
157
Assets/Oculus/Interaction/Runtime/Scripts/Grab/GrabbingRule.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73275021f6e6b1444b1fed2f2dab865e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
228
Assets/Oculus/Interaction/Runtime/Scripts/Grab/HandGrab.cs
Normal file
228
Assets/Oculus/Interaction/Runtime/Scripts/Grab/HandGrab.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11ab679e1e048cf46b1357b0d4c1c824
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
337
Assets/Oculus/Interaction/Runtime/Scripts/Grab/HandGrabAPI.cs
Normal file
337
Assets/Oculus/Interaction/Runtime/Scripts/Grab/HandGrabAPI.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae43f25c06eb2d9408a201216b4c3ed7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
26
Assets/Oculus/Interaction/Runtime/Scripts/Grab/IFingerAPI.cs
Normal file
26
Assets/Oculus/Interaction/Runtime/Scripts/Grab/IFingerAPI.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 629a079b5a23e404faee62377bf2620d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 563c8df175b2fbf4b9eb5ed0042e437b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c54cd4bb8381c74ebdc2b1ebf70bc7a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b387ba6b69a57bb4aad95e412cd74019
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69d6851a1cd1d114bad969d5ea8ed50a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66d82fc01fa658f40b9399eec55a1c7a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbe6418317e852d4e8fd122a4149acba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8951a656a7c00e74094166ef415cdce5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ab0e0bf5507fad4a9def20ce63299e4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b84868e1fec742746b4f97a01bf836af
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cada41609b5284144aef14e522e6351a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 18467ddce2adbac48aa374514e13d1ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: adee723269a015745afa89216e202d37
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d179fd81cd2e344ab2e610cd5f7260e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84de3b22a7efbab46967c1a17f5b8cda
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2ff26c21ac005534e8af75f8427be9d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 890181c147a8cc94597b7ab04b4db257
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3bf6df4a5ac85847831e1fb5fa00ff8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Oculus/Interaction/Runtime/Scripts/Input.meta
Normal file
8
Assets/Oculus/Interaction/Runtime/Scripts/Input.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ba644c544c6b1d45a252b75ec6bc9f1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 96dc2ac9879a2ae409623a72b39f8340
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4ae60cfa43388e449a7518f36418f81c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fe4ca944e80140841985b0f273793641
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a1675de914d203a4a910d701ade05ce3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8489868b0a7582e43890f869877224e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3673df66324d8f34d9433049c54de631
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 77db11dffd48abd4cb96a6076a2a2545
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b392af6b92984f2887f80975b07141ba
|
||||
timeCreated: 1629344165
|
||||
118
Assets/Oculus/Interaction/Runtime/Scripts/Input/DataModifier.cs
Normal file
118
Assets/Oculus/Interaction/Runtime/Scripts/Input/DataModifier.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 12efd6a9760b4bcaafd9cefbe1db0bad
|
||||
timeCreated: 1630526111
|
||||
202
Assets/Oculus/Interaction/Runtime/Scripts/Input/DataSource.cs
Normal file
202
Assets/Oculus/Interaction/Runtime/Scripts/Input/DataSource.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e3eaa7f7e074674929b88928616f063
|
||||
timeCreated: 1630527498
|
||||
8
Assets/Oculus/Interaction/Runtime/Scripts/Input/HMD.meta
Normal file
8
Assets/Oculus/Interaction/Runtime/Scripts/Input/HMD.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b30e59c0241c28d46b6f42afca0d444d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
53
Assets/Oculus/Interaction/Runtime/Scripts/Input/HMD/Hmd.cs
Normal file
53
Assets/Oculus/Interaction/Runtime/Scripts/Input/HMD/Hmd.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2606bf2f0c914a7aba4390f29ba2eb6e
|
||||
timeCreated: 1629334585
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 332eb9fb3feb493aa2632f2739bf3d41
|
||||
timeCreated: 1629321466
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e6945eb8bd2e9941a48a55b14cda4d5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 484167e684014224c9a8bff92b293c6a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
23
Assets/Oculus/Interaction/Runtime/Scripts/Input/HMD/IHmd.cs
Normal file
23
Assets/Oculus/Interaction/Runtime/Scripts/Input/HMD/IHmd.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d92925b93eb01cf4ba528518efe2705a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c02ebbf428f995b45a218658ebd541d6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b1422466b455b248ad5e0e27e1e043e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
Reference in New Issue
Block a user