AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
SimpleSinglePointerSelector.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 && UNITY_2017_2_OR_NEWER
7 using UnityEngine.XR.WSA.Input;
8 #endif
9 
10 namespace HoloToolkit.Unity.InputModule
11 {
19  {
20  #region Settings
21 
22  [Tooltip("The stabilizer, if any, used to smooth out controller ray data.")]
24 
25  [Tooltip("The cursor, if any, which should follow the selected pointer.")]
26  public Cursor Cursor;
27 
28  [Tooltip("If true, search for a cursor if one isn't explicitly set.")]
29  [SerializeField]
30  private bool searchForCursorIfUnset = true;
31  public bool SearchForCursorIfUnset { get { return searchForCursorIfUnset; } set { searchForCursorIfUnset = value; } }
32 
33  [Tooltip("If true, always select the best pointer available (OS behavior does not auto-select).")]
34  [SerializeField]
35  private bool autoselectBestAvailable = false;
36  public bool AutoselectBestAvailable { get { return autoselectBestAvailable; } set { autoselectBestAvailable = value; } }
37 
38  [Tooltip("The line pointer prefab to use, if any.")]
39  [SerializeField]
40  private GameObject linePointerPrefab = null;
41 
42  private PointerLine instantiatedPointerLine;
43 
44  #endregion
45 
46  #region Data
47 
48  private bool started;
49  private bool pointerWasChanged;
50 
51  private bool addedInputManagerListener;
52  private IPointingSource currentPointer;
53 
54  private readonly InputSourcePointer inputSourcePointer = new InputSourcePointer();
55 
56  #endregion
57 
58  #region MonoBehaviour Implementation
59 
60  private void Start()
61  {
62  started = true;
63 
67 
68  AddInputManagerListenerIfNeeded();
69  FindCursorIfNeeded();
70  ConnectBestAvailablePointer();
71  }
72 
73  private void OnEnable()
74  {
75  if (started)
76  {
77  AddInputManagerListenerIfNeeded();
78  }
79  }
80 
81  private void OnDisable()
82  {
83  RemoveInputManagerListenerIfNeeded();
84  }
85 
86  #endregion
87 
88  #region Input Event Handlers
89 
91  {
92  // If a pointing controller just became available, set it as primary.
93  if (autoselectBestAvailable && SupportsPointingRay(eventData))
94  {
95  ConnectBestAvailablePointer();
96  }
97  }
98 
100  {
101  if (IsInputSourcePointerActive && inputSourcePointer.InputIsFromSource(eventData))
102  {
103  ConnectBestAvailablePointer();
104  }
105  }
106 
107  void IInputHandler.OnInputUp(InputEventData eventData)
108  {
109  // Let the input fall to the next interactable object.
110  }
111 
113  {
114  HandleInputAction(eventData);
115  }
116 
117  #endregion
118 
119  #region Utilities
120 
121  private void AddInputManagerListenerIfNeeded()
122  {
123  if (!addedInputManagerListener)
124  {
125  InputManager.Instance.AddGlobalListener(gameObject);
126  addedInputManagerListener = true;
127  }
128  }
129 
130  private void RemoveInputManagerListenerIfNeeded()
131  {
132  if (addedInputManagerListener)
133  {
134  InputManager.Instance.RemoveGlobalListener(gameObject);
135  addedInputManagerListener = false;
136  }
137  }
138 
139  private void FindCursorIfNeeded()
140  {
141  if ((Cursor == null) && searchForCursorIfUnset)
142  {
143  Debug.LogWarningFormat(
144  this,
145  "Cursor hasn't been explicitly set on \"{0}.{1}\". We'll search for a cursor in the hierarchy, but"
146  + " that comes with a performance cost, so it would be best if you explicitly set the cursor.",
147  name,
148  GetType().Name
149  );
150 
151  Cursor[] foundCursors = FindObjectsOfType<Cursor>();
152 
153  if ((foundCursors == null) || (foundCursors.Length == 0))
154  {
155  Debug.LogErrorFormat(this, "Couldn't find cursor for \"{0}.{1}\".", name, GetType().Name);
156  }
157  else if (foundCursors.Length > 1)
158  {
159  Debug.LogErrorFormat(
160  this,
161  "Found more than one ({0}) cursors for \"{1}.{2}\", so couldn't automatically set one.",
162  foundCursors.Length,
163  name,
164  GetType().Name
165  );
166  }
167  else
168  {
169  Cursor = foundCursors[0];
170  }
171  }
172  }
173 
174  private void SetPointer(IPointingSource newPointer)
175  {
176  if (currentPointer != newPointer)
177  {
178  if (currentPointer != null)
179  {
180  FocusManager.Instance.UnregisterPointer(currentPointer);
181  }
182 
183  currentPointer = newPointer;
184 
185  if (newPointer != null)
186  {
187  FocusManager.Instance.RegisterPointer(newPointer);
188  }
189 
190  if (Cursor != null)
191  {
192  Cursor.Pointer = newPointer;
193  }
194  }
195 
196  Debug.Assert(currentPointer != null, "No Pointer Set!");
197 
198  if (IsGazePointerActive)
199  {
200  DetachInputSourcePointer();
201  }
202  }
203 
204  private void ConnectBestAvailablePointer()
205  {
206  IPointingSource bestPointer = null;
207  var inputSources = InputManager.Instance.DetectedInputSources;
208 
209  for (var i = 0; i < inputSources.Count; i++)
210  {
211  if (SupportsPointingRay(inputSources[i]))
212  {
213  AttachInputSourcePointer(inputSources[i]);
214  bestPointer = inputSourcePointer;
215  break;
216  }
217  }
218 
219  if (bestPointer == null)
220  {
221  bestPointer = GazeManager.Instance;
222  }
223 
224  SetPointer(bestPointer);
225  }
226 
227  private void HandleInputAction(InputEventData eventData)
228  {
229  // TODO: robertes: Investigate how this feels. Since "Down" will often be followed by "Click", is
230  // marking the event as used actually effective in preventing unintended app input during a
231  // pointer change?
232 
233  if (SupportsPointingRay(eventData))
234  {
235  if (IsInputSourcePointerActive && inputSourcePointer.InputIsFromSource(eventData))
236  {
237  pointerWasChanged = false;
238  }
239  else
240  {
241  AttachInputSourcePointer(eventData);
242  SetPointer(inputSourcePointer);
243  pointerWasChanged = true;
244  }
245  }
246  else
247  {
248  if (IsGazePointerActive)
249  {
250  pointerWasChanged = false;
251  }
252  else
253  {
254  // TODO: robertes: see if we can treat voice separately from the other simple committers,
255  // so voice doesn't steal from a pointing controller. I think input Kind would need
256  // to come through with the event data.
257  SetPointer(GazeManager.Instance);
258  pointerWasChanged = true;
259  }
260  }
261 
262  if (pointerWasChanged)
263  {
264  // Since this input resulted in a pointer change, we mark the event as used to
265  // prevent it from falling through to other handlers to prevent potentially
266  // unintended input from reaching handlers that aren't being pointed at by
267  // the new pointer.
268  eventData.Use();
269  }
270  }
271 
272  private bool SupportsPointingRay(BaseInputEventData eventData)
273  {
274  return SupportsPointingRay(eventData.InputSource, eventData.SourceId);
275  }
276 
277  private bool SupportsPointingRay(InputSourceInfo source)
278  {
279  return SupportsPointingRay(source.InputSource, source.SourceId);
280  }
281 
282  private bool SupportsPointingRay(IInputSource inputSource, uint sourceId)
283  {
284  return inputSource.SupportsInputInfo(sourceId, SupportedInputInfo.Pointing);
285  }
286 
287  private void AttachInputSourcePointer(BaseInputEventData eventData)
288  {
289  AttachInputSourcePointer(eventData.InputSource, eventData.SourceId);
290  }
291 
292  private void AttachInputSourcePointer(InputSourceInfo source)
293  {
294  AttachInputSourcePointer(source.InputSource, source.SourceId);
295  }
296 
297  private void AttachInputSourcePointer(IInputSource inputSource, uint sourceId)
298  {
299  inputSourcePointer.InputSource = inputSource;
300  inputSourcePointer.InputSourceId = sourceId;
301  inputSourcePointer.RayStabilizer = ControllerPointerStabilizer;
302  inputSourcePointer.OwnAllInput = false;
303  inputSourcePointer.ExtentOverride = null;
304  inputSourcePointer.PrioritizedLayerMasksOverride = null;
305 
306  InteractionInputSource interactionInputSource = inputSource as InteractionInputSource;
307 
308  // If the InputSource is not an InteractionInputSource, we don't display any ray visualizations.
309  if (interactionInputSource == null)
310  {
311  return;
312  }
313 
314  // If no pointing ray prefab has been provided, we return early as there's nothing to display.
315  if (linePointerPrefab == null)
316  {
317  return;
318  }
319 
320  // If the pointer line hasn't already been instantiated, create it and store it here.
321  if (instantiatedPointerLine == null)
322  {
323  instantiatedPointerLine = Instantiate(linePointerPrefab).GetComponent<PointerLine>();
324  }
325 
326  inputSourcePointer.PointerRay = instantiatedPointerLine;
327 
328  Handedness handedness;
329  if (interactionInputSource.TryGetHandedness(sourceId, out handedness))
330  {
331 #if UNITY_WSA && UNITY_2017_2_OR_NEWER
332  // This updates the handedness of the pointer line, allowing for re-use if it was already in the scene.
333  instantiatedPointerLine.ChangeHandedness((InteractionSourceHandedness)handedness);
334 #endif
335  }
336  }
337 
338  private void DetachInputSourcePointer()
339  {
340  if (instantiatedPointerLine != null)
341  {
342  Destroy(instantiatedPointerLine.gameObject);
343  }
344  }
345 
346  private bool IsInputSourcePointerActive
347  {
348  get { return (currentPointer == inputSourcePointer); }
349  }
350 
351  private bool IsGazePointerActive
352  {
353  get { return ReferenceEquals(currentPointer, GazeManager.Instance); }
354  }
355 
356  #endregion
357  }
358 }
The gaze manager manages everything related to a gaze ray that can interact with other objects...
Definition: GazeManager.cs:13
Input source for gestures and interaction source information from the WSA APIs, which gives access to...
Input Manager is responsible for managing input sources and dispatching relevant events to the approp...
Definition: InputManager.cs:19
void OnSourceLost(SourceStateEventData eventData)
InputSourceInfo gives you the input source like hands or motion controller. It will also report the s...
uint SourceId
The id of the source the event is from, for instance the hand id.
SupportedInputInfo
Flags used to indicate which input information is supported by an input source.
A base class for a stabilizer that takes an input position and rotation, and performs operations on t...
void OnSourceDetected(SourceStateEventData eventData)
IInputSource InputSource
The source the input event originates from.
static void AssertIsInitialized()
Definition: Singleton.cs:49
void OnInputUp(InputEventData eventData)
bool SupportsInputInfo(uint sourceId, SupportedInputInfo inputInfo)
Returns whether the input source supports the specified input info type.
static T Instance
Returns the Singleton instance of the classes type. If no instance is found, then we search for an in...
Definition: Singleton.cs:26
Interface for an input source. An input source can be anything that a user can use to interact with a...
Definition: IInputSource.cs:12
Interface to implement to react to source state changes, such as when an input source is detected or ...
bool TryGetHandedness(uint sourceId, out Handedness handedness)
Focus manager is the bridge that handles different types of pointing sources like gaze cursor or poin...
Definition: FocusManager.cs:16
Interface to implement to react to simple pointer-like input.
Implement this interface to register your pointer as a pointing source. This could be gaze based or m...
IPointingSource Pointer
The pointer that this cursor should follow and process input from.
Definition: Cursor.cs:24
Describes an source state event that has a source id.
Class implementing IPointingSource to demonstrate how to create a pointing source. This is consumed by SimpleSinglePointerSelector.
Describes an input event that has a source id and a press kind.
void OnInputDown(InputEventData eventData)
Script shows how to create your own &#39;point and commit&#39; style pointer which can steal cursor focus usi...