clean project

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,390 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
************************************************************************************/
using UnityEngine;
using UnityEngine.Assertions;
using System;
namespace Oculus.Interaction.HandPosing.SnapSurfaces
{
[Serializable]
public class BoxSurfaceData : ICloneable
{
public object Clone()
{
BoxSurfaceData clone = new BoxSurfaceData();
clone.widthOffset = this.widthOffset;
clone.snapOffset = this.snapOffset;
clone.size = this.size;
clone.eulerAngles = this.eulerAngles;
return clone;
}
public BoxSurfaceData Mirror()
{
BoxSurfaceData mirror = Clone() as BoxSurfaceData;
mirror.snapOffset = new Vector4(
-mirror.snapOffset.y, -mirror.snapOffset.x,
-mirror.snapOffset.w, -mirror.snapOffset.z);
return mirror;
}
[Range(0f, 1f)]
public float widthOffset = 0.5f;
public Vector4 snapOffset;
public Vector3 size = new Vector3(0.1f, 0f, 0.1f);
public Vector3 eulerAngles;
}
/// <summary>
/// This SnapSurface defines a Rectangle around which the grip point is valid.
/// Since the grip point might be offset from the fingers, a valid range for each opposite
/// side of the rectangle can be set so the grabbing fingers are constrained to the object bounds.
/// </summary>
[Serializable]
public class BoxSurface : MonoBehaviour, ISnapSurface
{
[SerializeField]
protected BoxSurfaceData _data = new BoxSurfaceData();
/// <summary>
/// Getter for the data-only version of this surface. Used so it can be stored when created
/// at Play-Mode.
/// </summary>
public BoxSurfaceData Data
{
get
{
return _data;
}
set
{
_data = value;
}
}
[SerializeField]
private Transform _relativeTo;
[SerializeField]
private Transform _gripPoint;
/// <summary>
/// Transform to which the surface refers to.
/// </summary>
public Transform RelativeTo
{
get => _relativeTo;
set => _relativeTo = value;
}
/// <summary>
/// Valid point at which the hand can snap, typically the SnapPoint position itself.
/// </summary>
public Transform GripPoint
{
get => _gripPoint;
set => _gripPoint = value;
}
/// <summary>
/// The lateral displacement of the grip point in the main side.
/// </summary>
public float WidthOffset
{
get
{
return _data.widthOffset;
}
set
{
_data.widthOffset = value;
}
}
/// <summary>
/// The range at which the sides are constrained.
/// X,Y for the back and forward sides range.
/// Z,W for the left and right sides range.
/// </summary>
public Vector4 SnapOffset
{
get
{
return _data.snapOffset;
}
set
{
_data.snapOffset = value;
}
}
/// <summary>
/// The size of the rectangle. Y is ignored.
/// </summary>
public Vector3 Size
{
get
{
return _data.size;
}
set
{
_data.size = value;
}
}
/// <summary>
/// The rotation of the rectangle from the Grip point
/// </summary>
public Quaternion Rotation
{
get
{
return this.RelativeTo.rotation * Quaternion.Euler(_data.eulerAngles);
}
set
{
_data.eulerAngles = (Quaternion.Inverse(this.RelativeTo.rotation) * value).eulerAngles;
}
}
/// <summary>
/// The forward direction of the rectangle (based on its rotation)
/// </summary>
public Vector3 Direction
{
get
{
return Rotation * Vector3.forward;
}
}
#region editor events
private void Reset()
{
_gripPoint = this.transform;
if (this.TryGetComponent(out HandGrabPoint grabPoint))
{
_relativeTo = grabPoint.RelativeTo;
}
}
#endregion
protected virtual void Start()
{
Assert.IsNotNull(_relativeTo);
Assert.IsNotNull(_gripPoint);
Assert.IsNotNull(_data);
}
public Pose MirrorPose(in Pose pose)
{
Vector3 normal = Quaternion.Inverse(this.RelativeTo.rotation) * Direction;
Vector3 tangent = Quaternion.Inverse(this.RelativeTo.rotation) * (Rotation * Vector3.up);
return pose.MirrorPoseRotation(normal, tangent);
}
public ISnapSurface CreateMirroredSurface(GameObject gameObject)
{
BoxSurface surface = gameObject.AddComponent<BoxSurface>();
surface.Data = _data.Mirror();
return surface;
}
public ISnapSurface CreateDuplicatedSurface(GameObject gameObject)
{
BoxSurface surface = gameObject.AddComponent<BoxSurface>();
surface.Data = _data;
return surface;
}
public float CalculateBestPoseAtSurface(in Pose targetPose, in Pose reference, out Pose bestPose, in PoseMeasureParameters scoringModifier)
{
return SnapSurfaceHelper.CalculateBestPoseAtSurface(targetPose, reference, out bestPose,
scoringModifier, MinimalTranslationPoseAtSurface, MinimalRotationPoseAtSurface);
}
private void CalculateCorners(out Vector3 bottomLeft, out Vector3 bottomRight, out Vector3 topLeft, out Vector3 topRight)
{
Vector3 rightRot = Rotation * Vector3.right;
bottomLeft = GripPoint.position - rightRot * _data.size.x * (1f - _data.widthOffset);
bottomRight = GripPoint.position + rightRot * _data.size.x * (_data.widthOffset);
Vector3 forwardOffset = Rotation * Vector3.forward * _data.size.z;
topLeft = bottomLeft + forwardOffset;
topRight = bottomRight + forwardOffset;
}
private Vector3 ProjectOnSegment(Vector3 point, (Vector3, Vector3) segment)
{
Vector3 line = segment.Item2 - segment.Item1;
Vector3 projection = Vector3.Project(point - segment.Item1, line);
if (Vector3.Dot(projection, line) < 0f)
{
projection = segment.Item1;
}
else if (projection.magnitude > line.magnitude)
{
projection = segment.Item2;
}
else
{
projection += segment.Item1;
}
return projection;
}
public bool CalculateBestPoseAtSurface(Ray targetRay, in Pose recordedPose, out Pose bestPose)
{
Plane plane = new Plane(Rotation * Vector3.up, this.transform.position);
plane.Raycast(targetRay, out float rayDistance);
Vector3 proximalPoint = targetRay.origin + targetRay.direction * rayDistance;
Vector3 surfacePoint = NearestPointInSurface(proximalPoint);
Pose desiredPose = new Pose(surfacePoint, recordedPose.rotation);
bestPose = MinimalTranslationPoseAtSurface(desiredPose, recordedPose);
return true;
}
protected Vector3 NearestPointInSurface(Vector3 targetPosition)
{
NearestPointAndAngleInSurface(targetPosition, out Vector3 surfacePoint, out float angle);
return surfacePoint;
}
private void NearestPointAndAngleInSurface(Vector3 targetPosition, out Vector3 surfacePoint, out float angle)
{
Vector3 rightDir = Rotation * Vector3.right;
Vector3 forwardDir = Rotation * Vector3.forward;
Vector3 bottomLeft, bottomRight, topLeft, topRight;
CalculateCorners(out bottomLeft, out bottomRight, out topLeft, out topRight);
Vector3 bottomP = ProjectOnSegment(targetPosition, (bottomLeft + rightDir * SnapOffset.y, bottomRight + rightDir * SnapOffset.x));
Vector3 topP = ProjectOnSegment(targetPosition, (topLeft - rightDir * SnapOffset.x, topRight - rightDir * SnapOffset.y));
Vector3 leftP = ProjectOnSegment(targetPosition, (bottomLeft - forwardDir * SnapOffset.z, topLeft - forwardDir * SnapOffset.w));
Vector3 rightP = ProjectOnSegment(targetPosition, (bottomRight + forwardDir * SnapOffset.w, topRight + forwardDir * SnapOffset.z));
float bottomDistance = Vector3.Distance(bottomP, targetPosition);
float topDistance = Vector3.Distance(topP, targetPosition);
float leftDistance = Vector3.Distance(leftP, targetPosition);
float rightDistance = Vector3.Distance(rightP, targetPosition);
float minDistance = Mathf.Min(bottomDistance, Mathf.Min(topDistance, Mathf.Min(leftDistance, rightDistance)));
if (bottomDistance == minDistance)
{
surfacePoint = bottomP;
angle = 0f;
return;
}
if (topDistance == minDistance)
{
surfacePoint = topP;
angle = 180f;
return;
}
if (leftDistance == minDistance)
{
surfacePoint = leftP;
angle = 90f;
return;
}
surfacePoint = rightP;
angle = -90f;
}
protected Pose MinimalRotationPoseAtSurface(in Pose userPose, in Pose snapPose)
{
Vector3 desiredPos = userPose.position;
Quaternion baseRot = snapPose.rotation;
Quaternion desiredRot = userPose.rotation;
Vector3 up = Rotation * Vector3.up;
Quaternion bottomRot = baseRot;
Quaternion topRot = Quaternion.AngleAxis(180f, up) * baseRot;
Quaternion leftRot = Quaternion.AngleAxis(90f, up) * baseRot;
Quaternion rightRot = Quaternion.AngleAxis(-90f, up) * baseRot;
float bottomDot = PoseUtils.RotationalSimilarity(bottomRot, desiredRot);
float topDot = PoseUtils.RotationalSimilarity(topRot, desiredRot);
float leftDot = PoseUtils.RotationalSimilarity(leftRot, desiredRot);
float rightDot = PoseUtils.RotationalSimilarity(rightRot, desiredRot);
Vector3 rightDir = Rotation * Vector3.right;
Vector3 forwardDir = Rotation * Vector3.forward;
Vector3 bottomLeft, bottomRight, topLeft, topRight;
CalculateCorners(out bottomLeft, out bottomRight, out topLeft, out topRight);
float maxDot = Mathf.Max(bottomDot, Mathf.Max(topDot, Mathf.Max(leftDot, rightDot)));
if (bottomDot == maxDot)
{
Vector3 projBottom = ProjectOnSegment(desiredPos, (bottomLeft + rightDir * SnapOffset.y, bottomRight + rightDir * SnapOffset.x));
return new Pose(projBottom, bottomRot);
}
if (topDot == maxDot)
{
Vector3 projTop = ProjectOnSegment(desiredPos, (topLeft - rightDir * SnapOffset.x, topRight - rightDir * SnapOffset.y));
return new Pose(projTop, topRot);
}
if (leftDot == maxDot)
{
Vector3 projLeft = ProjectOnSegment(desiredPos, (bottomLeft - forwardDir * SnapOffset.z, topLeft - forwardDir * SnapOffset.w));
return new Pose(projLeft, leftRot);
}
Vector3 projRight = ProjectOnSegment(desiredPos, (bottomRight + forwardDir * SnapOffset.w, topRight + forwardDir * SnapOffset.z));
return new Pose(projRight, rightRot);
}
protected Pose MinimalTranslationPoseAtSurface(in Pose userPose, in Pose snapPose)
{
Vector3 desiredPos = userPose.position;
Quaternion baseRot = snapPose.rotation;
Vector3 surfacePoint;
float surfaceAngle;
NearestPointAndAngleInSurface(desiredPos, out surfacePoint, out surfaceAngle);
Quaternion surfaceRotation = RotateUp(baseRot, surfaceAngle);
return new Pose(surfacePoint, surfaceRotation);
}
protected Quaternion RotateUp(Quaternion baseRot, float angle)
{
Quaternion offset = Quaternion.AngleAxis(angle, Rotation * Vector3.up);
return offset * baseRot;
}
#region Inject
public void InjectAllBoxSurface(BoxSurfaceData data,
Transform relativeTo, Transform gripPoint)
{
InjectData(data);
InjectRelativeTo(relativeTo);
InjectGripPoint(gripPoint);
}
public void InjectData(BoxSurfaceData data)
{
_data = data;
}
public void InjectRelativeTo(Transform relativeTo)
{
_relativeTo = relativeTo;
}
public void InjectGripPoint(Transform gripPoint)
{
_gripPoint = gripPoint;
}
#endregion
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,66 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
************************************************************************************/
using UnityEngine;
namespace Oculus.Interaction.HandPosing.SnapSurfaces
{
/// <summary>
/// This interface defines the method needed to use snap surfaces. They allow finding the
/// nearest poses at the surface to a given set of parameters as well as duplicating and
/// mirroring the surface.
/// </summary>
public interface ISnapSurface
{
/// <summary>
/// Finds the Pose at the surface that is the closest to the given pose.
/// </summary>
/// <param name="targetPose">The pose to find the nearest to.</param>
/// <param name="reference">The reference snap point to use for measuring at the surface.</param>
/// <param name="bestPose">The best found pose at the surface.<</param>
/// <returns>The score indicating how good the found pose was, -1 for invalid result.</returns>
float CalculateBestPoseAtSurface(in Pose targetPose, in Pose reference, out Pose bestPose, in PoseMeasureParameters scoringModifier);
/// <summary>
/// Finds the Pose at the surface that is the closest to the given ray.
/// </summary>
/// <param name="targetRay">Ray searching for the nearest snap pose</param>
/// <param name="reference">The reference snap point to use for measuring at the surface.</param>
/// <param name="bestPose">The best found pose at the surface.</param>
/// <returns>True if the pose was found</returns>
bool CalculateBestPoseAtSurface(Ray targetRay, in Pose reference, out Pose bestPose);
/// <summary>
/// Method for mirroring a Pose around the surface.
/// Different surfaces will prefer mirroring along different axis.
/// </summary>
/// <param name="gripPose">The Pose to be mirrored.</param>
/// <returns>A new pose mirrored at this surface.</returns>
Pose MirrorPose(in Pose gripPose);
/// <summary>
/// Creates a new ISnapSurface under the selected gameobject
/// that is a mirror version of the current.
/// </summary>
/// <param name="gameObject">The gameobject in which to place the new ISnapSurface.</param>
/// <returns>A mirror of this ISnapSurface.</returns>
ISnapSurface CreateMirroredSurface(GameObject gameObject);
/// <summary>
/// Creates a new ISnapSurface under the selected gameobject
/// with the same data as this one.
/// </summary>
/// <param name="gameObject">The gameobject in which to place the new ISnapSurface.</param>
/// <returns>A clone of this ISnapSurface.</returns>
ISnapSurface CreateDuplicatedSurface(GameObject gameObject);
}
}

View File

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

View File

@@ -0,0 +1,76 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
************************************************************************************/
using UnityEngine;
namespace Oculus.Interaction.HandPosing.SnapSurfaces
{
public static class SnapSurfaceHelper
{
public delegate Pose PoseCalculator(in Pose desiredPose, in Pose snapPose);
/// <summary>
/// Finds the best pose comparing the one that requires the minimum rotation
/// and minimum translation.
/// </summary>
/// <param name="desiredPose">Pose to measure from.</param>
/// <param name="snapPose">Reference pose of the surface.</param>
/// <param name="bestPose">Nearest pose to the desired one at the surface.</param>
/// <param name="maxDistance">Max distance to consider for scoring.</param>
/// <param name="positionRotationWeight">Ratio of position and rotation to select between poses.</param>
/// <param name="minimalTranslationPoseCalculator">Delegate to calculate the nearest, by position, pose at a surface.</param>
/// <param name="minimalRotationPoseCalculator">Delegate to calculate the nearest, by rotation, pose at a surface.</param>
/// <returns>The score, normalized, of the best pose.</returns>
public static float CalculateBestPoseAtSurface(in Pose desiredPose, in Pose snapPose, out Pose bestPose,
in PoseMeasureParameters scoringModifier,
PoseCalculator minimalTranslationPoseCalculator, PoseCalculator minimalRotationPoseCalculator)
{
float bestScore;
Pose minimalRotationPose = minimalRotationPoseCalculator(desiredPose, snapPose);
if (scoringModifier.MaxDistance > 0)
{
Pose minimalTranslationPose = minimalTranslationPoseCalculator(desiredPose, snapPose);
bestPose = SelectBestPose(minimalRotationPose, minimalTranslationPose, desiredPose, scoringModifier, out bestScore);
}
else
{
bestPose = minimalRotationPose;
bestScore = PoseUtils.RotationalSimilarity(desiredPose.rotation, bestPose.rotation);
}
return bestScore;
}
/// <summary>
/// Compares two poses to a reference and returns the most similar one
/// </summary>
/// <param name="a">First pose to compare with the reference.</param>
/// <param name="b">Second pose to compare with the reference.</param>
/// <param name="reference">Reference pose to measure from.</param>
/// <param name="normalizedWeight">Position to rotation scoring ratio.</param>
/// <param name="maxDistance">Max distance to measure the score.</param>
/// <param name="bestScore">Out value with the score of the best pose.</param>
/// <returns>The most similar pose to reference out of a and b</returns>
private static Pose SelectBestPose(in Pose a, in Pose b, in Pose reference, PoseMeasureParameters scoringModifier, out float bestScore)
{
float aScore = PoseUtils.Similarity(reference, a, scoringModifier);
float bScore = PoseUtils.Similarity(reference, b, scoringModifier);
if (aScore >= bScore)
{
bestScore = aScore;
return a;
}
bestScore = bScore;
return b;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,134 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
************************************************************************************/
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Serialization;
namespace Oculus.Interaction.HandPosing.Visuals
{
/// <summary>
/// A static (non-user controlled) representation of a hand. This script is used
/// to be able to manually visualize hand grab poses.
/// </summary>
[RequireComponent(typeof(HandPuppet))]
public class HandGhost : MonoBehaviour
{
/// <summary>
/// The puppet is used to actually move the representation of the hand.
/// </summary>
[SerializeField]
private HandPuppet _puppet;
/// <summary>
/// The GripPoint of the hand. Needs to be
/// at the same position/rotation as the gripPoint used
/// by the visual HandPuppet controlled by the user.
/// </summary>
[SerializeField]
private Transform _gripPoint;
/// <summary>
/// The HandGrab point can be set so the ghost automatically
/// adopts the desired pose of said point.
/// </summary>
[SerializeField, Optional]
private HandGrabPoint _handGrabPoint;
#region editor events
protected virtual void Reset()
{
_puppet = this.GetComponent<HandPuppet>();
_handGrabPoint = this.GetComponentInParent<HandGrabPoint>();
}
protected virtual void OnValidate()
{
if (_puppet == null
|| _gripPoint == null)
{
return;
}
if (_handGrabPoint == null)
{
HandGrabPoint point = this.GetComponentInParent<HandGrabPoint>();
if (point != null)
{
SetPose(point);
}
}
else if (_handGrabPoint != null)
{
SetPose(_handGrabPoint);
}
}
#endregion
protected virtual void Start()
{
Assert.IsNotNull(_puppet);
Assert.IsNotNull(_gripPoint);
}
/// <summary>
/// Relay to the Puppet to set the ghost hand to the desired static pose
/// </summary>
/// <param name="handGrabPoint">The point to read the HandPose from</param>
public void SetPose(HandGrabPoint handGrabPoint)
{
HandPose userPose = handGrabPoint.HandPose;
if (userPose == null)
{
return;
}
Transform relativeTo = handGrabPoint.RelativeTo;
_puppet.SetJointRotations(userPose.JointRotations);
SetGripPose(handGrabPoint.RelativeGrip, relativeTo);
}
/// <summary>
/// Moves the underlying puppet so the grip point aligns with the given parameters
/// </summary>
/// <param name="gripPose">The relative grip pose to align the hand to</param>
/// <param name="relativeTo">The object to use as anchor</param>
public void SetGripPose(Pose gripPose, Transform relativeTo)
{
Pose inverseGrip = _gripPoint.RelativeOffset(this.transform);
gripPose.Premultiply(inverseGrip);
gripPose.Postmultiply(relativeTo.GetPose());
_puppet.SetRootPose(gripPose);
}
#region Inject
public void InjectHandPuppet(HandPuppet puppet)
{
_puppet = puppet;
}
public void InjectGripPoint(Transform gripPoint)
{
_gripPoint = gripPoint;
}
public void InjectOptionalHandGrabPoint(HandGrabPoint handGrabPoint)
{
_handGrabPoint = handGrabPoint;
}
public void InjectAllHandGhost(HandPuppet puppet, Transform gripPoint)
{
InjectHandPuppet(puppet);
InjectGripPoint(gripPoint);
}
#endregion
}
}

View File

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

View File

@@ -0,0 +1,47 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
************************************************************************************/
using Oculus.Interaction.Input;
using UnityEngine;
namespace Oculus.Interaction.HandPosing.Visuals
{
/// <summary>
/// Holds references to the prefabs for Ghost-Hands, so they can be instantiated
/// in runtime to represent static poses.
/// </summary>
[CreateAssetMenu(menuName = "Oculus/Interaction/SDK/Pose Authoring/Hand Ghost Provider")]
public class HandGhostProvider : ScriptableObject
{
/// <summary>
/// The prefab for the left hand ghost.
/// </summary>
[SerializeField]
private HandGhost _leftHand;
/// <summary>
/// The prefab for the right hand ghost.
/// </summary>
[SerializeField]
private HandGhost _rightHand;
/// <summary>
/// Helper method to obtain the prototypes
/// The result is to be instanced, not used directly.
/// </summary>
/// <param name="handedness">The desired handedness of the ghost prefab</param>
/// <returns>A Ghost prefab</returns>
public HandGhost GetHand(Handedness handedness)
{
return handedness == Handedness.Left ? _leftHand : _rightHand;
}
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,106 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
************************************************************************************/
using Oculus.Interaction.Input;
using System.Collections.Generic;
using UnityEngine;
namespace Oculus.Interaction.HandPosing.Visuals
{
/// <summary>
/// Stores the translation between hand tracked data and the represented joint.
/// </summary>
[System.Serializable]
public class HandJointMap
{
/// <summary>
/// The unique identifier for the joint.
/// </summary>
public HandJointId id;
/// <summary>
/// The transform that this joint drives.
/// </summary>
public Transform transform;
/// <summary>
/// The rotation offset between the hand-tracked joint, and the represented joint.
/// </summary>
public Vector3 rotationOffset;
/// <summary>
/// Get the rotationOffset as a Quaternion.
/// </summary>
public Quaternion RotationOffset
{
get
{
return Quaternion.Euler(rotationOffset);
}
}
/// <summary>
/// Get the raw rotation of the joint, taken from the tracking data
/// </summary>
public Quaternion TrackedRotation
{
get
{
return Quaternion.Inverse(RotationOffset) * transform.localRotation;
}
}
}
/// <summary>
/// A collection of joint maps to quick access the joints that are actually available in the hand rig.
/// Stores an internal array of indices so it can transform from positions in the HandPose.HAND_JOINTIDS collection
/// to the JointMap List without having to search for the (maybe unavailable) index every time.
/// </summary>
[System.Serializable]
public class JointCollection
{
/// <summary>
/// List of indices of the joints in the actual rig for quick access
/// </summary>
[SerializeField]
[HideInInspector]
private int[] _jointIndices = new int[FingersMetadata.HAND_JOINT_IDS.Length];
/// <summary>
/// List of joints in the actual rig
/// </summary>
[SerializeField]
[HideInInspector]
private List<HandJointMap> _jointMaps;
public JointCollection(List<HandJointMap> joints)
{
_jointMaps = joints;
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; i++)
{
HandJointId boneId = FingersMetadata.HAND_JOINT_IDS[i];
_jointIndices[i] = joints.FindIndex(bone => bone.id == boneId);
}
}
public HandJointMap this[int jointIndex]
{
get
{
int joint = _jointIndices[jointIndex];
if (joint >= 0)
{
return _jointMaps[joint];
}
return null;
}
}
}
}

View File

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

View File

@@ -0,0 +1,122 @@
/************************************************************************************
Copyright : Copyright (c) Facebook Technologies, LLC and its affiliates. All rights reserved.
Your use of this SDK or tool is subject to the Oculus SDK License Agreement, available at
https://developer.oculus.com/licenses/oculussdk/
Unless required by applicable law or agreed to in writing, the Utilities SDK distributed
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
ANY KIND, either express or implied. See the License for the specific language governing
permissions and limitations under the License.
************************************************************************************/
using Oculus.Interaction.Input;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
namespace Oculus.Interaction.HandPosing.Visuals
{
/// <summary>
/// This class is a visual representation of a rigged hand (typically a skin-mesh renderer)
/// that can move its position/rotation and the rotations of the joints that compose it.
/// It also can offset the rotations of the individual joints, adapting the provided
/// data to any rig.
/// </summary>
public class HandPuppet : MonoBehaviour
{
/// <summary>
/// Joints of the hand and their relative rotations compared to hand-tracking.
/// </summary>
[SerializeField]
private List<HandJointMap> _jointMaps = new List<HandJointMap>(FingersMetadata.HAND_JOINT_IDS.Length);
/// <summary>
/// General getter for the joints of the hand.
/// </summary>
public List<HandJointMap> JointMaps
{
get
{
return _jointMaps;
}
}
/// <summary>
/// Current scale of the represented hand.
/// </summary>
public float Scale
{
get
{
return this.transform.localScale.x;
}
set
{
this.transform.localScale = Vector3.one * value;
}
}
private JointCollection _jointsCache;
private JointCollection JointsCache
{
get
{
if (_jointsCache == null)
{
_jointsCache = new JointCollection(_jointMaps);
}
return _jointsCache;
}
}
/// <summary>
/// Rotates all the joints in this puppet to the desired pose.
/// </summary>
/// <param name="jointRotations">
/// Array of rotations to use for the fingers. It must follow the FingersMetaData.HAND_JOINT_IDS order.
/// </param>
public void SetJointRotations(in Quaternion[] jointRotations)
{
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; ++i)
{
HandJointMap jointMap = JointsCache[i];
if (jointMap != null)
{
Transform jointTransform = jointMap.transform;
Quaternion targetRot = jointMap.RotationOffset * jointRotations[i];
jointTransform.localRotation = targetRot;
}
}
}
/// <summary>
/// Rotates and Translate the hand Wrist so it aligns with the given pose.
/// It can apply an offset for when using controllers.
/// </summary>
/// <param name="rootPose">The Wrist Pose to set this puppet to.</param>
/// </param>
public void SetRootPose(in Pose rootPose)
{
this.transform.SetPose(rootPose, Space.World);
}
/// <summary>
/// Copies the rotations of all the joints available in the puppet
/// as they are visually presented.
/// Note that any missing joints are skipped.
/// </summary>
/// <param name="result">Structure to copy the joints to</param>
public void CopyCachedJoints(ref HandPose result)
{
for (int i = 0; i < FingersMetadata.HAND_JOINT_IDS.Length; ++i)
{
HandJointMap jointMap = JointsCache[i];
if (jointMap != null)
{
result.JointRotations[i] = jointMap.TrackedRotation;
}
}
}
}
}

View File

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