IMHOTEP Framework
 All Classes Namespaces Functions Variables Enumerations Enumerator Properties Pages
Hand.cs
1 //======= Copyright (c) Valve Corporation, All rights reserved. ===============
2 //
3 // Purpose: The hands used by the player in the vr interaction system
4 //
5 //=============================================================================
6 
7 using UnityEngine;
8 using System;
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Collections.ObjectModel;
12 
13 namespace Valve.VR.InteractionSystem
14 {
15  //-------------------------------------------------------------------------
16  // Links with an appropriate SteamVR controller and facilitates
17  // interactions with objects in the virtual world.
18  //-------------------------------------------------------------------------
19  public class Hand : MonoBehaviour
20  {
21  public enum HandType
22  {
23  Left,
24  Right,
25  Any
26  };
27 
28  // The flags used to determine how an object is attached to the hand.
29  [Flags]
30  public enum AttachmentFlags
31  {
32  SnapOnAttach = 1 << 0, // The object should snap to the position of the specified attachment point on the hand.
33  DetachOthers = 1 << 1, // Other objects attached to this hand will be detached.
34  DetachFromOtherHand = 1 << 2, // This object will be detached from the other hand.
35  ParentToHand = 1 << 3, // The object will be parented to the hand.
36  };
37 
38  public const AttachmentFlags defaultAttachmentFlags = AttachmentFlags.ParentToHand |
39  AttachmentFlags.DetachOthers |
40  AttachmentFlags.DetachFromOtherHand |
41  AttachmentFlags.SnapOnAttach;
42 
43  public Hand otherHand;
44  public HandType startingHandType;
45 
46  public Transform hoverSphereTransform;
47  public float hoverSphereRadius = 0.05f;
48  public LayerMask hoverLayerMask = -1;
49  public float hoverUpdateInterval = 0.1f;
50 
51  public Camera noSteamVRFallbackCamera;
52  public float noSteamVRFallbackMaxDistanceNoItem = 10.0f;
53  public float noSteamVRFallbackMaxDistanceWithItem = 0.5f;
54  private float noSteamVRFallbackInteractorDistance = -1.0f;
55 
56  public SteamVR_Controller.Device controller;
57 
58  public GameObject controllerPrefab;
59  private GameObject controllerObject = null;
60 
61  public bool showDebugText = false;
62  public bool spewDebugText = false;
63 
64  public struct AttachedObject
65  {
66  public GameObject attachedObject;
67  public GameObject originalParent;
68  public bool isParentedToHand;
69  }
70 
71  private List<AttachedObject> attachedObjects = new List<AttachedObject>();
72 
73  public ReadOnlyCollection<AttachedObject> AttachedObjects
74  {
75  get { return attachedObjects.AsReadOnly(); }
76  }
77 
78  public bool hoverLocked { get; private set; }
79 
80  private Interactable _hoveringInteractable;
81 
82  private TextMesh debugText;
83  private int prevOverlappingColliders = 0;
84 
85  private const int ColliderArraySize = 16;
86  private Collider[] overlappingColliders;
87 
88  private Player playerInstance;
89 
90  private GameObject applicationLostFocusObject;
91 
92  SteamVR_Events.Action inputFocusAction;
93 
94 
95  //-------------------------------------------------
96  // The Interactable object this Hand is currently hovering over
97  //-------------------------------------------------
98  public Interactable hoveringInteractable
99  {
100  get { return _hoveringInteractable; }
101  set
102  {
103  if ( _hoveringInteractable != value )
104  {
105  if ( _hoveringInteractable != null )
106  {
107  HandDebugLog( "HoverEnd " + _hoveringInteractable.gameObject );
108  _hoveringInteractable.SendMessage( "OnHandHoverEnd", this, SendMessageOptions.DontRequireReceiver );
109 
110  //Note: The _hoveringInteractable can change after sending the OnHandHoverEnd message so we need to check it again before broadcasting this message
111  if ( _hoveringInteractable != null )
112  {
113  this.BroadcastMessage( "OnParentHandHoverEnd", _hoveringInteractable, SendMessageOptions.DontRequireReceiver ); // let objects attached to the hand know that a hover has ended
114  }
115  }
116 
117  _hoveringInteractable = value;
118 
119  if ( _hoveringInteractable != null )
120  {
121  HandDebugLog( "HoverBegin " + _hoveringInteractable.gameObject );
122  _hoveringInteractable.SendMessage( "OnHandHoverBegin", this, SendMessageOptions.DontRequireReceiver );
123 
124  //Note: The _hoveringInteractable can change after sending the OnHandHoverBegin message so we need to check it again before broadcasting this message
125  if ( _hoveringInteractable != null )
126  {
127  this.BroadcastMessage( "OnParentHandHoverBegin", _hoveringInteractable, SendMessageOptions.DontRequireReceiver ); // let objects attached to the hand know that a hover has begun
128  }
129  }
130  }
131  }
132  }
133 
134 
135  //-------------------------------------------------
136  // Active GameObject attached to this Hand
137  //-------------------------------------------------
138  public GameObject currentAttachedObject
139  {
140  get
141  {
142  CleanUpAttachedObjectStack();
143 
144  if ( attachedObjects.Count > 0 )
145  {
146  return attachedObjects[attachedObjects.Count - 1].attachedObject;
147  }
148 
149  return null;
150  }
151  }
152 
153 
154  //-------------------------------------------------
155  public Transform GetAttachmentTransform( string attachmentPoint = "" )
156  {
157  Transform attachmentTransform = null;
158 
159  if ( !string.IsNullOrEmpty( attachmentPoint ) )
160  {
161  attachmentTransform = transform.Find( attachmentPoint );
162  }
163 
164  if ( !attachmentTransform )
165  {
166  attachmentTransform = this.transform;
167  }
168 
169  return attachmentTransform;
170  }
171 
172 
173  //-------------------------------------------------
174  // Guess the type of this Hand
175  //
176  // If startingHandType is Hand.Left or Hand.Right, returns startingHandType.
177  // If otherHand is non-null and both Hands are linked to controllers, returns
178  // Hand.Left if this Hand is leftmost relative to the HMD, otherwise Hand.Right.
179  // Otherwise, returns Hand.Any
180  //-------------------------------------------------
181  public HandType GuessCurrentHandType()
182  {
183  if ( startingHandType == HandType.Left || startingHandType == HandType.Right )
184  {
185  return startingHandType;
186  }
187 
188  if ( startingHandType == HandType.Any && otherHand != null && otherHand.controller == null )
189  {
190  return HandType.Right;
191  }
192 
193  if ( controller == null || otherHand == null || otherHand.controller == null )
194  {
195  return startingHandType;
196  }
197 
198  if ( controller.index == SteamVR_Controller.GetDeviceIndex( SteamVR_Controller.DeviceRelation.Leftmost ) )
199  {
200  return HandType.Left;
201  }
202 
203  return HandType.Right;
204  }
205 
206 
207  //-------------------------------------------------
208  // Attach a GameObject to this GameObject
209  //
210  // objectToAttach - The GameObject to attach
211  // flags - The flags to use for attaching the object
212  // attachmentPoint - Name of the GameObject in the hierarchy of this Hand which should act as the attachment point for this GameObject
213  //-------------------------------------------------
214  public void AttachObject( GameObject objectToAttach, AttachmentFlags flags = defaultAttachmentFlags, string attachmentPoint = "" )
215  {
216  if ( flags == 0 )
217  {
218  flags = defaultAttachmentFlags;
219  }
220 
221  //Make sure top object on stack is non-null
222  CleanUpAttachedObjectStack();
223 
224  //Detach the object if it is already attached so that it can get re-attached at the top of the stack
225  DetachObject( objectToAttach );
226 
227  //Detach from the other hand if requested
228  if ( ( ( flags & AttachmentFlags.DetachFromOtherHand ) == AttachmentFlags.DetachFromOtherHand ) && otherHand )
229  {
230  otherHand.DetachObject( objectToAttach );
231  }
232 
233  if ( ( flags & AttachmentFlags.DetachOthers ) == AttachmentFlags.DetachOthers )
234  {
235  //Detach all the objects from the stack
236  while ( attachedObjects.Count > 0 )
237  {
238  DetachObject( attachedObjects[0].attachedObject );
239  }
240  }
241 
242  if ( currentAttachedObject )
243  {
244  currentAttachedObject.SendMessage( "OnHandFocusLost", this, SendMessageOptions.DontRequireReceiver );
245  }
246 
247  AttachedObject attachedObject = new AttachedObject();
248  attachedObject.attachedObject = objectToAttach;
249  attachedObject.originalParent = objectToAttach.transform.parent != null ? objectToAttach.transform.parent.gameObject : null;
250  if ( ( flags & AttachmentFlags.ParentToHand ) == AttachmentFlags.ParentToHand )
251  {
252  //Parent the object to the hand
253  objectToAttach.transform.parent = GetAttachmentTransform( attachmentPoint );
254  attachedObject.isParentedToHand = true;
255  }
256  else
257  {
258  attachedObject.isParentedToHand = false;
259  }
260  attachedObjects.Add( attachedObject );
261 
262  if ( ( flags & AttachmentFlags.SnapOnAttach ) == AttachmentFlags.SnapOnAttach )
263  {
264  objectToAttach.transform.localPosition = Vector3.zero;
265  objectToAttach.transform.localRotation = Quaternion.identity;
266  }
267 
268  HandDebugLog( "AttachObject " + objectToAttach );
269  objectToAttach.SendMessage( "OnAttachedToHand", this, SendMessageOptions.DontRequireReceiver );
270 
271  UpdateHovering();
272  }
273 
274 
275  //-------------------------------------------------
276  // Detach this GameObject from the attached object stack of this Hand
277  //
278  // objectToDetach - The GameObject to detach from this Hand
279  //-------------------------------------------------
280  public void DetachObject( GameObject objectToDetach, bool restoreOriginalParent = true )
281  {
282  int index = attachedObjects.FindIndex( l => l.attachedObject == objectToDetach );
283  if ( index != -1 )
284  {
285  HandDebugLog( "DetachObject " + objectToDetach );
286 
287  GameObject prevTopObject = currentAttachedObject;
288 
289  Transform parentTransform = null;
290  if ( attachedObjects[index].isParentedToHand )
291  {
292  if ( restoreOriginalParent && ( attachedObjects[index].originalParent != null ) )
293  {
294  parentTransform = attachedObjects[index].originalParent.transform;
295  }
296  attachedObjects[index].attachedObject.transform.parent = parentTransform;
297  }
298 
299  attachedObjects[index].attachedObject.SetActive( true );
300  attachedObjects[index].attachedObject.SendMessage( "OnDetachedFromHand", this, SendMessageOptions.DontRequireReceiver );
301  attachedObjects.RemoveAt( index );
302 
303  GameObject newTopObject = currentAttachedObject;
304 
305  //Give focus to the top most object on the stack if it changed
306  if ( newTopObject != null && newTopObject != prevTopObject )
307  {
308  newTopObject.SetActive( true );
309  newTopObject.SendMessage( "OnHandFocusAcquired", this, SendMessageOptions.DontRequireReceiver );
310  }
311  }
312 
313  CleanUpAttachedObjectStack();
314  }
315 
316 
317  //-------------------------------------------------
318  // Get the world velocity of the VR Hand.
319  // Note: controller velocity value only updates on controller events (Button but and down) so good for throwing
320  //-------------------------------------------------
321  public Vector3 GetTrackedObjectVelocity()
322  {
323  if ( controller != null )
324  {
325  return transform.parent.TransformVector( controller.velocity );
326  }
327 
328  return Vector3.zero;
329  }
330 
331 
332  //-------------------------------------------------
333  // Get the world angular velocity of the VR Hand.
334  // Note: controller velocity value only updates on controller events (Button but and down) so good for throwing
335  //-------------------------------------------------
336  public Vector3 GetTrackedObjectAngularVelocity()
337  {
338  if ( controller != null )
339  {
340  return transform.parent.TransformVector( controller.angularVelocity );
341  }
342 
343  return Vector3.zero;
344  }
345 
346 
347  //-------------------------------------------------
348  private void CleanUpAttachedObjectStack()
349  {
350  attachedObjects.RemoveAll( l => l.attachedObject == null );
351  }
352 
353 
354  //-------------------------------------------------
355  void Awake()
356  {
357  inputFocusAction = SteamVR_Events.InputFocusAction( OnInputFocus );
358 
359  if ( hoverSphereTransform == null )
360  {
361  hoverSphereTransform = this.transform;
362  }
363 
364  applicationLostFocusObject = new GameObject( "_application_lost_focus" );
365  applicationLostFocusObject.transform.parent = transform;
366  applicationLostFocusObject.SetActive( false );
367  }
368 
369 
370  //-------------------------------------------------
371  IEnumerator Start()
372  {
373  // save off player instance
374  playerInstance = Player.instance;
375  if ( !playerInstance )
376  {
377  Debug.LogError( "No player instance found in Hand Start()" );
378  }
379 
380  // allocate array for colliders
381  overlappingColliders = new Collider[ColliderArraySize];
382 
383  // We are a "no SteamVR fallback hand" if we have this camera set
384  // we'll use the right mouse to look around and left mouse to interact
385  // - don't need to find the device
386  if ( noSteamVRFallbackCamera )
387  {
388  yield break;
389  }
390 
391  //Debug.Log( "Hand - initializing connection routine" );
392 
393  // Acquire the correct device index for the hand we want to be
394  // Also for the other hand if we get there first
395  while ( true )
396  {
397  // Don't need to run this every frame
398  yield return new WaitForSeconds( 1.0f );
399 
400  // We have a controller now, break out of the loop!
401  if ( controller != null )
402  break;
403 
404  //Debug.Log( "Hand - checking controllers..." );
405 
406  // Initialize both hands simultaneously
407  if ( startingHandType == HandType.Left || startingHandType == HandType.Right )
408  {
409  // Left/right relationship.
410  // Wait until we have a clear unique left-right relationship to initialize.
411  int leftIndex = SteamVR_Controller.GetDeviceIndex( SteamVR_Controller.DeviceRelation.Leftmost );
412  int rightIndex = SteamVR_Controller.GetDeviceIndex( SteamVR_Controller.DeviceRelation.Rightmost );
413  if ( leftIndex == -1 || rightIndex == -1 || leftIndex == rightIndex )
414  {
415  //Debug.Log( string.Format( "...Left/right hand relationship not yet established: leftIndex={0}, rightIndex={1}", leftIndex, rightIndex ) );
416  continue;
417  }
418 
419  int myIndex = ( startingHandType == HandType.Right ) ? rightIndex : leftIndex;
420  int otherIndex = ( startingHandType == HandType.Right ) ? leftIndex : rightIndex;
421 
422  InitController( myIndex );
423  if ( otherHand )
424  {
425  otherHand.InitController( otherIndex );
426  }
427  }
428  else
429  {
430  // No left/right relationship. Just wait for a connection
431 
432  var vr = SteamVR.instance;
433  for ( int i = 0; i < Valve.VR.OpenVR.k_unMaxTrackedDeviceCount; i++ )
434  {
435  if ( vr.hmd.GetTrackedDeviceClass( (uint)i ) != Valve.VR.ETrackedDeviceClass.Controller )
436  {
437  //Debug.Log( string.Format( "Hand - device {0} is not a controller", i ) );
438  continue;
439  }
440 
441  var device = SteamVR_Controller.Input( i );
442  if ( !device.valid )
443  {
444  //Debug.Log( string.Format( "Hand - device {0} is not valid", i ) );
445  continue;
446  }
447 
448  if ( ( otherHand != null ) && ( otherHand.controller != null ) )
449  {
450  // Other hand is using this index, so we cannot use it.
451  if ( i == (int)otherHand.controller.index )
452  {
453  //Debug.Log( string.Format( "Hand - device {0} is owned by the other hand", i ) );
454  continue;
455  }
456  }
457 
458  InitController( i );
459  }
460  }
461  }
462  }
463 
464 
465  //-------------------------------------------------
466  private void UpdateHovering()
467  {
468  if ( ( noSteamVRFallbackCamera == null ) && ( controller == null ) )
469  {
470  return;
471  }
472 
473  if ( hoverLocked )
474  return;
475 
476  if ( applicationLostFocusObject.activeSelf )
477  return;
478 
479  float closestDistance = float.MaxValue;
480  Interactable closestInteractable = null;
481 
482  // Pick the closest hovering
483  float flHoverRadiusScale = playerInstance.transform.lossyScale.x;
484  float flScaledSphereRadius = hoverSphereRadius * flHoverRadiusScale;
485 
486  // if we're close to the floor, increase the radius to make things easier to pick up
487  float handDiff = Mathf.Abs( transform.position.y - playerInstance.trackingOriginTransform.position.y );
488  float boxMult = Util.RemapNumberClamped( handDiff, 0.0f, 0.5f * flHoverRadiusScale, 5.0f, 1.0f ) * flHoverRadiusScale;
489 
490  // null out old vals
491  for ( int i = 0; i < overlappingColliders.Length; ++i )
492  {
493  overlappingColliders[i] = null;
494  }
495 
496  Physics.OverlapBoxNonAlloc(
497  hoverSphereTransform.position - new Vector3( 0, flScaledSphereRadius * boxMult - flScaledSphereRadius, 0 ),
498  new Vector3( flScaledSphereRadius, flScaledSphereRadius * boxMult * 2.0f, flScaledSphereRadius ),
499  overlappingColliders,
500  Quaternion.identity,
501  hoverLayerMask.value
502  );
503 
504  // DebugVar
505  int iActualColliderCount = 0;
506 
507  foreach ( Collider collider in overlappingColliders )
508  {
509  if ( collider == null )
510  continue;
511 
512  Interactable contacting = collider.GetComponentInParent<Interactable>();
513 
514  // Yeah, it's null, skip
515  if ( contacting == null )
516  continue;
517 
518  // Ignore this collider for hovering
519  IgnoreHovering ignore = collider.GetComponent<IgnoreHovering>();
520  if ( ignore != null )
521  {
522  if ( ignore.onlyIgnoreHand == null || ignore.onlyIgnoreHand == this )
523  {
524  continue;
525  }
526  }
527 
528  // Can't hover over the object if it's attached
529  if ( attachedObjects.FindIndex( l => l.attachedObject == contacting.gameObject ) != -1 )
530  continue;
531 
532  // Occupied by another hand, so we can't touch it
533  if ( otherHand && otherHand.hoveringInteractable == contacting )
534  continue;
535 
536  // Best candidate so far...
537  float distance = Vector3.Distance( contacting.transform.position, hoverSphereTransform.position );
538  if ( distance < closestDistance )
539  {
540  closestDistance = distance;
541  closestInteractable = contacting;
542  }
543  iActualColliderCount++;
544  }
545 
546  // Hover on this one
547  hoveringInteractable = closestInteractable;
548 
549  if ( iActualColliderCount > 0 && iActualColliderCount != prevOverlappingColliders )
550  {
551  prevOverlappingColliders = iActualColliderCount;
552  HandDebugLog( "Found " + iActualColliderCount + " overlapping colliders." );
553  }
554  }
555 
556 
557  //-------------------------------------------------
558  private void UpdateNoSteamVRFallback()
559  {
560  if ( noSteamVRFallbackCamera )
561  {
562  Ray ray = noSteamVRFallbackCamera.ScreenPointToRay( Input.mousePosition );
563 
564  if ( attachedObjects.Count > 0 )
565  {
566  // Holding down the mouse:
567  // move around a fixed distance from the camera
568  transform.position = ray.origin + noSteamVRFallbackInteractorDistance * ray.direction;
569  }
570  else
571  {
572  // Not holding down the mouse:
573  // cast out a ray to see what we should mouse over
574 
575  // Don't want to hit the hand and anything underneath it
576  // So move it back behind the camera when we do the raycast
577  Vector3 oldPosition = transform.position;
578  transform.position = noSteamVRFallbackCamera.transform.forward * ( -1000.0f );
579 
580  RaycastHit raycastHit;
581  if ( Physics.Raycast( ray, out raycastHit, noSteamVRFallbackMaxDistanceNoItem ) )
582  {
583  transform.position = raycastHit.point;
584 
585  // Remember this distance in case we click and drag the mouse
586  noSteamVRFallbackInteractorDistance = Mathf.Min( noSteamVRFallbackMaxDistanceNoItem, raycastHit.distance );
587  }
588  else if ( noSteamVRFallbackInteractorDistance > 0.0f )
589  {
590  // Move it around at the distance we last had a hit
591  transform.position = ray.origin + Mathf.Min( noSteamVRFallbackMaxDistanceNoItem, noSteamVRFallbackInteractorDistance ) * ray.direction;
592  }
593  else
594  {
595  // Didn't hit, just leave it where it was
596  transform.position = oldPosition;
597  }
598  }
599  }
600  }
601 
602 
603  //-------------------------------------------------
604  private void UpdateDebugText()
605  {
606  if ( showDebugText )
607  {
608  if ( debugText == null )
609  {
610  debugText = new GameObject( "_debug_text" ).AddComponent<TextMesh>();
611  debugText.fontSize = 120;
612  debugText.characterSize = 0.001f;
613  debugText.transform.parent = transform;
614 
615  debugText.transform.localRotation = Quaternion.Euler( 90.0f, 0.0f, 0.0f );
616  }
617 
618  if ( GuessCurrentHandType() == HandType.Right )
619  {
620  debugText.transform.localPosition = new Vector3( -0.05f, 0.0f, 0.0f );
621  debugText.alignment = TextAlignment.Right;
622  debugText.anchor = TextAnchor.UpperRight;
623  }
624  else
625  {
626  debugText.transform.localPosition = new Vector3( 0.05f, 0.0f, 0.0f );
627  debugText.alignment = TextAlignment.Left;
628  debugText.anchor = TextAnchor.UpperLeft;
629  }
630 
631  debugText.text = string.Format(
632  "Hovering: {0}\n" +
633  "Hover Lock: {1}\n" +
634  "Attached: {2}\n" +
635  "Total Attached: {3}\n" +
636  "Type: {4}\n",
637  ( hoveringInteractable ? hoveringInteractable.gameObject.name : "null" ),
638  hoverLocked,
639  ( currentAttachedObject ? currentAttachedObject.name : "null" ),
640  attachedObjects.Count,
641  GuessCurrentHandType().ToString() );
642  }
643  else
644  {
645  if ( debugText != null )
646  {
647  Destroy( debugText.gameObject );
648  }
649  }
650  }
651 
652 
653  //-------------------------------------------------
654  void OnEnable()
655  {
656  inputFocusAction.enabled = true;
657 
658  // Stagger updates between hands
659  float hoverUpdateBegin = ( ( otherHand != null ) && ( otherHand.GetInstanceID() < GetInstanceID() ) ) ? ( 0.5f * hoverUpdateInterval ) : ( 0.0f );
660  InvokeRepeating( "UpdateHovering", hoverUpdateBegin, hoverUpdateInterval );
661  InvokeRepeating( "UpdateDebugText", hoverUpdateBegin, hoverUpdateInterval );
662  }
663 
664 
665  //-------------------------------------------------
666  void OnDisable()
667  {
668  inputFocusAction.enabled = false;
669 
670  CancelInvoke();
671  }
672 
673 
674  //-------------------------------------------------
675  void Update()
676  {
677  UpdateNoSteamVRFallback();
678 
679  GameObject attached = currentAttachedObject;
680  if ( attached )
681  {
682  attached.SendMessage( "HandAttachedUpdate", this, SendMessageOptions.DontRequireReceiver );
683  }
684 
685  if ( hoveringInteractable )
686  {
687  hoveringInteractable.SendMessage( "HandHoverUpdate", this, SendMessageOptions.DontRequireReceiver );
688  }
689  }
690 
691 
692  //-------------------------------------------------
693  void LateUpdate()
694  {
695  //Re-attach the controller if nothing else is attached to the hand
696  if ( controllerObject != null && attachedObjects.Count == 0 )
697  {
698  AttachObject( controllerObject );
699  }
700  }
701 
702 
703  //-------------------------------------------------
704  private void OnInputFocus( bool hasFocus )
705  {
706  if ( hasFocus )
707  {
708  DetachObject( applicationLostFocusObject, true );
709  applicationLostFocusObject.SetActive( false );
710  UpdateHandPoses();
711  UpdateHovering();
712  BroadcastMessage( "OnParentHandInputFocusAcquired", SendMessageOptions.DontRequireReceiver );
713  }
714  else
715  {
716  applicationLostFocusObject.SetActive( true );
717  AttachObject( applicationLostFocusObject, AttachmentFlags.ParentToHand );
718  BroadcastMessage( "OnParentHandInputFocusLost", SendMessageOptions.DontRequireReceiver );
719  }
720  }
721 
722 
723  //-------------------------------------------------
724  void FixedUpdate()
725  {
726  UpdateHandPoses();
727  }
728 
729 
730  //-------------------------------------------------
731  void OnDrawGizmos()
732  {
733  Gizmos.color = new Color( 0.5f, 1.0f, 0.5f, 0.9f );
734  Transform sphereTransform = hoverSphereTransform ? hoverSphereTransform : this.transform;
735  Gizmos.DrawWireSphere( sphereTransform.position, hoverSphereRadius );
736  }
737 
738 
739  //-------------------------------------------------
740  private void HandDebugLog( string msg )
741  {
742  if ( spewDebugText )
743  {
744  Debug.Log( "Hand (" + this.name + "): " + msg );
745  }
746  }
747 
748 
749  //-------------------------------------------------
750  private void UpdateHandPoses()
751  {
752  if ( controller != null )
753  {
754  SteamVR vr = SteamVR.instance;
755  if ( vr != null )
756  {
757  var pose = new Valve.VR.TrackedDevicePose_t();
758  var gamePose = new Valve.VR.TrackedDevicePose_t();
759  var err = vr.compositor.GetLastPoseForTrackedDeviceIndex( controller.index, ref pose, ref gamePose );
760  if ( err == Valve.VR.EVRCompositorError.None )
761  {
762  var t = new SteamVR_Utils.RigidTransform( gamePose.mDeviceToAbsoluteTracking );
763  transform.localPosition = t.pos;
764  transform.localRotation = t.rot;
765  }
766  }
767  }
768  }
769 
770 
771  //-------------------------------------------------
772  // Continue to hover over this object indefinitely, whether or not the Hand moves out of its interaction trigger volume.
773  //
774  // interactable - The Interactable to hover over indefinitely.
775  //-------------------------------------------------
776  public void HoverLock( Interactable interactable )
777  {
778  HandDebugLog( "HoverLock " + interactable );
779  hoverLocked = true;
780  hoveringInteractable = interactable;
781  }
782 
783 
784  //-------------------------------------------------
785  // Stop hovering over this object indefinitely.
786  //
787  // interactable - The hover-locked Interactable to stop hovering over indefinitely.
788  //-------------------------------------------------
789  public void HoverUnlock( Interactable interactable )
790  {
791  HandDebugLog( "HoverUnlock " + interactable );
792  if ( hoveringInteractable == interactable )
793  {
794  hoverLocked = false;
795  }
796  }
797 
798  //-------------------------------------------------
799  // Was the standard interaction button just pressed? In VR, this is a trigger press. In 2D fallback, this is a mouse left-click.
800  //-------------------------------------------------
801  public bool GetStandardInteractionButtonDown()
802  {
803  if ( noSteamVRFallbackCamera )
804  {
805  return Input.GetMouseButtonDown( 0 );
806  }
807  else if ( controller != null )
808  {
809  return controller.GetHairTriggerDown();
810  }
811 
812  return false;
813  }
814 
815 
816  //-------------------------------------------------
817  // Was the standard interaction button just released? In VR, this is a trigger press. In 2D fallback, this is a mouse left-click.
818  //-------------------------------------------------
819  public bool GetStandardInteractionButtonUp()
820  {
821  if ( noSteamVRFallbackCamera )
822  {
823  return Input.GetMouseButtonUp( 0 );
824  }
825  else if ( controller != null )
826  {
827  return controller.GetHairTriggerUp();
828  }
829 
830  return false;
831  }
832 
833 
834  //-------------------------------------------------
835  // Is the standard interaction button being pressed? In VR, this is a trigger press. In 2D fallback, this is a mouse left-click.
836  //-------------------------------------------------
837  public bool GetStandardInteractionButton()
838  {
839  if ( noSteamVRFallbackCamera )
840  {
841  return Input.GetMouseButton( 0 );
842  }
843  else if ( controller != null )
844  {
845  return controller.GetHairTrigger();
846  }
847 
848  return false;
849  }
850 
851 
852  //-------------------------------------------------
853  private void InitController( int index )
854  {
855  if ( controller == null )
856  {
857  controller = SteamVR_Controller.Input( index );
858 
859  HandDebugLog( "Hand " + name + " connected with device index " + controller.index );
860 
861  controllerObject = GameObject.Instantiate( controllerPrefab );
862  controllerObject.SetActive( true );
863  controllerObject.name = controllerPrefab.name + "_" + this.name;
864  controllerObject.layer = gameObject.layer;
865  controllerObject.tag = gameObject.tag;
866  AttachObject( controllerObject );
867  controller.TriggerHapticPulse( 800 );
868 
869  // If the player's scale has been changed the object to attach will be the wrong size.
870  // To fix this we change the object's scale back to its original, pre-attach scale.
871  controllerObject.transform.localScale = controllerPrefab.transform.localScale;
872 
873  this.BroadcastMessage( "OnHandInitialized", index, SendMessageOptions.DontRequireReceiver ); // let child objects know we've initialized
874  }
875  }
876  }
877 
878 #if UNITY_EDITOR
879  //-------------------------------------------------------------------------
880  [UnityEditor.CustomEditor( typeof( Hand ) )]
881  public class HandEditor : UnityEditor.Editor
882  {
883  //-------------------------------------------------
884  // Custom Inspector GUI allows us to click from within the UI
885  //-------------------------------------------------
886  public override void OnInspectorGUI()
887  {
888  DrawDefaultInspector();
889 
890  Hand hand = (Hand)target;
891 
892  if ( hand.otherHand )
893  {
894  if ( hand.otherHand.otherHand != hand )
895  {
896  UnityEditor.EditorGUILayout.HelpBox( "The otherHand of this Hand's otherHand is not this Hand.", UnityEditor.MessageType.Warning );
897  }
898 
899  if ( hand.startingHandType == Hand.HandType.Left && hand.otherHand.startingHandType != Hand.HandType.Right )
900  {
901  UnityEditor.EditorGUILayout.HelpBox( "This is a left Hand but otherHand is not a right Hand.", UnityEditor.MessageType.Warning );
902  }
903 
904  if ( hand.startingHandType == Hand.HandType.Right && hand.otherHand.startingHandType != Hand.HandType.Left )
905  {
906  UnityEditor.EditorGUILayout.HelpBox( "This is a right Hand but otherHand is not a left Hand.", UnityEditor.MessageType.Warning );
907  }
908 
909  if ( hand.startingHandType == Hand.HandType.Any && hand.otherHand.startingHandType != Hand.HandType.Any )
910  {
911  UnityEditor.EditorGUILayout.HelpBox( "This is an any-handed Hand but otherHand is not an any-handed Hand.", UnityEditor.MessageType.Warning );
912  }
913  }
914  }
915  }
916 #endif
917 }