9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Collections.ObjectModel;
13 namespace Valve.VR.InteractionSystem
19 public class Hand : MonoBehaviour
30 public enum AttachmentFlags
32 SnapOnAttach = 1 << 0,
33 DetachOthers = 1 << 1,
34 DetachFromOtherHand = 1 << 2,
35 ParentToHand = 1 << 3,
38 public const AttachmentFlags defaultAttachmentFlags = AttachmentFlags.ParentToHand |
39 AttachmentFlags.DetachOthers |
40 AttachmentFlags.DetachFromOtherHand |
41 AttachmentFlags.SnapOnAttach;
43 public Hand otherHand;
44 public HandType startingHandType;
46 public Transform hoverSphereTransform;
47 public float hoverSphereRadius = 0.05f;
48 public LayerMask hoverLayerMask = -1;
49 public float hoverUpdateInterval = 0.1f;
51 public Camera noSteamVRFallbackCamera;
52 public float noSteamVRFallbackMaxDistanceNoItem = 10.0f;
53 public float noSteamVRFallbackMaxDistanceWithItem = 0.5f;
54 private float noSteamVRFallbackInteractorDistance = -1.0f;
58 public GameObject controllerPrefab;
59 private GameObject controllerObject = null;
61 public bool showDebugText =
false;
62 public bool spewDebugText =
false;
66 public GameObject attachedObject;
67 public GameObject originalParent;
68 public bool isParentedToHand;
71 private List<AttachedObject> attachedObjects =
new List<AttachedObject>();
73 public ReadOnlyCollection<AttachedObject> AttachedObjects
75 get {
return attachedObjects.AsReadOnly(); }
78 public bool hoverLocked {
get;
private set; }
80 private Interactable _hoveringInteractable;
82 private TextMesh debugText;
83 private int prevOverlappingColliders = 0;
85 private const int ColliderArraySize = 16;
86 private Collider[] overlappingColliders;
88 private Player playerInstance;
90 private GameObject applicationLostFocusObject;
98 public Interactable hoveringInteractable
100 get {
return _hoveringInteractable; }
103 if ( _hoveringInteractable != value )
105 if ( _hoveringInteractable != null )
107 HandDebugLog(
"HoverEnd " + _hoveringInteractable.gameObject );
108 _hoveringInteractable.SendMessage(
"OnHandHoverEnd",
this, SendMessageOptions.DontRequireReceiver );
111 if ( _hoveringInteractable != null )
113 this.BroadcastMessage(
"OnParentHandHoverEnd", _hoveringInteractable, SendMessageOptions.DontRequireReceiver );
117 _hoveringInteractable = value;
119 if ( _hoveringInteractable != null )
121 HandDebugLog(
"HoverBegin " + _hoveringInteractable.gameObject );
122 _hoveringInteractable.SendMessage(
"OnHandHoverBegin",
this, SendMessageOptions.DontRequireReceiver );
125 if ( _hoveringInteractable != null )
127 this.BroadcastMessage(
"OnParentHandHoverBegin", _hoveringInteractable, SendMessageOptions.DontRequireReceiver );
138 public GameObject currentAttachedObject
142 CleanUpAttachedObjectStack();
144 if ( attachedObjects.Count > 0 )
146 return attachedObjects[attachedObjects.Count - 1].attachedObject;
155 public Transform GetAttachmentTransform(
string attachmentPoint =
"" )
157 Transform attachmentTransform = null;
159 if ( !
string.IsNullOrEmpty( attachmentPoint ) )
161 attachmentTransform = transform.Find( attachmentPoint );
164 if ( !attachmentTransform )
166 attachmentTransform = this.transform;
169 return attachmentTransform;
181 public HandType GuessCurrentHandType()
183 if ( startingHandType == HandType.Left || startingHandType == HandType.Right )
185 return startingHandType;
188 if ( startingHandType == HandType.Any && otherHand != null && otherHand.controller == null )
190 return HandType.Right;
193 if ( controller == null || otherHand == null || otherHand.controller == null )
195 return startingHandType;
200 return HandType.Left;
203 return HandType.Right;
214 public void AttachObject( GameObject objectToAttach, AttachmentFlags flags = defaultAttachmentFlags,
string attachmentPoint =
"" )
218 flags = defaultAttachmentFlags;
222 CleanUpAttachedObjectStack();
225 DetachObject( objectToAttach );
228 if ( ( ( flags & AttachmentFlags.DetachFromOtherHand ) == AttachmentFlags.DetachFromOtherHand ) && otherHand )
230 otherHand.DetachObject( objectToAttach );
233 if ( ( flags & AttachmentFlags.DetachOthers ) == AttachmentFlags.DetachOthers )
236 while ( attachedObjects.Count > 0 )
238 DetachObject( attachedObjects[0].attachedObject );
242 if ( currentAttachedObject )
244 currentAttachedObject.SendMessage(
"OnHandFocusLost",
this, SendMessageOptions.DontRequireReceiver );
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 )
253 objectToAttach.transform.parent = GetAttachmentTransform( attachmentPoint );
254 attachedObject.isParentedToHand =
true;
258 attachedObject.isParentedToHand =
false;
260 attachedObjects.Add( attachedObject );
262 if ( ( flags & AttachmentFlags.SnapOnAttach ) == AttachmentFlags.SnapOnAttach )
264 objectToAttach.transform.localPosition = Vector3.zero;
265 objectToAttach.transform.localRotation = Quaternion.identity;
268 HandDebugLog(
"AttachObject " + objectToAttach );
269 objectToAttach.SendMessage(
"OnAttachedToHand",
this, SendMessageOptions.DontRequireReceiver );
280 public void DetachObject( GameObject objectToDetach,
bool restoreOriginalParent =
true )
282 int index = attachedObjects.FindIndex( l => l.attachedObject == objectToDetach );
285 HandDebugLog(
"DetachObject " + objectToDetach );
287 GameObject prevTopObject = currentAttachedObject;
289 Transform parentTransform = null;
290 if ( attachedObjects[index].isParentedToHand )
292 if ( restoreOriginalParent && ( attachedObjects[index].originalParent != null ) )
294 parentTransform = attachedObjects[index].originalParent.transform;
296 attachedObjects[index].attachedObject.transform.parent = parentTransform;
299 attachedObjects[index].attachedObject.SetActive( true );
300 attachedObjects[index].attachedObject.SendMessage(
"OnDetachedFromHand",
this, SendMessageOptions.DontRequireReceiver );
301 attachedObjects.RemoveAt( index );
303 GameObject newTopObject = currentAttachedObject;
306 if ( newTopObject != null && newTopObject != prevTopObject )
308 newTopObject.SetActive( true );
309 newTopObject.SendMessage(
"OnHandFocusAcquired",
this, SendMessageOptions.DontRequireReceiver );
313 CleanUpAttachedObjectStack();
321 public Vector3 GetTrackedObjectVelocity()
323 if ( controller != null )
325 return transform.parent.TransformVector( controller.velocity );
336 public Vector3 GetTrackedObjectAngularVelocity()
338 if ( controller != null )
340 return transform.parent.TransformVector( controller.angularVelocity );
348 private void CleanUpAttachedObjectStack()
350 attachedObjects.RemoveAll( l => l.attachedObject == null );
357 inputFocusAction = SteamVR_Events.InputFocusAction( OnInputFocus );
359 if ( hoverSphereTransform == null )
361 hoverSphereTransform = this.transform;
364 applicationLostFocusObject =
new GameObject(
"_application_lost_focus" );
365 applicationLostFocusObject.transform.parent = transform;
366 applicationLostFocusObject.SetActive( false );
374 playerInstance = Player.instance;
375 if ( !playerInstance )
377 Debug.LogError(
"No player instance found in Hand Start()" );
381 overlappingColliders =
new Collider[ColliderArraySize];
386 if ( noSteamVRFallbackCamera )
398 yield
return new WaitForSeconds( 1.0f );
401 if ( controller != null )
407 if ( startingHandType == HandType.Left || startingHandType == HandType.Right )
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 )
419 int myIndex = ( startingHandType == HandType.Right ) ? rightIndex : leftIndex;
420 int otherIndex = ( startingHandType == HandType.Right ) ? leftIndex : rightIndex;
422 InitController( myIndex );
425 otherHand.InitController( otherIndex );
432 var vr = SteamVR.instance;
433 for (
int i = 0; i < Valve.VR.OpenVR.k_unMaxTrackedDeviceCount; i++ )
435 if ( vr.hmd.GetTrackedDeviceClass( (uint)i ) != Valve.VR.ETrackedDeviceClass.Controller )
441 var device = SteamVR_Controller.Input( i );
448 if ( ( otherHand != null ) && ( otherHand.controller != null ) )
451 if ( i == (
int)otherHand.controller.index )
466 private void UpdateHovering()
468 if ( ( noSteamVRFallbackCamera == null ) && ( controller == null ) )
476 if ( applicationLostFocusObject.activeSelf )
479 float closestDistance = float.MaxValue;
480 Interactable closestInteractable = null;
483 float flHoverRadiusScale = playerInstance.transform.lossyScale.x;
484 float flScaledSphereRadius = hoverSphereRadius * flHoverRadiusScale;
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;
491 for (
int i = 0; i < overlappingColliders.Length; ++i )
493 overlappingColliders[i] = null;
496 Physics.OverlapBoxNonAlloc(
497 hoverSphereTransform.position -
new Vector3( 0, flScaledSphereRadius * boxMult - flScaledSphereRadius, 0 ),
498 new Vector3( flScaledSphereRadius, flScaledSphereRadius * boxMult * 2.0f, flScaledSphereRadius ),
499 overlappingColliders,
505 int iActualColliderCount = 0;
507 foreach ( Collider collider
in overlappingColliders )
509 if ( collider == null )
512 Interactable contacting = collider.GetComponentInParent<Interactable>();
515 if ( contacting == null )
519 IgnoreHovering ignore = collider.GetComponent<IgnoreHovering>();
520 if ( ignore != null )
522 if ( ignore.onlyIgnoreHand == null || ignore.onlyIgnoreHand ==
this )
529 if ( attachedObjects.FindIndex( l => l.attachedObject == contacting.gameObject ) != -1 )
533 if ( otherHand && otherHand.hoveringInteractable == contacting )
537 float distance = Vector3.Distance( contacting.transform.position, hoverSphereTransform.position );
538 if ( distance < closestDistance )
540 closestDistance = distance;
541 closestInteractable = contacting;
543 iActualColliderCount++;
547 hoveringInteractable = closestInteractable;
549 if ( iActualColliderCount > 0 && iActualColliderCount != prevOverlappingColliders )
551 prevOverlappingColliders = iActualColliderCount;
552 HandDebugLog(
"Found " + iActualColliderCount +
" overlapping colliders." );
558 private void UpdateNoSteamVRFallback()
560 if ( noSteamVRFallbackCamera )
562 Ray ray = noSteamVRFallbackCamera.ScreenPointToRay( Input.mousePosition );
564 if ( attachedObjects.Count > 0 )
568 transform.position = ray.origin + noSteamVRFallbackInteractorDistance * ray.direction;
577 Vector3 oldPosition = transform.position;
578 transform.position = noSteamVRFallbackCamera.transform.forward * ( -1000.0f );
580 RaycastHit raycastHit;
581 if ( Physics.Raycast( ray, out raycastHit, noSteamVRFallbackMaxDistanceNoItem ) )
583 transform.position = raycastHit.point;
586 noSteamVRFallbackInteractorDistance = Mathf.Min( noSteamVRFallbackMaxDistanceNoItem, raycastHit.distance );
588 else if ( noSteamVRFallbackInteractorDistance > 0.0f )
591 transform.position = ray.origin + Mathf.Min( noSteamVRFallbackMaxDistanceNoItem, noSteamVRFallbackInteractorDistance ) * ray.direction;
596 transform.position = oldPosition;
604 private void UpdateDebugText()
608 if ( debugText == null )
610 debugText =
new GameObject(
"_debug_text" ).AddComponent<TextMesh>();
611 debugText.fontSize = 120;
612 debugText.characterSize = 0.001f;
613 debugText.transform.parent = transform;
615 debugText.transform.localRotation = Quaternion.Euler( 90.0f, 0.0f, 0.0f );
618 if ( GuessCurrentHandType() == HandType.Right )
620 debugText.transform.localPosition =
new Vector3( -0.05f, 0.0f, 0.0f );
621 debugText.alignment = TextAlignment.Right;
622 debugText.anchor = TextAnchor.UpperRight;
626 debugText.transform.localPosition =
new Vector3( 0.05f, 0.0f, 0.0f );
627 debugText.alignment = TextAlignment.Left;
628 debugText.anchor = TextAnchor.UpperLeft;
631 debugText.text = string.Format(
633 "Hover Lock: {1}\n" +
635 "Total Attached: {3}\n" +
637 ( hoveringInteractable ? hoveringInteractable.gameObject.name :
"null" ),
639 ( currentAttachedObject ? currentAttachedObject.name :
"null" ),
640 attachedObjects.Count,
641 GuessCurrentHandType().ToString() );
645 if ( debugText != null )
647 Destroy( debugText.gameObject );
656 inputFocusAction.enabled =
true;
659 float hoverUpdateBegin = ( ( otherHand != null ) && ( otherHand.GetInstanceID() < GetInstanceID() ) ) ? ( 0.5f * hoverUpdateInterval ) : ( 0.0f );
660 InvokeRepeating(
"UpdateHovering", hoverUpdateBegin, hoverUpdateInterval );
661 InvokeRepeating(
"UpdateDebugText", hoverUpdateBegin, hoverUpdateInterval );
668 inputFocusAction.enabled =
false;
677 UpdateNoSteamVRFallback();
679 GameObject attached = currentAttachedObject;
682 attached.SendMessage(
"HandAttachedUpdate",
this, SendMessageOptions.DontRequireReceiver );
685 if ( hoveringInteractable )
687 hoveringInteractable.SendMessage(
"HandHoverUpdate",
this, SendMessageOptions.DontRequireReceiver );
696 if ( controllerObject != null && attachedObjects.Count == 0 )
698 AttachObject( controllerObject );
704 private void OnInputFocus(
bool hasFocus )
708 DetachObject( applicationLostFocusObject,
true );
709 applicationLostFocusObject.SetActive( false );
712 BroadcastMessage(
"OnParentHandInputFocusAcquired", SendMessageOptions.DontRequireReceiver );
716 applicationLostFocusObject.SetActive( true );
717 AttachObject( applicationLostFocusObject, AttachmentFlags.ParentToHand );
718 BroadcastMessage(
"OnParentHandInputFocusLost", SendMessageOptions.DontRequireReceiver );
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 );
740 private void HandDebugLog(
string msg )
744 Debug.Log(
"Hand (" + this.name +
"): " + msg );
750 private void UpdateHandPoses()
752 if ( controller != null )
759 var err = vr.compositor.GetLastPoseForTrackedDeviceIndex( controller.index, ref pose, ref gamePose );
760 if ( err == Valve.VR.EVRCompositorError.None )
763 transform.localPosition = t.pos;
764 transform.localRotation = t.rot;
776 public void HoverLock( Interactable interactable )
778 HandDebugLog(
"HoverLock " + interactable );
780 hoveringInteractable = interactable;
789 public void HoverUnlock( Interactable interactable )
791 HandDebugLog(
"HoverUnlock " + interactable );
792 if ( hoveringInteractable == interactable )
801 public bool GetStandardInteractionButtonDown()
803 if ( noSteamVRFallbackCamera )
805 return Input.GetMouseButtonDown( 0 );
807 else if ( controller != null )
809 return controller.GetHairTriggerDown();
819 public bool GetStandardInteractionButtonUp()
821 if ( noSteamVRFallbackCamera )
823 return Input.GetMouseButtonUp( 0 );
825 else if ( controller != null )
827 return controller.GetHairTriggerUp();
837 public bool GetStandardInteractionButton()
839 if ( noSteamVRFallbackCamera )
841 return Input.GetMouseButton( 0 );
843 else if ( controller != null )
845 return controller.GetHairTrigger();
853 private void InitController(
int index )
855 if ( controller == null )
857 controller = SteamVR_Controller.Input( index );
859 HandDebugLog(
"Hand " + name +
" connected with device index " + controller.index );
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 );
871 controllerObject.transform.localScale = controllerPrefab.transform.localScale;
873 this.BroadcastMessage(
"OnHandInitialized", index, SendMessageOptions.DontRequireReceiver );
880 [UnityEditor.CustomEditor( typeof( Hand ) )]
881 public class HandEditor : UnityEditor.Editor
886 public override void OnInspectorGUI()
888 DrawDefaultInspector();
890 Hand hand = (Hand)target;
892 if ( hand.otherHand )
894 if ( hand.otherHand.otherHand != hand )
896 UnityEditor.EditorGUILayout.HelpBox(
"The otherHand of this Hand's otherHand is not this Hand.", UnityEditor.MessageType.Warning );
899 if ( hand.startingHandType == Hand.HandType.Left && hand.otherHand.startingHandType != Hand.HandType.Right )
901 UnityEditor.EditorGUILayout.HelpBox(
"This is a left Hand but otherHand is not a right Hand.", UnityEditor.MessageType.Warning );
904 if ( hand.startingHandType == Hand.HandType.Right && hand.otherHand.startingHandType != Hand.HandType.Left )
906 UnityEditor.EditorGUILayout.HelpBox(
"This is a right Hand but otherHand is not a left Hand.", UnityEditor.MessageType.Warning );
909 if ( hand.startingHandType == Hand.HandType.Any && hand.otherHand.startingHandType != Hand.HandType.Any )
911 UnityEditor.EditorGUILayout.HelpBox(
"This is an any-handed Hand but otherHand is not an any-handed Hand.", UnityEditor.MessageType.Warning );