AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
Cursor.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 namespace HoloToolkit.Unity.InputModule
7 {
11  public abstract class Cursor : MonoBehaviour, ICursor
12  {
13  public CursorStateEnum CursorState { get { return cursorState; } }
14  private CursorStateEnum cursorState = CursorStateEnum.None;
15 
16  [SerializeField]
17  [Tooltip("Set this in the editor to an object with a component that implements IPointerSource to tell this cursor which pointer to follow. To set the pointer programmatically, set Pointer directly.")]
18  protected GameObject LoadPointer;
19 
23  public IPointingSource Pointer
24  {
25  get { return pointer; }
26  set
27  {
28  // This value is used to determine the cursor's default distance.
29  // It is cached here to prevent repeated casting in the update loop.
30  pointerIsInputSourcePointer = value is InputSourcePointer;
31  pointer = value;
32  }
33  }
34  private IPointingSource pointer;
35 
40  private bool pointerIsInputSourcePointer = false;
41 
45  [Header("Cursor Distance")]
46  [Tooltip("The minimum distance the cursor can be with nothing hit")]
47  public float MinCursorDistance = 1.0f;
48 
52  [Tooltip("The maximum distance the cursor can be with nothing hit")]
53  public float DefaultCursorDistance = 2.0f;
54 
58  [Tooltip("The distance from the hit surface to place the cursor")]
59  public float SurfaceCursorDistance = 0.02f;
60 
61  [Header("Motion")]
62  [Tooltip("When lerping, use unscaled time. This is useful for games that have a pause mechanism or otherwise adjust the game timescale.")]
63  public bool UseUnscaledTime = true;
64 
68  public float PositionLerpTime = 0.01f;
69 
73  public float ScaleLerpTime = 0.01f;
74 
78  public float RotationLerpTime = 0.01f;
79 
83  [Range(0, 1)]
84  public float LookRotationBlend = 0.5f;
85 
89  [Header("Transform References")]
90  public Transform PrimaryCursorVisual;
91 
92  public Vector3 Position
93  {
94  get { return transform.position; }
95  }
96 
97  public Quaternion Rotation
98  {
99  get { return transform.rotation; }
100  }
101 
102  public Vector3 LocalScale
103  {
104  get { return transform.localScale; }
105  }
106 
110  protected bool IsHandVisible;
111 
115  protected bool IsInputSourceDown;
116 
117  protected GameObject TargetedObject;
119 
120  private uint visibleHandsCount = 0;
121 
122  [SerializeField]
123  [Tooltip("Set this to specify if the Cursor should start visible or invisible in the scene.")]
124  private bool isVisible = true;
125 
129  private Vector3 targetPosition;
130  private Vector3 targetScale;
131  private Quaternion targetRotation;
132 
137  private float originalDefaultCursorDistance;
138 
142  public bool IsVisible
143  {
144  set
145  {
146  isVisible = value;
147  SetVisibility(isVisible);
148  }
149  }
150 
151  #region MonoBehaviour Functions
152 
153  protected virtual void Awake()
154  {
155  originalDefaultCursorDistance = DefaultCursorDistance;
156 
157  // Use the setter to update visibility of the cursor at startup based on user preferences
158  IsVisible = isVisible;
159  }
160 
161  protected virtual void Start()
162  {
163  RegisterManagers();
164  TryLoadPointerIfNeeded();
165  }
166 
167  protected virtual void Update()
168  {
169  UpdateCursorState();
170  UpdateCursorTransform();
171  }
172 
176  protected virtual void OnEnable()
177  {
178  if (FocusManager.IsInitialized && Pointer != null)
179  {
180  OnPointerSpecificFocusChanged(Pointer, null, FocusManager.Instance.GetFocusedObject(Pointer));
181  }
182  OnCursorStateChange(CursorStateEnum.None);
183  }
184 
188  protected virtual void OnDisable()
189  {
190  TargetedObject = null;
191  TargetedCursorModifier = null;
192  visibleHandsCount = 0;
193  IsHandVisible = false;
194  OnCursorStateChange(CursorStateEnum.Contextual);
195  }
196 
197  protected virtual void OnDestroy()
198  {
199  UnregisterManagers();
200  }
201 
202  #endregion
203 
207  protected virtual void RegisterManagers()
208  {
209  // This accounts for any input sources that were detected before we register as a global listener below.
210  visibleHandsCount = (uint)InputManager.Instance.DetectedInputSources.Count;
211  IsHandVisible = visibleHandsCount > 0;
212 
213  // Register the cursor as a global listener, so that it can always get input events it cares about
214  InputManager.Instance.AddGlobalListener(gameObject);
215 
216  // Setup the cursor to be able to respond to input being globally enabled / disabled
217  if (InputManager.Instance.IsInputEnabled)
218  {
219  OnInputEnabled();
220  }
221  else
222  {
223  OnInputDisabled();
224  }
225 
226  InputManager.Instance.InputEnabled += OnInputEnabled;
227  InputManager.Instance.InputDisabled += OnInputDisabled;
228 
229  FocusManager.Instance.PointerSpecificFocusChanged += OnPointerSpecificFocusChanged;
230  }
231 
235  protected virtual void UnregisterManagers()
236  {
238  {
239  InputManager.Instance.InputEnabled -= OnInputEnabled;
240  InputManager.Instance.InputDisabled -= OnInputDisabled;
241  InputManager.Instance.RemoveGlobalListener(gameObject);
242  }
243 
245  {
246  FocusManager.Instance.PointerSpecificFocusChanged -= OnPointerSpecificFocusChanged;
247  }
248  }
249 
250  private void TryLoadPointerIfNeeded()
251  {
252  if (Pointer != null)
253  {
254  // Nothing to do. Keep the pointer that must have been set programmatically.
255  }
256  else if (LoadPointer != null)
257  {
258  Pointer = LoadPointer.GetComponent<IPointingSource>();
259 
260  if (Pointer == null)
261  {
262  Debug.LogErrorFormat("Load pointer object \"{0}\" is missing its {1} component.",
263  LoadPointer.name,
264  typeof(IPointingSource).Name
265  );
266  }
267  }
268  else if (FocusManager.IsInitialized)
269  {
270  // For backward-compatibility, if a pointer wasn't specified, but there's exactly one
271  // pointer currently registered with FocusManager, we use it.
272  IPointingSource pointingSource;
273  if (FocusManager.Instance.TryGetSinglePointer(out pointingSource))
274  {
275  Pointer = pointingSource;
276  }
277  }
278  else
279  {
280  // No options available, so we leave Pointer unset. It will need to be set programmatically later.
281  }
282  }
283 
291  protected virtual void OnPointerSpecificFocusChanged(IPointingSource pointer, GameObject oldFocusedObject, GameObject newFocusedObject)
292  {
293  if (pointer == Pointer)
294  {
295  TargetedObject = newFocusedObject;
296 
297  CursorModifier newModifier = (newFocusedObject == null)
298  ? null
299  : newFocusedObject.GetComponent<CursorModifier>();
300 
301  OnActiveModifier(newModifier);
302  }
303  }
304 
309  protected virtual void OnActiveModifier(CursorModifier modifier)
310  {
311  TargetedCursorModifier = modifier;
312  }
313 
317  protected virtual void UpdateCursorTransform()
318  {
319  FocusDetails focusDetails = FocusManager.Instance.GetFocusDetails(Pointer);
320  GameObject newTargetedObject = focusDetails.Object;
321  Vector3 lookForward = Vector3.forward;
322 
323  // Normalize scale on before update
324  targetScale = Vector3.one;
325 
326  // If no game object is hit, put the cursor at the default distance
327  if (newTargetedObject == null)
328  {
329  TargetedObject = null;
330  TargetedCursorModifier = null;
331 
332  if (pointerIsInputSourcePointer)
333  {
334  // This value get re-queried every update, in case the app has
335  // changed the pointing extent of the pointer for the current scenario.
336  float distance = FocusManager.Instance.GetPointingExtent(Pointer);
337  if (DefaultCursorDistance != distance)
338  {
339  DefaultCursorDistance = distance;
340  }
341  }
342  else if (DefaultCursorDistance != originalDefaultCursorDistance)
343  {
344  DefaultCursorDistance = originalDefaultCursorDistance;
345  }
346 
347  targetPosition = RayStep.GetPointByDistance(Pointer.Rays, DefaultCursorDistance);
348  lookForward = -RayStep.GetDirectionByDistance(Pointer.Rays, DefaultCursorDistance);
349  targetRotation = lookForward.magnitude > 0 ? Quaternion.LookRotation(lookForward, Vector3.up) : transform.rotation;
350  }
351  else
352  {
353  // Update currently targeted object
354  TargetedObject = newTargetedObject;
355 
356  if (TargetedCursorModifier != null)
357  {
358  TargetedCursorModifier.GetModifiedTransform(this, out targetPosition, out targetRotation, out targetScale);
359  }
360  else
361  {
362  // If no modifier is on the target, just use the hit result to set cursor position
363  // Get the look forward by using distance between pointer origin and target position
364  // (This may not be strictly accurate for extremely wobbly pointers, but it should produce usable results)
365  float distanceToTarget = Vector3.Distance(Pointer.Rays[0].Origin, focusDetails.Point);
366  lookForward = -RayStep.GetDirectionByDistance(Pointer.Rays, distanceToTarget);
367  targetPosition = focusDetails.Point + (lookForward * SurfaceCursorDistance);
368  Vector3 lookRotation = Vector3.Slerp(focusDetails.Normal, lookForward, LookRotationBlend);
369  targetRotation = Quaternion.LookRotation(lookRotation == Vector3.zero ? lookForward : lookRotation, Vector3.up);
370  }
371  }
372 
373  float deltaTime = UseUnscaledTime
374  ? Time.unscaledDeltaTime
375  : Time.deltaTime;
376 
377  // Use the lerp times to blend the position to the target position
378  transform.position = Vector3.Lerp(transform.position, targetPosition, deltaTime / PositionLerpTime);
379  transform.localScale = Vector3.Lerp(transform.localScale, targetScale, deltaTime / ScaleLerpTime);
380  transform.rotation = Quaternion.Lerp(transform.rotation, targetRotation, deltaTime / RotationLerpTime);
381  }
382 
386  public virtual void SetVisibility(bool visible)
387  {
388  if (PrimaryCursorVisual != null)
389  {
390  PrimaryCursorVisual.gameObject.SetActive(visible);
391  }
392  }
393 
397  public virtual void OnInputDisabled()
398  {
399  // Reset visible hands on disable
400  visibleHandsCount = 0;
401  IsHandVisible = false;
402 
403  OnCursorStateChange(CursorStateEnum.Contextual);
404  }
405 
409  public virtual void OnInputEnabled()
410  {
411  OnCursorStateChange(CursorStateEnum.None);
412  }
413 
418  public virtual void OnInputUp(InputEventData eventData)
419  {
420  if (Pointer != null && Pointer.OwnsInput(eventData))
421  {
422  IsInputSourceDown = false;
423  }
424  }
425 
430  public virtual void OnInputDown(InputEventData eventData)
431  {
432  if (Pointer != null && Pointer.OwnsInput(eventData))
433  {
434  IsInputSourceDown = true;
435  }
436  }
437 
442  public virtual void OnInputClicked(InputClickedEventData eventData)
443  {
444  // Open input socket for other cool stuff...
445  }
446 
447 
452  public virtual void OnSourceDetected(SourceStateEventData eventData)
453  {
454  if (Pointer != null && Pointer.OwnsInput(eventData))
455  {
456  visibleHandsCount++;
457  IsHandVisible = true;
458  }
459  }
460 
461 
466  public virtual void OnSourceLost(SourceStateEventData eventData)
467  {
468  if (Pointer != null && Pointer.OwnsInput(eventData))
469  {
470  visibleHandsCount--;
471  if (visibleHandsCount == 0)
472  {
473  IsHandVisible = false;
474  IsInputSourceDown = false;
475  }
476  }
477  }
478 
482  private void UpdateCursorState()
483  {
484  CursorStateEnum newState = CheckCursorState();
485  if (cursorState != newState)
486  {
487  OnCursorStateChange(newState);
488  }
489  }
490 
495  {
496  if (cursorState != CursorStateEnum.Contextual)
497  {
498  if (IsInputSourceDown)
499  {
500  return CursorStateEnum.Select;
501  }
502  else if (cursorState == CursorStateEnum.Select)
503  {
504  return CursorStateEnum.Release;
505  }
506 
507  if (IsHandVisible)
508  {
509  return TargetedObject != null ? CursorStateEnum.InteractHover : CursorStateEnum.Interact;
510  }
511  return TargetedObject != null ? CursorStateEnum.ObserveHover : CursorStateEnum.Observe;
512  }
513  return CursorStateEnum.Contextual;
514  }
515 
520  public virtual void OnCursorStateChange(CursorStateEnum state)
521  {
522  cursorState = state;
523  }
524  }
525 }
Component that can be added to any game object with a collider to modify how a cursor reacts when on ...
virtual void OnInputEnabled()
Enable input and set to none to reset cursor
Definition: Cursor.cs:409
virtual void RegisterManagers()
Register to events from the managers the cursor needs.
Definition: Cursor.cs:207
virtual void SetVisibility(bool visible)
Updates the visual representation of the cursor.
Definition: Cursor.cs:386
Input Manager is responsible for managing input sources and dispatching relevant events to the approp...
Definition: InputManager.cs:19
virtual void UpdateCursorTransform()
Update the cursor&#39;s transform
Definition: Cursor.cs:317
virtual void OnActiveModifier(CursorModifier modifier)
Override function when a new modifier is found or no modifier is valid
Definition: Cursor.cs:309
FocusDetails struct contains information about which game object has the focus currently. Also contains information about the normal of that point.
Definition: FocusDetails.cs:14
Cursor Interface for handling input events and setting visibility.
Definition: ICursor.cs:11
virtual void OnInputDisabled()
Disable input and set to contextual to override input
Definition: Cursor.cs:397
virtual void OnDisable()
Override for disable functions
Definition: Cursor.cs:188
virtual void OnCursorStateChange(CursorStateEnum state)
Change the cursor state to the new state. Override in cursor implementations.
Definition: Cursor.cs:520
ICursorModifier TargetedCursorModifier
Definition: Cursor.cs:118
virtual void UnregisterManagers()
Unregister from events from the managers the cursor needs.
Definition: Cursor.cs:235
void GetModifiedTransform(ICursor cursor, out Vector3 position, out Quaternion rotation, out Vector3 scale)
Returns the modified transform for the cursor after considering this modifier.
virtual void OnInputClicked(InputClickedEventData eventData)
Function for receiving OnInputClicked events from InputManager
Definition: Cursor.cs:442
virtual void OnSourceDetected(SourceStateEventData eventData)
Input source detected callback for the cursor
Definition: Cursor.cs:452
virtual CursorStateEnum CheckCursorState()
Virtual function for checking state changes.
Definition: Cursor.cs:494
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
virtual void OnSourceLost(SourceStateEventData eventData)
Input source lost callback for the cursor
Definition: Cursor.cs:466
static Vector3 GetPointByDistance(RayStep[] steps, float distance)
Returns a point along an array of RaySteps by distance
Definition: RayStep.cs:59
bool IsInputSourceDown
Indicates air tap down
Definition: Cursor.cs:115
static Vector3 GetDirectionByDistance(RayStep[] steps, float distance)
Returns a direction along an array of RaySteps by distance
Definition: RayStep.cs:133
virtual void OnPointerSpecificFocusChanged(IPointingSource pointer, GameObject oldFocusedObject, GameObject newFocusedObject)
Updates the currently targeted object and cursor modifier upon getting an event indicating that the f...
Definition: Cursor.cs:291
Focus manager is the bridge that handles different types of pointing sources like gaze cursor or poin...
Definition: FocusManager.cs:16
virtual void OnInputDown(InputEventData eventData)
Function for receiving OnInputDown events from InputManager
Definition: Cursor.cs:430
Describes an input event that involves a tap.
Implement this interface to register your pointer as a pointing source. This could be gaze based or m...
Describes an source state event that has a source id.
Transform PrimaryCursorVisual
Visual that is displayed when cursor is active normally
Definition: Cursor.cs:90
CursorStateEnum
Enum for current cursor state
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.
bool IsHandVisible
Indicates if hand is current in the view
Definition: Cursor.cs:110
virtual void OnEnable()
Override for enable functions
Definition: Cursor.cs:176
Cursor Modifier Interface that provides basic overrides for cursor behavior.
static bool IsInitialized
Returns whether the instance has been initialized or not.
Definition: Singleton.cs:58
virtual void OnInputUp(InputEventData eventData)
Function for consuming the OnInputUp events
Definition: Cursor.cs:418