AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
HandGuidance.cs
Go to the documentation of this file.
1 // Copyright (c) Microsoft Corporation. All rights reserved.
2 // Licensed under the MIT License. See LICENSE in the project root for license information.
3 
4 using UnityEngine;
5 
6 #if UNITY_WSA
7 #if UNITY_2017_2_OR_NEWER
8 using UnityEngine.XR.WSA.Input;
9 #else
10 using UnityEngine.VR.WSA.Input;
11 #endif
12 #endif
13 
14 namespace HoloToolkit.Unity.InputModule
15 {
19  public class HandGuidance : Singleton<HandGuidance>
20  {
21  [Tooltip("The Cursor object the HandGuidanceIndicator will be positioned around.")]
22  public GameObject Cursor;
23 
24  [Tooltip("GameObject to display when your hand is about to lose tracking.")]
25  public GameObject HandGuidanceIndicator;
26 
27  // Hand source loss risk to start showing a hand indicator.
28  // As the source loss risk approaches 1, the hand is closer to being out of view.
29  [Range(0.0f, 1.0f)]
30  [Tooltip("When to start showing the Hand Guidance Indicator. 1 is out of view, 0 is centered in view.")]
31  public float HandGuidanceThreshold = 0.5f;
32 
33 #if UNITY_WSA
34  private GameObject handGuidanceIndicatorGameObject;
35 
36  private Quaternion defaultHandGuidanceRotation;
37 
38  private uint? currentlyTrackedHand;
39 
40  protected override void Awake()
41  {
42  base.Awake();
43  if (HandGuidanceIndicator == null)
44  {
45  Debug.LogError("Please include a GameObject for the Hand Guidance Indicator.");
46  }
47 
48  if (Cursor == null)
49  {
50  Debug.LogError("Please include a GameObject for the Cursor to display the indicator around.");
51  }
52 
53  if (HandGuidanceIndicator != null)
54  {
55  // Cache the initial rotation of the HandGuidanceIndicator so future rotations
56  // can be done with respect to this rotation.
57  defaultHandGuidanceRotation = HandGuidanceIndicator.transform.rotation;
58  }
59 
60  // Create an object in the scene for the guidance indicator and default it to not be visible.
61  handGuidanceIndicatorGameObject = Instantiate(HandGuidanceIndicator);
62  handGuidanceIndicatorGameObject.SetActive(false);
63 
64  // Register for hand and finger events to know where your hand
65  // is being tracked and what state it is in.
66 #if UNITY_2017_2_OR_NEWER
67  InteractionManager.InteractionSourceLost += InteractionManager_InteractionSourceLost;
68  InteractionManager.InteractionSourceUpdated += InteractionManager_InteractionSourceUpdated;
69  InteractionManager.InteractionSourceReleased += InteractionManager_InteractionSourceReleased;
70 #else
71  InteractionManager.SourceLost += InteractionManager_SourceLost;
72  InteractionManager.SourceUpdated += InteractionManager_SourceUpdated;
73  InteractionManager.SourceReleased += InteractionManager_SourceReleased;
74 #endif
75  }
76 
77  private void ShowHandGuidanceIndicator(InteractionSourceState hand)
78  {
79  if (!currentlyTrackedHand.HasValue)
80  {
81  return;
82  }
83 
84  // Get the position and rotation of the hand guidance indicator and display the indicator object.
85  if (handGuidanceIndicatorGameObject != null)
86  {
87  Vector3 position;
88  Quaternion rotation;
89  GetIndicatorPositionAndRotation(hand, out position, out rotation);
90 
91  handGuidanceIndicatorGameObject.transform.position = position;
92  handGuidanceIndicatorGameObject.transform.rotation = rotation * defaultHandGuidanceRotation;
93  handGuidanceIndicatorGameObject.SetActive(true);
94  }
95  }
96 
97  private void HideHandGuidanceIndicator(InteractionSourceState hand)
98  {
99  if (!currentlyTrackedHand.HasValue)
100  {
101  return;
102  }
103 
104  if (handGuidanceIndicatorGameObject != null)
105  {
106  handGuidanceIndicatorGameObject.SetActive(false);
107  }
108  }
109 
110  private void GetIndicatorPositionAndRotation(InteractionSourceState hand, out Vector3 position, out Quaternion rotation)
111  {
112  // Update the distance from IndicatorParent based on the user's hand's distance from the center of the view.
113  // Bound this distance by this maxDistanceFromCenter field, in meters.
114  const float maxDistanceFromCenter = 0.3f;
115  float distanceFromCenter = (float)(hand.properties.sourceLossRisk * maxDistanceFromCenter);
116 
117  // Subtract direction from origin so that the indicator is between the hand and the origin.
118  position = Cursor.transform.position - hand.properties.sourceLossMitigationDirection * distanceFromCenter;
119  rotation = Quaternion.LookRotation(CameraCache.Main.transform.forward, hand.properties.sourceLossMitigationDirection);
120  }
121 
122 #if UNITY_2017_2_OR_NEWER
123  private void InteractionManager_InteractionSourceUpdated(InteractionSourceUpdatedEventArgs obj)
124  {
125  if (obj.state.source.kind == InteractionSourceKind.Hand)
126  {
127  InteractionSourceState hand = obj.state;
128 
129  // Only display hand indicators when we are in a holding state, since hands going out of view will affect any active gestures.
130  if (!hand.anyPressed)
131  {
132  return;
133  }
134 
135  // Only track a new hand if are not currently tracking a hand.
136  if (!currentlyTrackedHand.HasValue)
137  {
138  currentlyTrackedHand = hand.source.id;
139  }
140  else if (currentlyTrackedHand.Value != hand.source.id)
141  {
142  // This hand is not the currently tracked hand, do not drawn a guidance indicator for this hand.
143  return;
144  }
145 
146  // Start showing an indicator to move your hand toward the center of the view.
147  if (hand.properties.sourceLossRisk > HandGuidanceThreshold)
148  {
149  ShowHandGuidanceIndicator(hand);
150  }
151  else
152  {
153  HideHandGuidanceIndicator(hand);
154  }
155  }
156  }
157 
158  private void InteractionManager_InteractionSourceReleased(InteractionSourceReleasedEventArgs obj)
159  {
160  if (obj.state.source.kind == InteractionSourceKind.Hand)
161  {
162  // Stop displaying the guidance indicator when the user releases their finger from the pressed state.
163  RemoveTrackedHand(obj.state);
164  }
165  }
166 
167  private void InteractionManager_InteractionSourceLost(InteractionSourceLostEventArgs obj)
168  {
169  if (obj.state.source.kind == InteractionSourceKind.Hand)
170  {
171  // Stop displaying the guidance indicator when the user's hand leaves the view.
172  RemoveTrackedHand(obj.state);
173  }
174  }
175 
176  private void RemoveTrackedHand(InteractionSourceState hand)
177  {
178  // Only remove a hand if we are currently tracking a hand, and the hand to remove matches this tracked hand.
179  if (currentlyTrackedHand.HasValue && currentlyTrackedHand.Value == hand.source.id)
180  {
181  // Remove a hand by hiding the guidance indicator and nulling out the currentlyTrackedHand field.
182  handGuidanceIndicatorGameObject.SetActive(false);
183  currentlyTrackedHand = null;
184  }
185  }
186 
187  protected override void OnDestroy()
188  {
189  InteractionManager.InteractionSourceLost -= InteractionManager_InteractionSourceLost;
190  InteractionManager.InteractionSourceUpdated -= InteractionManager_InteractionSourceUpdated;
191  InteractionManager.InteractionSourceReleased -= InteractionManager_InteractionSourceReleased;
192 
193  base.OnDestroy();
194  }
195 #else
196  private void InteractionManager_SourceUpdated(InteractionSourceState hand)
197  {
198  // Only display hand indicators when we are in a holding state, since hands going out of view will affect any active gestures.
199  if (!hand.pressed)
200  {
201  return;
202  }
203 
204  // Only track a new hand if are not currently tracking a hand.
205  if (!currentlyTrackedHand.HasValue)
206  {
207  currentlyTrackedHand = hand.source.id;
208  }
209  else if (currentlyTrackedHand.Value != hand.source.id)
210  {
211  // This hand is not the currently tracked hand, do not drawn a guidance indicator for this hand.
212  return;
213  }
214 
215  // Start showing an indicator to move your hand toward the center of the view.
216  if (hand.properties.sourceLossRisk > HandGuidanceThreshold)
217  {
218  ShowHandGuidanceIndicator(hand);
219  }
220  else
221  {
222  HideHandGuidanceIndicator(hand);
223  }
224  }
225 
226  private void InteractionManager_SourceReleased(InteractionSourceState hand)
227  {
228  // Stop displaying the guidance indicator when the user releases their finger from the pressed state.
229  RemoveTrackedHand(hand);
230  }
231 
232  private void InteractionManager_SourceLost(InteractionSourceState hand)
233  {
234  // Stop displaying the guidance indicator when the user's hand leaves the view.
235  RemoveTrackedHand(hand);
236  }
237 
238  private void RemoveTrackedHand(InteractionSourceState hand)
239  {
240  // Only remove a hand if we are currently tracking a hand, and the hand to remove matches this tracked hand.
241  if (currentlyTrackedHand.HasValue && currentlyTrackedHand.Value == hand.source.id)
242  {
243  // Remove a hand by hiding the guidance indicator and nulling out the currentlyTrackedHand field.
244  handGuidanceIndicatorGameObject.SetActive(false);
245  currentlyTrackedHand = null;
246  }
247  }
248 
249  protected override void OnDestroy()
250  {
251  InteractionManager.SourceLost -= InteractionManager_SourceLost;
252  InteractionManager.SourceUpdated -= InteractionManager_SourceUpdated;
253  InteractionManager.SourceReleased -= InteractionManager_SourceReleased;
254 
255  base.OnDestroy();
256  }
257 #endif
258 #endif
259  }
260 }
Show a hand guidance indicator when the user&#39;s hand is close to leaving the camera&#39;s view...
Definition: HandGuidance.cs:19
The purpose of this class is to provide a cached reference to the main camera. Calling Camera...
Definition: CameraCache.cs:12
static Camera Main
Returns a cached reference to the main camera and uses Camera.main if it hasn&#39;t been cached yet...
Definition: CameraCache.cs:20
Singleton behaviour class, used for components that should only have one instance.
Definition: Singleton.cs:14