AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
HandDraggable.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 using System;
6 
7 namespace HoloToolkit.Unity.InputModule
8 {
15  {
19  public event Action StartedDragging;
20 
24  public event Action StoppedDragging;
25 
26  [Tooltip("Transform that will be dragged. Defaults to the object of the component.")]
27  public Transform HostTransform;
28 
29  [Tooltip("Scale by which hand movement in z is multiplied to move the dragged object.")]
30  public float DistanceScale = 2f;
31 
32  public enum RotationModeEnum
33  {
34  Default,
35  LockObjectRotation,
36  OrientTowardUser,
37  OrientTowardUserAndKeepUpright
38  }
39 
40  public RotationModeEnum RotationMode = RotationModeEnum.Default;
41 
42  [Tooltip("Controls the speed at which the object will interpolate toward the desired position")]
43  [Range(0.01f, 1.0f)]
44  public float PositionLerpSpeed = 0.2f;
45 
46  [Tooltip("Controls the speed at which the object will interpolate toward the desired rotation")]
47  [Range(0.01f, 1.0f)]
48  public float RotationLerpSpeed = 0.2f;
49 
50  public bool IsDraggingEnabled = true;
51 
52  private bool isDragging;
53  private bool isGazed;
54  private Vector3 objRefForward;
55  private Vector3 objRefUp;
56  private float objRefDistance;
57  private Quaternion gazeAngularOffset;
58  private float handRefDistance;
59  private Vector3 objRefGrabPoint;
60 
61  private Vector3 draggingPosition;
62  private Quaternion draggingRotation;
63 
64  private IInputSource currentInputSource;
65  private uint currentInputSourceId;
66  private Rigidbody hostRigidbody;
67  private bool hostRigidbodyWasKinematic;
68 
69  private void Start()
70  {
71  if (HostTransform == null)
72  {
73  HostTransform = transform;
74  }
75 
76  hostRigidbody = HostTransform.GetComponent<Rigidbody>();
77  }
78 
79  private void OnDestroy()
80  {
81  if (isDragging)
82  {
83  StopDragging();
84  }
85 
86  if (isGazed)
87  {
88  OnFocusExit();
89  }
90  }
91 
92  private void Update()
93  {
94  if (IsDraggingEnabled && isDragging)
95  {
96  UpdateDragging();
97  }
98  }
99 
103  public void StartDragging(Vector3 initialDraggingPosition)
104  {
105  if (!IsDraggingEnabled)
106  {
107  return;
108  }
109 
110  if (isDragging)
111  {
112  return;
113  }
114 
115  // TODO: robertes: Fix push/pop and single-handler model so that multiple HandDraggable components
116  // can be active at once.
117 
118  // Add self as a modal input handler, to get all inputs during the manipulation
119  InputManager.Instance.PushModalInputHandler(gameObject);
120 
121  isDragging = true;
122  if (hostRigidbody != null)
123  {
124  hostRigidbodyWasKinematic = hostRigidbody.isKinematic;
125  hostRigidbody.isKinematic = true;
126  }
127 
128  Transform cameraTransform = CameraCache.Main.transform;
129 
130  Vector3 inputPosition = Vector3.zero;
131 #if UNITY_2017_2_OR_NEWER
132  InteractionSourceInfo sourceKind;
133  currentInputSource.TryGetSourceKind(currentInputSourceId, out sourceKind);
134  switch (sourceKind)
135  {
136  case InteractionSourceInfo.Hand:
137  currentInputSource.TryGetGripPosition(currentInputSourceId, out inputPosition);
138  break;
139  case InteractionSourceInfo.Controller:
140  currentInputSource.TryGetPointerPosition(currentInputSourceId, out inputPosition);
141  break;
142  }
143 #else
144  currentInputSource.TryGetPointerPosition(currentInputSourceId, out inputPosition);
145 #endif
146 
147  Vector3 pivotPosition = GetHandPivotPosition(cameraTransform);
148  handRefDistance = Vector3.Magnitude(inputPosition - pivotPosition);
149  objRefDistance = Vector3.Magnitude(initialDraggingPosition - pivotPosition);
150 
151  Vector3 objForward = HostTransform.forward;
152  Vector3 objUp = HostTransform.up;
153  // Store where the object was grabbed from
154  objRefGrabPoint = cameraTransform.transform.InverseTransformDirection(HostTransform.position - initialDraggingPosition);
155 
156  Vector3 objDirection = Vector3.Normalize(initialDraggingPosition - pivotPosition);
157  Vector3 handDirection = Vector3.Normalize(inputPosition - pivotPosition);
158 
159  objForward = cameraTransform.InverseTransformDirection(objForward); // in camera space
160  objUp = cameraTransform.InverseTransformDirection(objUp); // in camera space
161  objDirection = cameraTransform.InverseTransformDirection(objDirection); // in camera space
162  handDirection = cameraTransform.InverseTransformDirection(handDirection); // in camera space
163 
164  objRefForward = objForward;
165  objRefUp = objUp;
166 
167  // Store the initial offset between the hand and the object, so that we can consider it when dragging
168  gazeAngularOffset = Quaternion.FromToRotation(handDirection, objDirection);
169  draggingPosition = initialDraggingPosition;
170 
171  StartedDragging.RaiseEvent();
172  }
173 
178  private Vector3 GetHandPivotPosition(Transform cameraTransform)
179  {
180  return cameraTransform.position + new Vector3(0, -0.2f, 0) - cameraTransform.forward * 0.2f; // a bit lower and behind
181  }
182 
187  public void SetDragging(bool isEnabled)
188  {
189  if (IsDraggingEnabled == isEnabled)
190  {
191  return;
192  }
193 
194  IsDraggingEnabled = isEnabled;
195 
196  if (isDragging)
197  {
198  StopDragging();
199  }
200  }
201 
205  private void UpdateDragging()
206  {
207  Transform cameraTransform = CameraCache.Main.transform;
208 
209  Vector3 inputPosition = Vector3.zero;
210 #if UNITY_2017_2_OR_NEWER
211  InteractionSourceInfo sourceKind;
212  currentInputSource.TryGetSourceKind(currentInputSourceId, out sourceKind);
213  switch (sourceKind)
214  {
215  case InteractionSourceInfo.Hand:
216  currentInputSource.TryGetGripPosition(currentInputSourceId, out inputPosition);
217  break;
218  case InteractionSourceInfo.Controller:
219  currentInputSource.TryGetPointerPosition(currentInputSourceId, out inputPosition);
220  break;
221  }
222 #else
223  currentInputSource.TryGetPointerPosition(currentInputSourceId, out inputPosition);
224 #endif
225 
226  Vector3 pivotPosition = GetHandPivotPosition(cameraTransform);
227 
228  Vector3 newHandDirection = Vector3.Normalize(inputPosition - pivotPosition);
229 
230  newHandDirection = cameraTransform.InverseTransformDirection(newHandDirection); // in camera space
231  Vector3 targetDirection = Vector3.Normalize(gazeAngularOffset * newHandDirection);
232  targetDirection = cameraTransform.TransformDirection(targetDirection); // back to world space
233 
234  float currentHandDistance = Vector3.Magnitude(inputPosition - pivotPosition);
235 
236  float distanceRatio = currentHandDistance / handRefDistance;
237  float distanceOffset = distanceRatio > 0 ? (distanceRatio - 1f) * DistanceScale : 0;
238  float targetDistance = objRefDistance + distanceOffset;
239 
240  draggingPosition = pivotPosition + (targetDirection * targetDistance);
241 
242  if (RotationMode == RotationModeEnum.OrientTowardUser || RotationMode == RotationModeEnum.OrientTowardUserAndKeepUpright)
243  {
244  draggingRotation = Quaternion.LookRotation(HostTransform.position - pivotPosition);
245  }
246  else if (RotationMode == RotationModeEnum.LockObjectRotation)
247  {
248  draggingRotation = HostTransform.rotation;
249  }
250  else // RotationModeEnum.Default
251  {
252  Vector3 objForward = cameraTransform.TransformDirection(objRefForward); // in world space
253  Vector3 objUp = cameraTransform.TransformDirection(objRefUp); // in world space
254  draggingRotation = Quaternion.LookRotation(objForward, objUp);
255  }
256 
257  Vector3 newPosition = Vector3.Lerp(HostTransform.position, draggingPosition + cameraTransform.TransformDirection(objRefGrabPoint), PositionLerpSpeed);
258  // Apply Final Position
259  if (hostRigidbody == null)
260  {
261  HostTransform.position = newPosition;
262  }
263  else
264  {
265  hostRigidbody.MovePosition(newPosition);
266  }
267 
268  // Apply Final Rotation
269  Quaternion newRotation = Quaternion.Lerp(HostTransform.rotation, draggingRotation, RotationLerpSpeed);
270  if (hostRigidbody == null)
271  {
272  HostTransform.rotation = newRotation;
273  }
274  else
275  {
276  hostRigidbody.MoveRotation(newRotation);
277  }
278 
279  if (RotationMode == RotationModeEnum.OrientTowardUserAndKeepUpright)
280  {
281  Quaternion upRotation = Quaternion.FromToRotation(HostTransform.up, Vector3.up);
282  HostTransform.rotation = upRotation * HostTransform.rotation;
283  }
284  }
285 
289  public void StopDragging()
290  {
291  if (!isDragging)
292  {
293  return;
294  }
295 
296  // Remove self as a modal input handler
297  InputManager.Instance.PopModalInputHandler();
298 
299  isDragging = false;
300  currentInputSource = null;
301  currentInputSourceId = 0;
302  if (hostRigidbody != null)
303  {
304  hostRigidbody.isKinematic = hostRigidbodyWasKinematic;
305  }
306  StoppedDragging.RaiseEvent();
307  }
308 
309  public void OnFocusEnter()
310  {
311  if (!IsDraggingEnabled)
312  {
313  return;
314  }
315 
316  if (isGazed)
317  {
318  return;
319  }
320 
321  isGazed = true;
322  }
323 
324  public void OnFocusExit()
325  {
326  if (!IsDraggingEnabled)
327  {
328  return;
329  }
330 
331  if (!isGazed)
332  {
333  return;
334  }
335 
336  isGazed = false;
337  }
338 
339  public void OnInputUp(InputEventData eventData)
340  {
341  if (currentInputSource != null &&
342  eventData.SourceId == currentInputSourceId)
343  {
344  eventData.Use(); // Mark the event as used, so it doesn't fall through to other handlers.
345 
346  StopDragging();
347  }
348  }
349 
350  public void OnInputDown(InputEventData eventData)
351  {
352  if (isDragging)
353  {
354  // We're already handling drag input, so we can't start a new drag operation.
355  return;
356  }
357 
358 #if UNITY_2017_2_OR_NEWER
359  InteractionSourceInfo sourceKind;
360  eventData.InputSource.TryGetSourceKind(eventData.SourceId, out sourceKind);
361  if (sourceKind != InteractionSourceInfo.Hand)
362  {
363  if (!eventData.InputSource.SupportsInputInfo(eventData.SourceId, SupportedInputInfo.GripPosition))
364  {
365  // The input source must provide grip positional data for this script to be usable
366  return;
367  }
368  }
369 #else
370  if (!eventData.InputSource.SupportsInputInfo(eventData.SourceId, SupportedInputInfo.PointerPosition))
371  {
372  // The input source must provide positional data for this script to be usable
373  return;
374  }
375 #endif // UNITY_2017_2_OR_NEWER
376 
377  eventData.Use(); // Mark the event as used, so it doesn't fall through to other handlers.
378 
379  currentInputSource = eventData.InputSource;
380  currentInputSourceId = eventData.SourceId;
381 
382  FocusDetails? details = FocusManager.Instance.TryGetFocusDetails(eventData);
383 
384  Vector3 initialDraggingPosition = (details == null)
385  ? HostTransform.position
386  : details.Value.Point;
387 
388  StartDragging(initialDraggingPosition);
389  }
390 
391  public void OnSourceDetected(SourceStateEventData eventData)
392  {
393  // Nothing to do
394  }
395 
396  public void OnSourceLost(SourceStateEventData eventData)
397  {
398  if (currentInputSource != null && eventData.SourceId == currentInputSourceId)
399  {
400  StopDragging();
401  }
402  }
403  }
404 }
void SetDragging(bool isEnabled)
Enables or disables dragging.
void OnInputDown(InputEventData eventData)
Input Manager is responsible for managing input sources and dispatching relevant events to the approp...
Definition: InputManager.cs:19
void OnSourceLost(SourceStateEventData eventData)
uint SourceId
The id of the source the event is from, for instance the hand id.
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
SupportedInputInfo
Flags used to indicate which input information is supported by an input source.
void StartDragging(Vector3 initialDraggingPosition)
Starts dragging the object.
void OnInputUp(InputEventData eventData)
Action StartedDragging
Event triggered when dragging starts.
bool TryGetGripPosition(uint sourceId, out Vector3 position)
Returns the position of the input source, if available. Not all input sources support positional info...
IInputSource InputSource
The source the input event originates from.
bool SupportsInputInfo(uint sourceId, SupportedInputInfo inputInfo)
Returns whether the input source supports the specified input info type.
The purpose of this class is to provide a cached reference to the main camera. Calling Camera...
Definition: CameraCache.cs:12
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
Action StoppedDragging
Event triggered when dragging stops.
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
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 ...
Interface to implement to react to focus enter/exit.
Definition: IFocusable.cs:11
void StopDragging()
Stops dragging the object.
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.
Describes an source state event that has a source id.
void OnSourceDetected(SourceStateEventData eventData)
Component that allows dragging an object with your hand on HoloLens. Dragging is done by calculating ...
bool TryGetPointerPosition(uint sourceId, out Vector3 position)
Returns the position of the input source, if available. Not all input sources support positional info...
bool TryGetSourceKind(uint sourceId, out InteractionSourceInfo sourceKind)
Describes an input event that has a source id and a press kind.