mixedreality/com.microsoft.mixedreality..../SDK/Features/UX/Scripts/Slate/HandInteractionPanZoom.cs

940 lines
33 KiB
C#

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Microsoft.MixedReality.Toolkit.Input;
using Microsoft.MixedReality.Toolkit.Utilities;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
namespace Microsoft.MixedReality.Toolkit.UI
{
[AddComponentMenu("Scripts/MRTK/SDK/HandInteractionPanZoom")]
public class HandInteractionPanZoom :
BaseFocusHandler, IMixedRealityTouchHandler, IMixedRealityPointerHandler, IMixedRealitySourceStateHandler
{
/// <summary>
/// Internal data stored for each hand or pointer.
/// </summary>
protected class HandPanData
{
public bool IsActive = true;
public bool IsSourceNear = false;
public Vector2 uvOffset = Vector2.zero;
public Vector2 touchingQuadCoord = Vector2.zero;
public Vector2 uvTotalOffset = Vector2.zero;
public Vector3 touchingPoint = Vector3.zero;
public Vector3 touchingPointSmoothed = Vector3.zero;
public Vector3 touchingInitialPt = Vector3.zero;
public Vector3 touchingRayOffset = Vector3.zero;
public Vector2 touchingInitialUV = Vector2.zero;
public Vector2 touchingUVOffset = Vector2.zero;
public Vector2 touchingUVTotalOffset = Vector2.zero;
public Vector3 initialProjectedOffset = Vector3.zero;
public IMixedRealityInputSource touchingSource = null;
public IMixedRealityController currentController = null;
public IMixedRealityPointer currentPointer = null;
}
#region Serialized Fields
[SerializeField]
[FormerlySerializedAs("enabled")]
private bool isEnabled = true;
/// <summary>
/// This Property sets and gets whether a the pan/zoom behavior is active.
/// </summary>
public bool Enabled { get => isEnabled; set => isEnabled = value; }
[Header("Behavior")]
[SerializeField]
private bool enableZoom = false;
[SerializeField]
private bool lockHorizontal = false;
[SerializeField]
private bool lockVertical = false;
[SerializeField]
[Tooltip("If this is checked, Max Pan Horizontal and Max Pan Vertical are ignored.")]
private bool unlimitedPan = true;
[SerializeField]
[Range(1.0f, 20.0f)]
private float maxPanHorizontal = 2;
[SerializeField]
[Range(1.0f, 20.0f)]
private float maxPanVertical = 2;
[SerializeField]
[Range(0.1f, 1.0f)]
private float minScale = 0.2f;
[SerializeField]
[Range(1.0f, 10.0f)]
private float maxScale = 1.5f;
[SerializeField]
[Range(0.0f, 0.99f)]
[Tooltip("a value of 0 results in panning coming to a complete stop when released.")]
private float momentumHorizontal = 0.9f;
[SerializeField]
[Tooltip("a value of 0 results in panning coming to a complete stop when released.")]
[Range(0.0f, 0.99f)]
private float momentumVertical = 0.9f;
[SerializeField]
[Range(0.0f, 99.0f)]
private float panZoomSmoothing = 80.0f;
[Header("Visual affordance")]
[SerializeField]
[Tooltip("If affordance geometry is desired to emphasize the touch points(leftPoint and rightPoint) and the center point between them (reticle), assign them here.")]
[FormerlySerializedAs("reticle")]
private GameObject centerPoint = null;
[SerializeField]
private GameObject leftPoint = null;
[SerializeField]
private GameObject rightPoint = null;
[Tooltip("When the slate is touched, what color to change on the ProximityLight center color override to. (Assumes the target material uses a proximity light and proximity light color override)")]
[SerializeField]
private Color proximityLightCenterColor = new Color(0.25f, 0.25f, 0.25f, 0.0f);
[SerializeField]
[Tooltip("Current scale value. 1 is the original 100%.")]
private float currentScale;
/// <summary>
/// Current scale value. 1 is the original 100%.
/// </summary>
public float CurrentScale => currentScale;
/// <summary>
/// Returns the current pan delta (pan value - previous pan value)
/// in UV coordinates (0 being no pan, 1 being pan of the entire slate)
/// </summary>
public Vector2 CurrentPanDelta => totalUVOffset;
[Header("Events")]
public PanUnityEvent PanStarted = new PanUnityEvent();
public PanUnityEvent PanStopped = new PanUnityEvent();
public PanUnityEvent PanUpdated = new PanUnityEvent();
#endregion Serialized Fields
#region Private Properties
private Mesh mesh;
private MeshFilter meshFilter;
private BoxCollider boxCollider;
private bool TouchActive => handDataMap.Count > 0;
private bool ScaleActive => enableZoom && handDataMap.Count > 1;
private float previousContactRatio = 1.0f;
private float initialTouchDistance = 0.0f;
private Vector2 totalUVOffset = Vector2.zero;
private Vector2 totalUVScale = Vector2.one;
private bool affordancesVisible = false;
private float runningAverageSmoothing = 0.0f;
private const float percentToDecimal = 0.01f;
private Material currentMaterial;
private int proximityLightCenterColorID;
private Color defaultProximityLightCenterColor;
private List<Vector2> unTransformedUVs = new List<Vector2>();
private Dictionary<uint, HandPanData> handDataMap = new Dictionary<uint, HandPanData>();
private List<Vector2> uvs = new List<Vector2>();
private List<Vector2> uvsOrig = new List<Vector2>();
private List<Vector2> uvDeltas = new List<Vector2>();
private bool oldIsTargetPositionLockedOnFocusLock;
#if UNITY_2019_3_OR_NEWER
// Quad meshes by default (in 2019 and higher) appear to follow the vertex order
// specified here: https://docs.unity3d.com/Manual/Example-CreatingaBillboardPlane.html
// That is, LowerLeft->LowerRight->UpperLeft->UpperRight
// Note that even though the example on that page is one that creates a quad manually
// using a specific order of vertices, this order seems to be what a quad mesh defaults to.
// This was discovered when looking into an issue on the SlateTests, which depend on the
// projection math within this to be using the correct right and up vectors.
private const int UpperLeftQuadIndex = 2;
private const int UpperRightQuadIndex = 3;
private const int LowerLeftQuadIndex = 0;
#else // !UNITY_2019_3_OR_NEWER
// Quad meshes in 2018 and lower appear to follow a vertex order that looks like this:
// [0] "(-0.5, -0.5, 0.0)"
// [1] "(0.5, 0.5, 0.0)"
// [2] "(0.5, -0.5, 0.0)"
// [3] "(-0.5, 0.5, 0.0)"
// That is, LowerLeft->UpperRight->LowerRight->UpperLeft
// Note that the ifdefs only cover +/- 2019.3 because that was the min tested version
// for Unity 2019 - this could very well be needed for 2019.2 and 2019.1, but with 2019.4
// out at this point, support is mainly on the LTS release.
private const int UpperLeftQuadIndex = 3;
private const int UpperRightQuadIndex = 1;
private const int LowerLeftQuadIndex = 0;
#endif
#endregion Private Properties
/// <summary>
/// This function sets the pan and zoom back to their starting settings.
/// </summary>
public void Reset()
{
mesh.SetUVs(0, unTransformedUVs);
totalUVOffset = Vector2.zero;
totalUVScale = Vector2.one;
initialTouchDistance = 0.0f;
}
#region MonoBehaviour Handlers
private void Awake()
{
Initialize();
}
private void Update()
{
if (isEnabled)
{
if (TouchActive)
{
foreach (uint key in handDataMap.Keys)
{
if (UpdateHandTouchingPoint(key))
{
MoveTouch(key);
}
}
totalUVOffset = GetUvOffset();
}
UpdateIdle();
UpdateUVMapping();
if (!TouchActive && affordancesVisible)
{
SetAffordancesActive(false);
}
if (affordancesVisible)
{
if (centerPoint != null)
{
centerPoint.transform.position = GetContactCenter();
}
if (leftPoint != null)
{
leftPoint.transform.position = GetContactForHand(Handedness.Left);
}
if (rightPoint != null)
{
rightPoint.transform.position = GetContactForHand(Handedness.Right);
}
}
}
}
#endregion MonoBehaviour Handlers
#region Private Methods
private bool TryGetMRControllerRayPoint(HandPanData data, out Vector3 rayPoint)
{
if (data.currentPointer != null && data.currentController != null && data.currentController.IsPositionAvailable)
{
rayPoint = data.touchingInitialPt + (SnapFingerToQuad(data.currentPointer.Position) - data.initialProjectedOffset);
return true;
}
rayPoint = Vector3.zero;
return false;
}
private bool UpdateHandTouchingPoint(uint sourceId)
{
Vector3 tryHandPoint = Vector3.zero;
bool tryGetSucceeded = false;
if (handDataMap.ContainsKey(sourceId))
{
HandPanData data = handDataMap[sourceId];
if (data.IsActive)
{
if (data.IsSourceNear)
{
tryGetSucceeded = TryGetHandPositionFromController(data.currentController, TrackedHandJoint.IndexTip, out tryHandPoint);
}
else
{
tryGetSucceeded = TryGetHandPositionFromController(data.currentController, TrackedHandJoint.Palm, out tryHandPoint);
}
if (!tryGetSucceeded)
{
tryGetSucceeded = TryGetMRControllerRayPoint(data, out tryHandPoint);
}
if (tryGetSucceeded)
{
tryHandPoint = SnapFingerToQuad(tryHandPoint);
Vector3 unfilteredTouchPt = data.IsSourceNear ? tryHandPoint : tryHandPoint + data.touchingRayOffset;
runningAverageSmoothing = panZoomSmoothing * percentToDecimal;
unfilteredTouchPt *= (1.0f - runningAverageSmoothing);
data.touchingPointSmoothed = (data.touchingPointSmoothed * runningAverageSmoothing) + unfilteredTouchPt;
data.touchingPoint = data.touchingPointSmoothed;
}
}
}
return true;
}
private bool TryGetHandRayPoint(IMixedRealityController controller, out Vector3 handRayPoint)
{
if (controller != null &&
controller.InputSource != null &&
controller.InputSource.Pointers != null &&
controller.InputSource.Pointers.Length > 0 &&
controller.InputSource.Pointers[0].Result != null)
{
handRayPoint = controller.InputSource.Pointers[0].Result.Details.Point;
return true;
}
handRayPoint = Vector3.zero;
return false;
}
private void Initialize()
{
SetAffordancesActive(false);
// Check for boxcollider
boxCollider = GetComponent<BoxCollider>();
if (boxCollider == null)
{
Debug.Log("The GameObject that runs this script must have a BoxCollider attached.");
}
else
{
Renderer renderer = this.GetComponent<Renderer>();
Material material = (renderer != null) ? renderer.material : null;
if ((material != null) && (material.mainTexture != null))
{
material.mainTexture.wrapMode = TextureWrapMode.Repeat;
}
}
// Get material
currentMaterial = this.gameObject.GetComponent<Renderer>().material;
proximityLightCenterColorID = Shader.PropertyToID("_ProximityLightCenterColorOverride");
bool materialValid = currentMaterial != null && currentMaterial.HasProperty(proximityLightCenterColorID);
defaultProximityLightCenterColor = materialValid ?
currentMaterial.GetColor(proximityLightCenterColorID) :
new Color(0.0f, 0.0f, 0.0f, 0.0f);
// Precache references
meshFilter = gameObject.GetComponent<MeshFilter>();
if (meshFilter == null)
{
Debug.Log("The GameObject: " + this.gameObject.name + " " + "does not have a Mesh component.");
}
else
{
mesh = meshFilter.mesh;
}
mesh.GetUVs(0, unTransformedUVs);
}
private void UpdateIdle()
{
if (!TouchActive)
{
if (Mathf.Abs(totalUVOffset.x) < 0.01f && Mathf.Abs(totalUVOffset.y) < 0.01f)
{
totalUVOffset = Vector2.zero;
}
else
{
totalUVOffset = new Vector2(totalUVOffset.x * momentumHorizontal, totalUVOffset.y * momentumVertical);
RaisePanning(0);
}
}
}
private void UpdateUVMapping()
{
mesh.GetUVs(0, uvs);
uvsOrig.Clear();
uvsOrig.AddRange(uvs);
Vector2 offsetUVDelta = new Vector2(-totalUVOffset.x, totalUVOffset.y);
// Scale
if (ScaleActive)
{
var scaleUVCentroid = GetDisplayedUVCentroid(uvs);
var currentContactRatio = GetUVScaleFromTouches();
var scaleUVDelta = currentContactRatio / previousContactRatio;
previousContactRatio = currentContactRatio;
currentScale = totalUVScale.x / scaleUVDelta;
// Test for scale limits
if (currentScale > minScale && currentScale < maxScale)
{
uvDeltas.Clear();
for (int i = 0; i < uvs.Count; i++)
{
Vector2 adjustedScaleUVDelta = ((uvs[i] - scaleUVCentroid) / scaleUVDelta) + scaleUVCentroid - uvs[i];
uvDeltas.Add(adjustedScaleUVDelta + offsetUVDelta);
}
UpdateUV(uvs, uvDeltas);
Vector2 upperLeft = uvs[UpperLeftQuadIndex];
Vector2 upperRight = uvs[UpperRightQuadIndex];
Vector2 lowerLeft = uvs[LowerLeftQuadIndex];
totalUVScale.x = upperRight.x - upperLeft.x;
totalUVScale.y = upperLeft.y - lowerLeft.y;
}
}
else
{
// Scroll
UpdateUVWithScroll(uvs, offsetUVDelta);
}
mesh.SetUVs(0, uvs);
}
private void UpdateUVWithScroll(List<Vector2> uvs, Vector2 uvDelta)
{
uvDeltas.Clear();
for (int i = 0; i < uvs.Count; i++)
{
uvDeltas.Add(uvDelta);
}
UpdateUV(uvs, uvDeltas, true);
}
private void UpdateUV(List<Vector2> uvs, List<Vector2> uvDeltas, bool scrollOnly = false)
{
Vector2 tiling = currentMaterial != null ? currentMaterial.mainTextureScale : new Vector2(1.0f, 1.0f);
if (!unlimitedPan)
{
bool xLimited = false;
bool yLimited = false;
for (int i = 0; i < uvs.Count; i++)
{
var uvTestValue = uvs[i] + uvDeltas[i];
if (uvTestValue.x > tiling.x * maxPanHorizontal || uvTestValue.x < -(tiling.x * maxPanHorizontal))
{
xLimited = true;
}
if (uvTestValue.y > tiling.y * maxPanVertical || uvTestValue.y < -(tiling.y * maxPanVertical))
{
yLimited = true;
}
}
if (scrollOnly)
{
for (int i = 0; i < uvs.Count; ++i)
{
uvs[i] = new Vector2(xLimited ? uvs[i].x : uvs[i].x + uvDeltas[i].x, yLimited ? uvs[i].y : uvs[i].y + uvDeltas[i].y);
}
}
else if (!xLimited && !yLimited)
{
for (int i = 0; i < uvs.Count; ++i)
{
uvs[i] += uvDeltas[i];
}
}
}
else
{
for (int i = 0; i < uvs.Count; ++i)
{
uvs[i] += uvDeltas[i];
}
}
}
private float GetUVScaleFromTouches()
{
if (!ScaleActive || initialTouchDistance == 0)
{
return 0.0f;
}
float uvScaleFromTouches = GetContactDistance() / initialTouchDistance;
return uvScaleFromTouches;
}
private void UpdateTouchUVOffset(uint sourceId)
{
HandPanData data = handDataMap[sourceId];
Vector2 currentQuadCoord = GetQuadCoordFromPoint(data.touchingPoint);
data.uvOffset = currentQuadCoord - data.touchingQuadCoord;
data.touchingQuadCoord = currentQuadCoord;
}
private Vector2 GetUvOffset()
{
if (TouchActive && AreSourcesCompatible())
{
Vector2 offset = Vector2.zero;
foreach (uint key in handDataMap.Keys)
{
offset += handDataMap[key].uvOffset;
}
offset /= (float)handDataMap.Count;
return offset;
}
return totalUVOffset;
}
private Vector3 GetContactCenter()
{
Vector3 center = Vector3.zero;
if (handDataMap.Keys.Count > 0)
{
foreach (uint key in handDataMap.Keys)
{
center += handDataMap[key].touchingPoint;
}
center /= (float)handDataMap.Keys.Count;
}
return center;
}
private void SetAffordancesActive(bool active)
{
affordancesVisible = active;
if (centerPoint != null)
{
centerPoint.SetActive(affordancesVisible);
}
if (leftPoint != null)
{
leftPoint.SetActive(affordancesVisible);
}
if (rightPoint != null)
{
rightPoint.SetActive(affordancesVisible);
}
if (currentMaterial != null)
{
currentMaterial.SetColor(proximityLightCenterColorID, active ? proximityLightCenterColor : defaultProximityLightCenterColor);
}
}
private Vector3 GetContactForHand(Handedness hand)
{
Vector3 handPoint = Vector3.zero;
if (handDataMap.Keys.Count > 0)
{
foreach (uint key in handDataMap.Keys)
{
if (handDataMap[key].currentController.ControllerHandedness == hand)
{
return handDataMap[key].touchingPoint;
}
}
}
return handPoint;
}
private bool AreSourcesCompatible()
{
int score = 0;
foreach (uint key in handDataMap.Keys)
{
score += handDataMap[key].IsSourceNear ? 1 : 0;
}
return (score == 0 || score == handDataMap.Keys.Count);
}
private bool AreSourcesNear()
{
foreach (uint key in handDataMap.Keys)
{
if (handDataMap[key].IsSourceNear == false)
{
return false;
}
}
return true;
}
private Vector3 GetTouchPoint()
{
if (TouchActive)
{
Vector3 touchingPoint = Vector3.zero;
foreach (uint key in handDataMap.Keys)
{
touchingPoint += handDataMap[key].touchingPoint;
}
touchingPoint /= (float)handDataMap.Count;
return touchingPoint;
}
return Vector3.zero;
}
private Vector2 GetScaleUVCentroid()
{
return GetUVFromPoint(GetTouchPoint());
}
private Vector2 GetDisplayedUVCentroid(List<Vector2> uvs)
{
Vector2 centroid = Vector2.zero;
for (int i = 0; i < uvs.Count; ++i)
{
centroid += uvs[i];
}
return centroid /= (float)uvs.Count;
}
private float GetContactDistance()
{
if (!ScaleActive || handDataMap.Keys.Count < 2)
{
return 0.0f;
}
int index = 0;
Vector3 a = Vector3.zero;
Vector3 b = Vector3.zero;
foreach (uint key in handDataMap.Keys)
{
if (index == 0)
{
a = handDataMap[key].touchingPoint;
}
else if (index == 1)
{
b = handDataMap[key].touchingPoint;
}
index++;
}
return (b - a).magnitude;
}
private Vector2 GetQuadCoordFromPoint(Vector3 point)
{
Vector2 quadCoord = GetQuadCoord(point);
quadCoord = new Vector2(lockHorizontal ? 0.0f : quadCoord.x, lockVertical ? 0.0f : quadCoord.y);
return quadCoord;
}
private Vector2 GetUVFromQuadCoord(Vector2 coord)
{
Vector2 uvCoord = Vector2.zero;
Vector2[] uvs = mesh.uv;
Vector2 upperLeft = uvs[UpperLeftQuadIndex];
Vector2 upperRight = uvs[UpperRightQuadIndex];
Vector2 lowerLeft = uvs[LowerLeftQuadIndex];
float magVertical = (lowerLeft - upperLeft).magnitude;
float magHorizontal = (upperRight - upperLeft).magnitude;
if (!Mathf.Approximately(0, magVertical) && !Mathf.Approximately(0, magHorizontal))
{
// Get coord projection on uv coordinates then divide by length to get quad coord 0 to 1
uvCoord.x = Vector2.Dot(coord - upperLeft, upperRight - upperLeft) / (magHorizontal * magHorizontal);
uvCoord.y = Vector2.Dot(coord - upperLeft, lowerLeft - upperLeft) / (magVertical * magVertical);
}
return uvCoord;
}
private Vector2 GetUVFromPoint(Vector3 point)
{
Vector2 quadCoord = GetQuadCoordFromPoint(point);
return GetUVFromQuadCoord(quadCoord);
}
private Vector2 GetQuadCoord(Vector3 point)
{
Vector2 quadCoord = Vector2.zero;
Vector3[] vertices = mesh.vertices;
Vector3 upperLeft = transform.TransformPoint(vertices[UpperLeftQuadIndex]);
Vector3 upperRight = transform.TransformPoint(vertices[UpperRightQuadIndex]);
Vector3 lowerLeft = transform.TransformPoint(vertices[LowerLeftQuadIndex]);
float magVertical = (lowerLeft - upperLeft).magnitude;
float magHorizontal = (upperRight - upperLeft).magnitude;
if (!Mathf.Approximately(0, magVertical) && !Mathf.Approximately(0, magHorizontal))
{
// Get point projection on vertices coordinates then divide by length to get quad coord 0 to 1
quadCoord.x = Vector3.Dot(point - upperLeft, upperRight - upperLeft) / (magHorizontal * magHorizontal);
quadCoord.y = Vector3.Dot(point - upperLeft, lowerLeft - upperLeft) / (magVertical * magVertical);
}
return quadCoord;
}
private Vector3 SnapFingerToQuad(Vector3 pointToSnap)
{
Vector3 planePoint = this.transform.TransformPoint(mesh.vertices[0]);
Vector3 planeNormal = gameObject.transform.forward;
return Vector3.ProjectOnPlane(pointToSnap - planePoint, planeNormal) + planePoint;
}
private void SetHandDataFromController(IMixedRealityController controller, IMixedRealityPointer pointer, bool isNear)
{
HandPanData data = new HandPanData();
data.IsSourceNear = isNear;
data.IsActive = true;
data.touchingSource = controller.InputSource;
data.currentController = controller;
data.currentPointer = pointer;
if (isNear)
{
if (TryGetHandPositionFromController(data.currentController, TrackedHandJoint.IndexTip, out Vector3 touchPosition))
{
data.touchingInitialPt = SnapFingerToQuad(touchPosition);
data.touchingPointSmoothed = data.touchingInitialPt;
data.touchingPoint = data.touchingInitialPt;
}
}
else // Is far
{
if (data.currentPointer is GGVPointer)
{
data.touchingInitialPt = SnapFingerToQuad(data.currentPointer.Position);
data.touchingPoint = data.touchingInitialPt;
data.touchingPointSmoothed = data.touchingInitialPt;
}
else if (TryGetHandRayPoint(controller, out Vector3 handRayPt))
{
data.touchingInitialPt = SnapFingerToQuad(handRayPt);
data.touchingPoint = data.touchingInitialPt;
data.touchingPointSmoothed = data.touchingInitialPt;
if (TryGetHandPositionFromController(data.currentController, TrackedHandJoint.Palm, out Vector3 touchPosition))
{
data.touchingRayOffset = handRayPt - SnapFingerToQuad(touchPosition);
}
}
}
// Store value in case of MRController
if (data.currentPointer != null)
{
Vector3 pt = data.currentPointer.Position;
data.initialProjectedOffset = SnapFingerToQuad(pt);
}
data.touchingQuadCoord = GetUVFromPoint(data.touchingPoint);
data.touchingInitialUV = data.touchingQuadCoord;
data.touchingUVTotalOffset = totalUVOffset;
data.touchingUVOffset = data.touchingUVTotalOffset;
handDataMap.Add(data.touchingSource.SourceId, data);
initialTouchDistance = GetContactDistance();
totalUVOffset = Vector2.zero;
if (handDataMap.Keys.Count > 1)
{
if (initialTouchDistance == 0)
{
initialTouchDistance = GetContactDistance();
}
else
{
float contactDist = GetContactDistance();
initialTouchDistance = contactDist + (initialTouchDistance - contactDist);
}
previousContactRatio = 1.0f;
}
SetAffordancesActive(isNear);
StartTouch(data.touchingSource.SourceId);
}
private bool TryGetHandPositionFromController(IMixedRealityController controller, TrackedHandJoint joint, out Vector3 position)
{
if (controller is IMixedRealityHand hand)
{
if (hand.TryGetJoint(joint, out MixedRealityPose pose))
{
position = pose.Position;
return true;
}
}
position = Vector3.zero;
return false;
}
#endregion Private Methods
#region Internal State Handlers
private void StartTouch(uint sourceId)
{
UpdateTouchUVOffset(sourceId);
RaisePanStarted(sourceId);
}
private void EndTouch(uint sourceId)
{
if (handDataMap.ContainsKey(sourceId))
{
handDataMap.Remove(sourceId);
RaisePanEnded(0);
}
}
private void EndAllTouches()
{
if (handDataMap.Count > 0)
{
handDataMap.Clear();
RaisePanEnded(0);
}
}
private void MoveTouch(uint sourceId)
{
UpdateTouchUVOffset(sourceId);
RaisePanning(sourceId);
}
#endregion Internal State Handlers
#region Fire Events to Listening Objects
private void RaisePanStarted(uint sourceId)
{
HandPanEventData eventData = new HandPanEventData();
eventData.PanDelta = GetUvOffset();
PanStarted?.Invoke(eventData);
}
private void RaisePanEnded(uint sourceId)
{
HandPanEventData eventData = new HandPanEventData();
eventData.PanDelta = Vector2.zero;
PanStopped?.Invoke(eventData);
}
private void RaisePanning(uint sourceId)
{
HandPanEventData eventData = new HandPanEventData();
eventData.PanDelta = GetUvOffset();
PanUpdated?.Invoke(eventData);
}
#endregion Fire Events to Listening Objects
#region BaseFocusHandler Methods
/// <inheritdoc />
public override void OnFocusEnter(FocusEventData eventData) { }
/// <inheritdoc />
public override void OnFocusExit(FocusEventData eventData)
{
EndAllTouches();
}
#endregion
#region IMixedRealityTouchHandler
/// <summary>
/// In order to receive Touch Events from the IMixedRealityTouchHandler
/// remember to add a NearInteractionTouchable script to the object that has this script.
/// </summary>
public void OnTouchStarted(HandTrackingInputEventData eventData)
{
EndTouch(eventData.SourceId);
SetHandDataFromController(eventData.Controller, null, true);
eventData.Use();
}
public void OnTouchCompleted(HandTrackingInputEventData eventData)
{
EndTouch(eventData.SourceId);
eventData.Use();
}
public void OnTouchUpdated(HandTrackingInputEventData eventData) { }
#endregion IMixedRealityTouchHandler
#region IMixedRealityInputHandler Methods
/// <summary>
/// The Input Event handlers receive Hand Ray events.
/// </summary>
public void OnPointerDown(MixedRealityPointerEventData eventData)
{
bool isNear = eventData.Pointer is IMixedRealityNearPointer;
oldIsTargetPositionLockedOnFocusLock = eventData.Pointer.IsTargetPositionLockedOnFocusLock;
if (!isNear && eventData.Pointer.Controller.IsRotationAvailable)
{
eventData.Pointer.IsTargetPositionLockedOnFocusLock = false;
}
SetAffordancesActive(false);
EndTouch(eventData.SourceId);
SetHandDataFromController(eventData.Pointer.Controller, eventData.Pointer, isNear);
eventData.Use();
}
public void OnPointerUp(MixedRealityPointerEventData eventData)
{
eventData.Pointer.IsTargetPositionLockedOnFocusLock = oldIsTargetPositionLockedOnFocusLock;
EndTouch(eventData.SourceId);
eventData.Use();
}
#endregion IMixedRealityInputHandler Methods
#region IMixedRealitySourceStateHandler Methods
public void OnSourceLost(SourceStateEventData eventData)
{
EndTouch(eventData.SourceId);
eventData.Use();
}
#endregion IMixedRealitySourceStateHandler Methods
#region Unused Methods
public void OnSourceDetected(SourceStateEventData eventData) { }
public void OnPointerDragged(MixedRealityPointerEventData eventData) { }
public void OnPointerClicked(MixedRealityPointerEventData eventData) { }
#endregion Unused Methods
}
}