clean project
This commit is contained in:
@@ -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:
|
||||
Reference in New Issue
Block a user