AR Design
UBC EML collab with UBC SALA - visualizing IoT data in AR
BoundingBoxGizmoHandle.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 
5 using UnityEngine;
6 
7 #if UNITY_WSA && UNITY_2017_2_OR_NEWER
8 using UnityEngine.XR.WSA;
9 #endif
10 
11 namespace HoloToolkit.Unity.UX
12 {
17  {
18  private BoundingBoxRig rig;
19  private Transform transformToAffect;
20  private BoundingBoxGizmoHandleTransformType affineType;
22  private Vector3 initialHandPosition;
23  private Vector3 initialScale;
24  private Vector3 initialPosition;
25  private Vector3 initialOrientation;
26  private Quaternion initialRotation;
27  private Quaternion initialHandOrientation;
28  private Vector3 initialScaleOrigin;
29  private InputEventData inputDownEventData;
30  private bool isHandRotationAvailable;
31  private bool isLeftHandedRotation = false;
32  private Vector3 rotationFromPositionScale = new Vector3(-300.0f, -300.0f, -300.0f);
33  private float minimumScaleNav = 0.001f;
34  private float scaleRate = 1.0f;
35  private float maxScale = 10.0f;
36  private BoundingBoxGizmoHandleRotationType rotationCoordinateSystem;
37  private BoundingBoxGizmoHandleHandMotionType handMotionForRotation;
38  private Vector3 lastHandWorldPos = Vector3.zero;
39 
40  public BoundingBoxGizmoHandleTransformType AffineType
41  {
42  get
43  {
44  return affineType;
45  }
46 
47  set
48  {
49  affineType = value;
50  }
51  }
53  {
54  get
55  {
56  return axis;
57  }
58 
59  set
60  {
61  axis = value;
62  }
63  }
64  public bool IsLeftHandedRotation
65  {
66  get
67  {
68  return isLeftHandedRotation;
69  }
70 
71  set
72  {
73  isLeftHandedRotation = value;
74  }
75  }
76  public Transform TransformToAffect
77  {
78  get
79  {
80  return transformToAffect;
81  }
82 
83  set
84  {
85  transformToAffect = value;
86  }
87  }
88  public BoundingBoxGizmoHandleRotationType RotationCoordinateSystem
89  {
90  get
91  {
92  return rotationCoordinateSystem;
93  }
94  set
95  {
96  rotationCoordinateSystem = value;
97  }
98  }
99  public BoundingBoxGizmoHandleHandMotionType HandMotionForRotation
100  {
101  get
102  {
103  return handMotionForRotation;
104  }
105  set
106  {
107  handMotionForRotation = value;
108  }
109  }
110  public float ScaleRate
111  {
112  get
113  {
114  return scaleRate;
115  }
116  set
117  {
118  scaleRate = value;
119  }
120  }
121  public float MaxScale
122  {
123  get
124  {
125  return maxScale;
126  }
127  set
128  {
129  maxScale = value;
130  }
131  }
132  public BoundingBoxRig Rig
133  {
134  get
135  {
136  return rig;
137  }
138 
139  set
140  {
141  rig = value;
142  }
143  }
144  public bool RotateAroundPivot { get; set; }
145 
146  private void Start()
147  {
148  isHandRotationAvailable = true;
149 
150 #if UNITY_WSA && UNITY_2017_2_OR_NEWER
151  if (HolographicSettings.IsDisplayOpaque == false)
152  {
153  isHandRotationAvailable = false;
154  }
155 #endif
156  cachedRenderer = gameObject.GetComponent<Renderer>();
157  }
158  private void Update()
159  {
160  if (inputDownEventData != null)
161  {
162  Vector3 currentHandPosition = Vector3.zero;
163  Quaternion currentHandOrientation = Quaternion.identity;
164 
165  //set values from hand
166  currentHandPosition = GetHandPosition(inputDownEventData.SourceId);
167  if (isHandRotationAvailable)
168  {
169  currentHandOrientation = GetHandOrientation(inputDownEventData.SourceId);
170  }
171 
172  //calculate affines
173  if (this.AffineType == BoundingBoxGizmoHandleTransformType.Scale)
174  {
175  ApplyScale(currentHandPosition);
176  }
177  else if (this.AffineType == BoundingBoxGizmoHandleTransformType.Rotation)
178  {
179  if (isHandRotationAvailable && handMotionForRotation == BoundingBoxGizmoHandleHandMotionType.handRotatesToRotateObject)
180  {
181  ApplyRotation(currentHandOrientation);
182  }
183  else
184  {
185  ApplyRotation(currentHandPosition);
186  }
187  }
188 
189  lastHandWorldPos = currentHandPosition;
190  }
191  }
192 
193  private Vector3 GetHandPosition(uint sourceId)
194  {
195  Vector3 handPosition = new Vector3(0, 0, 0);
196  inputDownEventData.InputSource.TryGetGripPosition(sourceId, out handPosition);
197  return handPosition;
198  }
199  private Quaternion GetHandOrientation(uint sourceId)
200  {
201  Quaternion handOrientation = Quaternion.identity;
202  inputDownEventData.InputSource.TryGetGripRotation(sourceId, out handOrientation);
203  return handOrientation;
204  }
205  private void ApplyScale(Vector3 currentHandPosition)
206  {
207  if ((transformToAffect.position - initialHandPosition).magnitude > minimumScaleNav)
208  {
209  float scaleScalar = (currentHandPosition - transformToAffect.position).magnitude / (transformToAffect.position - initialHandPosition).magnitude;
210  scaleScalar = Mathf.Pow(scaleScalar, scaleRate);
211  Vector3 changeScale = new Vector3(scaleScalar, scaleScalar, scaleScalar);
212  changeScale = GetBoundedScaleChange(changeScale);
213 
214  Vector3 newScale = changeScale;
215  newScale.Scale(initialScale);
216 
217  //scale from object center
218  transformToAffect.localScale = newScale;
219 
220  //now handle offset
221  Vector3 currentScaleOrigin = initialScaleOrigin;
222  currentScaleOrigin.Scale(changeScale);
223  Vector3 postScaleOffset = currentScaleOrigin - initialScaleOrigin;
224 
225  //translate so that scale is effectively from opposite corner
226  transformToAffect.position = initialPosition - postScaleOffset;
227  }
228  }
229  private void ApplyRotation(Quaternion currentHandOrientation)
230  {
231 #if UNITY_2017_1_OR_NEWER
232  Matrix4x4 m = Matrix4x4.Rotate(initialHandOrientation);
233  Vector3 initRay = new Vector3(0, 0, 1);
234  initRay = m.MultiplyPoint(initRay);
235  initRay.Normalize();
236 
237  m = Matrix4x4.Rotate(currentHandOrientation);
238  Vector3 currentRay = new Vector3(0, 0, 1);
239  currentRay = m.MultiplyPoint(currentRay);
240  currentRay.Normalize();
241 
242  float angle = Vector3.Dot(initRay, currentRay);
243  angle = Mathf.Acos(angle) * Mathf.Rad2Deg;
244  if (Mathf.Abs(initRay.y - currentRay.y) < Mathf.Abs(initRay.x - currentRay.x))
245  {
246  if (Vector3.Cross(initRay, currentRay).y > 0)
247  {
248  angle = -angle;
249  }
250  }
251  else if (Vector3.Cross(initRay, currentRay).x > 0)
252  {
253  angle = -angle;
254  }
255 
256  if (rotationCoordinateSystem == BoundingBoxGizmoHandleRotationType.globalCoordinates)
257  {
258  Vector3 newEulers = (Axis == BoundingBoxGizmoHandleAxisToAffect.X ? new Vector3(angle, 0, 0) : Axis == BoundingBoxGizmoHandleAxisToAffect.Y ? new Vector3(0, angle, 0) : new Vector3(0, 0, angle));
259  newEulers += initialOrientation;
260  transformToAffect.rotation = Quaternion.Euler(newEulers);
261  }
262  else
263  {
264  Vector3 axis = (Axis == BoundingBoxGizmoHandleAxisToAffect.X ? new Vector3(1, 0, 0) : Axis == BoundingBoxGizmoHandleAxisToAffect.Y ? new Vector3(0, 1, 0) : new Vector3(0, 0, 1));
265  transformToAffect.localRotation = initialRotation;
266  transformToAffect.Rotate(axis, angle * 5.0f);
267  }
268 #endif // UNITY_2017_1_OR_NEWER
269  }
270  private void ApplyRotation(Vector3 currentHandPosition)
271  {
272  if (RotateAroundPivot)
273  {
274  ApplyRotationPivot(currentHandPosition);
275  }
276  else
277  {
278  ApplyRotationContinuous(currentHandPosition);
279  }
280  }
281 
282  private void ApplyRotationContinuous(Vector3 currentHandPosition)
283  {
284  Vector3 initialRay = initialHandPosition - transformToAffect.position;
285  initialRay.Normalize();
286 
287  Vector3 currentRay = currentHandPosition - transformToAffect.position;
288  currentRay.Normalize();
289 
290  Vector3 delta = currentRay - initialRay;
291  delta.Scale(rotationFromPositionScale);
292 
293  Vector3 newEulers = new Vector3(0, 0, 0);
295  {
296  newEulers = new Vector3(-delta.y, 0, 0);
297  }
298  else if (Axis == BoundingBoxGizmoHandleAxisToAffect.Y)
299  {
300  newEulers = new Vector3(0, delta.x, 0);
301  }
302  else if (Axis == BoundingBoxGizmoHandleAxisToAffect.Z)
303  {
304  newEulers = new Vector3(0, 0, delta.y);
305  }
306  if (IsLeftHandedRotation)
307  {
308  newEulers.Scale(new Vector3(-1.0f, -1.0f, -1.0f));
309  }
310 
311  if (rotationCoordinateSystem == BoundingBoxGizmoHandleRotationType.globalCoordinates)
312  {
313  newEulers += initialOrientation;
314  transformToAffect.rotation = Quaternion.Euler(newEulers);
315  }
316  else
317  {
318  Vector3 axis = (Axis == BoundingBoxGizmoHandleAxisToAffect.X ? new Vector3(1, 0, 0) : Axis == BoundingBoxGizmoHandleAxisToAffect.Y ? new Vector3(0, 1, 0) : new Vector3(0, 0, 1));
319  transformToAffect.localRotation = initialRotation;
320  float angle = newEulers.x != 0 ? newEulers.x : newEulers.y != 0 ? newEulers.y : newEulers.z;
321  transformToAffect.Rotate(axis, angle * 2.0f);
322  }
323  }
324 
325  private void ApplyRotationPivot(Vector3 currentHandPosition)
326  {
327  Vector3 delta = currentHandPosition - lastHandWorldPos;
328 
329  if (delta.sqrMagnitude == 0) { return; }
330 
331  delta.Scale(rotationFromPositionScale);
332 
333  var pivotToHandleDir = (transform.position - transformToAffect.position).normalized;
334  switch (Axis)
335  {
336  default:
338  transformToAffect.Rotate(Vector3.right, Vector3.Dot(delta, Vector3.Cross(pivotToHandleDir, transformToAffect.right)), Space.Self);
339  break;
341  transformToAffect.Rotate(Vector3.up, Vector3.Dot(delta, Vector3.Cross(pivotToHandleDir, transformToAffect.up)), Space.Self);
342  break;
344  transformToAffect.Rotate(Vector3.forward, Vector3.Dot(delta, Vector3.Cross(pivotToHandleDir, transformToAffect.forward)), Space.Self);
345  break;
346  }
347  }
348 
349  private Vector3 GetBoundedScaleChange(Vector3 scale)
350  {
351  Vector3 maximumScale = new Vector3(initialScale.x * maxScale, initialScale.y * maxScale, initialScale.z * maxScale);
352  Vector3 intendedFinalScale = new Vector3(initialScale.x, initialScale.y, initialScale.z);
353  intendedFinalScale.Scale(scale);
354  if (intendedFinalScale.x > maximumScale.x || intendedFinalScale.y > maximumScale.y || intendedFinalScale.z > maximumScale.z)
355  {
356  return new Vector3(maximumScale.x / initialScale.x, maximumScale.y / initialScale.y, maximumScale.z / initialScale.z);
357  }
358 
359  return scale;
360  }
361 
362  private Renderer cachedRenderer;
363 
364  private void ResetRigHandles()
365  {
366  inputDownEventData = null;
367 
368  if (this.AffineType == BoundingBoxGizmoHandleTransformType.Scale)
369  {
370  cachedRenderer.sharedMaterial = Rig.ScaleHandleMaterial;
371  }
372  else
373  {
374  cachedRenderer.sharedMaterial = Rig.RotateHandleMaterial;
375  }
376 
377  HoloToolkit.Unity.InputModule.InputManager.Instance.PopModalInputHandler();
378  Rig.FocusOnHandle(null);
379  }
380 
381  public void OnInputDown(InputEventData eventData)
382  {
383  inputDownEventData = eventData;
384 
385  initialHandPosition = GetHandPosition(eventData.SourceId);
386  lastHandWorldPos = initialHandPosition;
387  initialScale = transformToAffect.localScale;
388  initialPosition = transformToAffect.position;
389  initialOrientation = transformToAffect.rotation.eulerAngles;
390  initialRotation = transformToAffect.rotation;
391  initialHandOrientation = GetHandOrientation(eventData.SourceId);
392  initialScaleOrigin = transformToAffect.position - this.transform.position;
393 
394  HoloToolkit.Unity.InputModule.InputManager.Instance.PushModalInputHandler(gameObject);
395 
396  cachedRenderer.sharedMaterial = Rig.InteractingMaterial;
397  Rig.FocusOnHandle(this.gameObject);
398  eventData.Use();
399  }
400  public void OnInputUp(InputEventData eventData)
401  {
402  inputDownEventData = null;
403  ResetRigHandles();
404 
405  if (eventData != null)
406  {
407  eventData.Use();
408  }
409  }
410  public void OnSourceDetected(SourceStateEventData eventData)
411  {
412  }
413 
414  public void OnSourceLost(SourceStateEventData eventData)
415  {
416  if ((inputDownEventData != null) &&
417  (eventData.SourceId == inputDownEventData.SourceId))
418  {
419  inputDownEventData = null;
420  ResetRigHandles();
421 
422  eventData.Use();
423  }
424  }
425  }
426 }
Constructs the scale and rotate gizmo handles for the Bounding Box
Input Manager is responsible for managing input sources and dispatching relevant events to the approp...
Definition: InputManager.cs:19
uint SourceId
The id of the source the event is from, for instance the hand id.
bool TryGetGripRotation(uint sourceId, out Quaternion rotation)
Returns the rotation of the input source, if available. Not all input sources support rotation inform...
void OnSourceLost(SourceStateEventData eventData)
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.
void OnSourceDetected(SourceStateEventData eventData)
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.
Describes an source state event that has a source id.
Describes an input event that has a source id and a press kind.
Logic for the gizmo handles in Bounding Box