AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
TwoHandManipulatable.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 HoloToolkit.Unity.UX;
5 using System.Collections.Generic;
6 using System.Linq;
7 using UnityEngine;
8 using UnityEngine.Assertions;
9 
10 namespace HoloToolkit.Unity.InputModule.Utilities.Interactions
11 {
20  {
21  [SerializeField]
22  [Tooltip("Transform that will be dragged. Defaults to the object of the component.")]
23  private Transform hostTransform = null;
24 
25  public Transform HostTransform
26  {
27  get { return hostTransform; }
28  set { hostTransform = value; }
29  }
30 
31  [SerializeField]
32  [Tooltip("To visualize the object bounding box, drop the HoloToolkit/UX/Prefabs/BoundingBoxes/BoundingBoxBasic.prefab here. This is optional.")]
33  private BoundingBox boundingBoxPrefab = null;
34 
38  public BoundingBox BoundingBoxPrefab
39  {
40  set { boundingBoxPrefab = value; }
41  get { return boundingBoxPrefab; }
42  }
43 
44  [SerializeField]
45  [Tooltip("What manipulation will two hands perform?")]
46  private ManipulationMode manipulationMode = ManipulationMode.Scale;
47 
49  {
50  get { return manipulationMode; }
51  set { manipulationMode = value; }
52  }
53 
54  [SerializeField]
55  [Tooltip("Constrain rotation along an axis")]
56  private AxisConstraint rotationConstraint = AxisConstraint.None;
57 
58  public AxisConstraint RotationConstraint
59  {
60  get { return rotationConstraint; }
61  set
62  {
63  rotationConstraint = value;
64  rotateLogic = new TwoHandRotateLogic(rotationConstraint);
65  }
66  }
67 
68  [SerializeField]
69  [Tooltip("If true, grabbing the object with one hand will initiate movement.")]
70  private bool enableOneHandMovement = true;
71 
72  public bool EnableEnableOneHandedMovement
73  {
74  get { return enableOneHandMovement; }
75  set { enableOneHandMovement = value; }
76  }
77 
78  // Private fields that store transform information.
79  #region Transform Info
80 
81  private BoundingBox boundingBoxInstance;
82  private ManipulationMode currentState;
83  private TwoHandMoveLogic moveLogic;
84  private TwoHandScaleLogic scaleLogic;
85  private TwoHandRotateLogic rotateLogic;
86 
87  #endregion Transform Info
88 
92  private readonly Dictionary<uint, Vector3> handsPressedLocationsMap = new Dictionary<uint, Vector3>();
93 
97  private readonly Dictionary<uint, IInputSource> handsPressedInputSourceMap = new Dictionary<uint, IInputSource>();
98 
102  private bool ShowBoundingBox
103  {
104  set
105  {
106  if (boundingBoxPrefab != null)
107  {
108  if (boundingBoxInstance == null)
109  {
110  // Instantiate Bounding Box from the Prefab
111  boundingBoxInstance = Instantiate(boundingBoxPrefab) as BoundingBox;
112  }
113 
114  if (value)
115  {
116  boundingBoxInstance.Target = HostTransform.gameObject;
117  boundingBoxInstance.gameObject.SetActive(true);
118  }
119  else
120  {
121  boundingBoxInstance.Target = null;
122  boundingBoxInstance.gameObject.SetActive(false);
123  }
124  }
125  }
126  }
127 
131  [System.Obsolete("Use ManipulationMode.")]
133  {
134  manipulationMode = mode;
135  }
136 
137  private void Awake()
138  {
139  moveLogic = new TwoHandMoveLogic();
140  rotateLogic = new TwoHandRotateLogic(rotationConstraint);
141  scaleLogic = new TwoHandScaleLogic();
142  }
143 
144  private void Start()
145  {
146  if (hostTransform == null)
147  {
148  hostTransform = transform;
149  }
150  }
151 
152  private void Update()
153  {
154  // Update positions of all hands
155  foreach (var key in handsPressedInputSourceMap.Keys)
156  {
157  var inputSource = handsPressedInputSourceMap[key];
158  Vector3 inputPosition;
159  if (inputSource.TryGetGripPosition(key, out inputPosition))
160  {
161  handsPressedLocationsMap[key] = inputPosition;
162  }
163  }
164 
165  if (currentState != ManipulationMode.None)
166  {
167  UpdateStateMachine();
168  }
169  }
170 
171  private Vector3 GetInputPosition(InputEventData eventData)
172  {
173  Vector3 result;
174  eventData.InputSource.TryGetGripPosition(eventData.SourceId, out result);
175  return result;
176  }
177 
178  private void RemoveSourceIdFromHandMap(uint sourceId)
179  {
180  if (handsPressedLocationsMap.ContainsKey(sourceId))
181  {
182  handsPressedLocationsMap.Remove(sourceId);
183  }
184 
185  if (handsPressedInputSourceMap.ContainsKey(sourceId))
186  {
187  handsPressedInputSourceMap.Remove(sourceId);
188  }
189  }
190 
194  public void OnInputDown(InputEventData eventData)
195  {
196  // Add to hand map
197  handsPressedLocationsMap[eventData.SourceId] = GetInputPosition(eventData);
198  handsPressedInputSourceMap[eventData.SourceId] = eventData.InputSource;
199  UpdateStateMachine();
200  eventData.Use();
201  }
202 
206  public void OnInputUp(InputEventData eventData)
207  {
208  RemoveSourceIdFromHandMap(eventData.SourceId);
209  UpdateStateMachine();
210  eventData.Use();
211  }
212 
216  public void OnSourceDetected(SourceStateEventData eventData) { }
217 
221  public void OnSourceLost(SourceStateEventData eventData)
222  {
223  RemoveSourceIdFromHandMap(eventData.SourceId);
224  UpdateStateMachine();
225  eventData.Use();
226  }
227 
231  private void UpdateStateMachine()
232  {
233  var handsPressedCount = handsPressedLocationsMap.Count;
234  ManipulationMode newState = currentState;
235 
236  switch (currentState)
237  {
238  case ManipulationMode.None:
239  case ManipulationMode.Move:
240  if (handsPressedCount == 0)
241  {
242  newState = ManipulationMode.None;
243  }
244  else if (handsPressedCount == 1)
245  {
246  newState = enableOneHandMovement ? ManipulationMode.Move : ManipulationMode.None;
247  }
248  else if (handsPressedCount > 1)
249  {
250  newState = manipulationMode;
251  }
252  break;
253  case ManipulationMode.Scale:
254  case ManipulationMode.Rotate:
255  case ManipulationMode.MoveAndScale:
256  case ManipulationMode.MoveAndRotate:
257  case ManipulationMode.RotateAndScale:
258  case ManipulationMode.MoveScaleAndRotate:
259  if (handsPressedCount == 0)
260  {
261  newState = ManipulationMode.None;
262  }
263  else if (handsPressedCount == 1)
264  {
265  newState = enableOneHandMovement ? ManipulationMode.Move : ManipulationMode.None;
266  }
267  break;
268  }
269 
270  InvokeStateUpdateFunctions(currentState, newState);
271  currentState = newState;
272  }
273 
274  private void InvokeStateUpdateFunctions(ManipulationMode oldState, ManipulationMode newState)
275  {
276  if (newState != oldState)
277  {
278  switch (newState)
279  {
280  case ManipulationMode.None:
281  OnManipulationEnded();
282  break;
283  case ManipulationMode.Move:
284  OnOneHandMoveStarted();
285  break;
286  case ManipulationMode.Scale:
287  case ManipulationMode.Rotate:
288  case ManipulationMode.MoveAndScale:
289  case ManipulationMode.MoveAndRotate:
290  case ManipulationMode.RotateAndScale:
291  case ManipulationMode.MoveScaleAndRotate:
292  OnTwoHandManipulationStarted(newState);
293  break;
294  }
295 
296  switch (oldState)
297  {
298  case ManipulationMode.None:
299  OnManipulationStarted();
300  break;
301  case ManipulationMode.Move:
302  break;
303  case ManipulationMode.Scale:
304  case ManipulationMode.Rotate:
305  case ManipulationMode.MoveAndScale:
306  case ManipulationMode.MoveAndRotate:
307  case ManipulationMode.RotateAndScale:
308  case ManipulationMode.MoveScaleAndRotate:
309  OnTwoHandManipulationEnded();
310  break;
311  }
312  }
313  else
314  {
315  switch (newState)
316  {
317  case ManipulationMode.None:
318  break;
319  case ManipulationMode.Move:
320  OnOneHandMoveUpdated();
321  break;
322  case ManipulationMode.Scale:
323  case ManipulationMode.Rotate:
324  case ManipulationMode.MoveAndScale:
325  case ManipulationMode.MoveAndRotate:
326  case ManipulationMode.RotateAndScale:
327  case ManipulationMode.MoveScaleAndRotate:
328  OnTwoHandManipulationUpdated();
329  break;
330  }
331  }
332  }
333 
334  private void OnTwoHandManipulationUpdated()
335  {
336 #if UNITY_2017_2_OR_NEWER
337  var targetRotation = hostTransform.rotation;
338  var targetPosition = hostTransform.position;
339  var targetScale = hostTransform.localScale;
340 
341  if ((currentState & ManipulationMode.Move) > 0)
342  {
343  targetPosition = moveLogic.Update(GetHandsCentroid(), targetPosition);
344  }
345 
346  if ((currentState & ManipulationMode.Rotate) > 0)
347  {
348  targetRotation = rotateLogic.Update(handsPressedLocationsMap, hostTransform, targetRotation);
349  }
350 
351  if ((currentState & ManipulationMode.Scale) > 0)
352  {
353  targetScale = scaleLogic.UpdateMap(handsPressedLocationsMap);
354  }
355 
356  hostTransform.position = targetPosition;
357  hostTransform.rotation = targetRotation;
358  hostTransform.localScale = targetScale;
359 #endif // UNITY_2017_2_OR_NEWER
360  }
361 
362  private void OnOneHandMoveUpdated()
363  {
364  var targetPosition = moveLogic.Update(handsPressedLocationsMap.Values.First(), hostTransform.position);
365 
366  hostTransform.position = targetPosition;
367  }
368 
369  private void OnTwoHandManipulationEnded()
370  {
371 #if UNITY_2017_2_OR_NEWER
372  // This implementation currently does nothing
373 #endif // UNITY_2017_2_OR_NEWER
374  }
375 
376  private Vector3 GetHandsCentroid()
377  {
378  Vector3 result = handsPressedLocationsMap.Values.Aggregate(Vector3.zero, (current, state) => current + state);
379  return result / handsPressedLocationsMap.Count;
380  }
381 
382  private void OnTwoHandManipulationStarted(ManipulationMode newState)
383  {
384 #if UNITY_2017_2_OR_NEWER
385  if ((newState & ManipulationMode.Rotate) > 0)
386  {
387  rotateLogic.Setup(handsPressedLocationsMap, hostTransform);
388  }
389 
390  if ((newState & ManipulationMode.Move) > 0)
391  {
392  moveLogic.Setup(GetHandsCentroid(), hostTransform);
393  }
394 
395  if ((newState & ManipulationMode.Scale) > 0)
396  {
397  scaleLogic.Setup(handsPressedLocationsMap, hostTransform);
398  }
399 #endif // UNITY_2017_2_OR_NEWER
400  }
401 
402  private void OnOneHandMoveStarted()
403  {
404  Assert.IsTrue(handsPressedLocationsMap.Count == 1);
405 
406  moveLogic.Setup(handsPressedLocationsMap.Values.First(), hostTransform);
407  }
408 
409  private void OnManipulationStarted()
410  {
411  InputManager.Instance.PushModalInputHandler(gameObject);
412 
413  // Show Bounding Box visual on manipulation interaction
414  ShowBoundingBox = true;
415  }
416 
417  private void OnManipulationEnded()
418  {
419  InputManager.Instance.PopModalInputHandler();
420 
421  // Hide Bounding Box visual on release
422  ShowBoundingBox = false;
423  }
424  }
425 }
void Setup(Vector3 startHandPositionMeters, Transform manipulationRoot)
Initialize system with controller/hand states- starting position and current Transform.
void OnSourceDetected(SourceStateEventData eventData)
OnSourceDetected Event Handler.
virtual GameObject Target
The target object being manipulated
Definition: BoundingBox.cs:147
virtual void Setup(Dictionary< uint, Vector3 > handsPressedMap, Transform manipulationRoot)
Initialize system with source info from controllers/hands
This script allows for an object to be movable, scalable, and rotatable with one or two hands...
Input Manager is responsible for managing input sources and dispatching relevant events to the approp...
Definition: InputManager.cs:19
void OnInputDown(InputEventData eventData)
Event Handler receives input from inputSource.
uint SourceId
The id of the source the event is from, for instance the hand id.
virtual Vector3 UpdateMap(Dictionary< uint, Vector3 > handsPressedMap)
update Gameobject with new Scale state
Quaternion Update(Dictionary< uint, Vector3 > handsPressedMap, Transform manipulationRoot, Quaternion currentRotation)
Updates internal states based on current Controller/hand states.
void Setup(Dictionary< uint, Vector3 > handsPressedMap, Transform manipulationRoot)
Initializes twohand system with controller/hand source info: the Dictionary collection already filled...
Implements a movement logic that uses the model of angular rotations along a sphere whose radius vari...
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.
Base class for bounding box objects
Definition: BoundingBox.cs:14
void SetManipulationMode(ManipulationMode mode)
Change the manipulation mode.
void OnInputUp(InputEventData eventData)
Event Handler receives input from inputSource.
Implements a scale logic that will scale an object based on the ratio of the distance between hands...
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 to implement to react to source state changes, such as when an input source is detected or ...
Interface to implement to react to simple pointer-like input.
void OnSourceLost(SourceStateEventData eventData)
OnSourceLost Event Handler.
Describes an source state event that has a source id.
Vector3 Update(Vector3 centroid, Vector3 manipulationObjectPosition)
Updates gameobject with new position information of controller/hand
Describes an input event that has a source id and a press kind.
Implements common logic for rotating holograms using a handlebar metaphor. each frame, object_rotation_delta = rotation_delta(current_hands_vector, previous_hands_vector) where hands_vector is the vector between two hand/controller positions.