﻿//======= Copyright (c) Valve Corporation, All rights reserved. ===============
//
// Purpose: The hands used by the player in the vr interaction system
//
//=============================================================================

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

namespace Valve.VR.InteractionSystem
{
	//-------------------------------------------------------------------------
	// Links with an appropriate SteamVR controller and facilitates
	// interactions with objects in the virtual world.
	//-------------------------------------------------------------------------
	public class Hand : MonoBehaviour
	{
		public enum HandType
		{
			Left,
			Right,
			Any
		};

		// The flags used to determine how an object is attached to the hand.
		[Flags]
		public enum AttachmentFlags
		{
			SnapOnAttach = 1 << 0, // The object should snap to the position of the specified attachment point on the hand.
			DetachOthers = 1 << 1, // Other objects attached to this hand will be detached.
			DetachFromOtherHand = 1 << 2, // This object will be detached from the other hand.
			ParentToHand = 1 << 3, // The object will be parented to the hand.
		};

		public const AttachmentFlags defaultAttachmentFlags = AttachmentFlags.ParentToHand |
															  AttachmentFlags.DetachOthers |
															  AttachmentFlags.DetachFromOtherHand |
															  AttachmentFlags.SnapOnAttach;

		public Hand otherHand;
		public HandType startingHandType;

		public Transform hoverSphereTransform;
		public float hoverSphereRadius = 0.05f;
		public LayerMask hoverLayerMask = -1;
		public float hoverUpdateInterval = 0.1f;

		public Camera noSteamVRFallbackCamera;
		public float noSteamVRFallbackMaxDistanceNoItem = 10.0f;
		public float noSteamVRFallbackMaxDistanceWithItem = 0.5f;
		private float noSteamVRFallbackInteractorDistance = -1.0f;

		public SteamVR_Controller.Device controller;

		public GameObject controllerPrefab;
		private GameObject controllerObject = null;

		public bool showDebugText = false;
		public bool spewDebugText = false;

		public struct AttachedObject
		{
			public GameObject attachedObject;
			public GameObject originalParent;
			public bool isParentedToHand;
		}

		private List<AttachedObject> attachedObjects = new List<AttachedObject>();

		public ReadOnlyCollection<AttachedObject> AttachedObjects
		{
			get { return attachedObjects.AsReadOnly(); }
		}

		public bool hoverLocked { get; private set; }

		private Interactable _hoveringInteractable;

		private TextMesh debugText;
		private int prevOverlappingColliders = 0;

		private const int ColliderArraySize = 16;
		private Collider[] overlappingColliders;

		private Player playerInstance;

		private GameObject applicationLostFocusObject;

		SteamVR_Events.Action inputFocusAction;


		//-------------------------------------------------
		// The Interactable object this Hand is currently hovering over
		//-------------------------------------------------
		public Interactable hoveringInteractable
		{
			get { return _hoveringInteractable; }
			set
			{
				if ( _hoveringInteractable != value )
				{
					if ( _hoveringInteractable != null )
					{
						HandDebugLog( "HoverEnd " + _hoveringInteractable.gameObject );
						_hoveringInteractable.SendMessage( "OnHandHoverEnd", this, SendMessageOptions.DontRequireReceiver );

						//Note: The _hoveringInteractable can change after sending the OnHandHoverEnd message so we need to check it again before broadcasting this message
						if ( _hoveringInteractable != null )
						{
							this.BroadcastMessage( "OnParentHandHoverEnd", _hoveringInteractable, SendMessageOptions.DontRequireReceiver ); // let objects attached to the hand know that a hover has ended
						}
					}

					_hoveringInteractable = value;

					if ( _hoveringInteractable != null )
					{
						HandDebugLog( "HoverBegin " + _hoveringInteractable.gameObject );
						_hoveringInteractable.SendMessage( "OnHandHoverBegin", this, SendMessageOptions.DontRequireReceiver );

						//Note: The _hoveringInteractable can change after sending the OnHandHoverBegin message so we need to check it again before broadcasting this message
						if ( _hoveringInteractable != null )
						{
							this.BroadcastMessage( "OnParentHandHoverBegin", _hoveringInteractable, SendMessageOptions.DontRequireReceiver ); // let objects attached to the hand know that a hover has begun
						}
					}
				}
			}
		}


		//-------------------------------------------------
		// Active GameObject attached to this Hand
		//-------------------------------------------------
		public GameObject currentAttachedObject
		{
			get
			{
				CleanUpAttachedObjectStack();

				if ( attachedObjects.Count > 0 )
				{
					return attachedObjects[attachedObjects.Count - 1].attachedObject;
				}

				return null;
			}
		}


		//-------------------------------------------------
		public Transform GetAttachmentTransform( string attachmentPoint = "" )
		{
			Transform attachmentTransform = null;

			if ( !string.IsNullOrEmpty( attachmentPoint ) )
			{
				attachmentTransform = transform.Find( attachmentPoint );
			}

			if ( !attachmentTransform )
			{
				attachmentTransform = this.transform;
			}

			return attachmentTransform;
		}


		//-------------------------------------------------
		// Guess the type of this Hand
		//
		// If startingHandType is Hand.Left or Hand.Right, returns startingHandType.
		// If otherHand is non-null and both Hands are linked to controllers, returns
		// Hand.Left if this Hand is leftmost relative to the HMD, otherwise Hand.Right.
		// Otherwise, returns Hand.Any
		//-------------------------------------------------
		public HandType GuessCurrentHandType()
		{
			if ( startingHandType == HandType.Left || startingHandType == HandType.Right )
			{
				return startingHandType;
			}

			if ( startingHandType == HandType.Any && otherHand != null && otherHand.controller == null )
			{
				return HandType.Right;
			}

			if ( controller == null || otherHand == null || otherHand.controller == null )
			{
				return startingHandType;
			}

			if ( controller.index == SteamVR_Controller.GetDeviceIndex( SteamVR_Controller.DeviceRelation.Leftmost ) )
			{
				return HandType.Left;
			}

			return HandType.Right;
		}


		//-------------------------------------------------
		// Attach a GameObject to this GameObject
		//
		// objectToAttach - The GameObject to attach
		// flags - The flags to use for attaching the object
		// attachmentPoint - Name of the GameObject in the hierarchy of this Hand which should act as the attachment point for this GameObject
		//-------------------------------------------------
		public void AttachObject( GameObject objectToAttach, AttachmentFlags flags = defaultAttachmentFlags, string attachmentPoint = "" )
		{
			if ( flags == 0 )
			{
				flags = defaultAttachmentFlags;
			}

			//Make sure top object on stack is non-null
			CleanUpAttachedObjectStack();

			//Detach the object if it is already attached so that it can get re-attached at the top of the stack
			DetachObject( objectToAttach );

			//Detach from the other hand if requested
			if ( ( ( flags & AttachmentFlags.DetachFromOtherHand ) == AttachmentFlags.DetachFromOtherHand ) && otherHand )
			{
				otherHand.DetachObject( objectToAttach );
			}

			if ( ( flags & AttachmentFlags.DetachOthers ) == AttachmentFlags.DetachOthers )
			{
				//Detach all the objects from the stack
				while ( attachedObjects.Count > 0 )
				{
					DetachObject( attachedObjects[0].attachedObject );
				}
			}

			if ( currentAttachedObject )
			{
				currentAttachedObject.SendMessage( "OnHandFocusLost", this, SendMessageOptions.DontRequireReceiver );
			}

			AttachedObject attachedObject = new AttachedObject();
			attachedObject.attachedObject = objectToAttach;
			attachedObject.originalParent = objectToAttach.transform.parent != null ? objectToAttach.transform.parent.gameObject : null;
			if ( ( flags & AttachmentFlags.ParentToHand ) == AttachmentFlags.ParentToHand )
			{
				//Parent the object to the hand
				objectToAttach.transform.parent = GetAttachmentTransform( attachmentPoint );
				attachedObject.isParentedToHand = true;
			}
			else
			{
				attachedObject.isParentedToHand = false;
			}
			attachedObjects.Add( attachedObject );

			if ( ( flags & AttachmentFlags.SnapOnAttach ) == AttachmentFlags.SnapOnAttach )
			{
				objectToAttach.transform.localPosition = Vector3.zero;
				objectToAttach.transform.localRotation = Quaternion.identity;
			}

			HandDebugLog( "AttachObject " + objectToAttach );
			objectToAttach.SendMessage( "OnAttachedToHand", this, SendMessageOptions.DontRequireReceiver );

			UpdateHovering();
		}


		//-------------------------------------------------
		// Detach this GameObject from the attached object stack of this Hand
		//
		// objectToDetach - The GameObject to detach from this Hand
		//-------------------------------------------------
		public void DetachObject( GameObject objectToDetach, bool restoreOriginalParent = true )
		{
			int index = attachedObjects.FindIndex( l => l.attachedObject == objectToDetach );
			if ( index != -1 )
			{
				HandDebugLog( "DetachObject " + objectToDetach );

				GameObject prevTopObject = currentAttachedObject;

				Transform parentTransform = null;
				if ( attachedObjects[index].isParentedToHand )
				{
					if ( restoreOriginalParent && ( attachedObjects[index].originalParent != null ) )
					{
						parentTransform = attachedObjects[index].originalParent.transform;
					}
					attachedObjects[index].attachedObject.transform.parent = parentTransform;
				}

				attachedObjects[index].attachedObject.SetActive( true );
				attachedObjects[index].attachedObject.SendMessage( "OnDetachedFromHand", this, SendMessageOptions.DontRequireReceiver );
				attachedObjects.RemoveAt( index );

				GameObject newTopObject = currentAttachedObject;

				//Give focus to the top most object on the stack if it changed
				if ( newTopObject != null && newTopObject != prevTopObject )
				{
					newTopObject.SetActive( true );
					newTopObject.SendMessage( "OnHandFocusAcquired", this, SendMessageOptions.DontRequireReceiver );
				}
			}

			CleanUpAttachedObjectStack();
		}


		//-------------------------------------------------
		// Get the world velocity of the VR Hand.
		// Note: controller velocity value only updates on controller events (Button but and down) so good for throwing
		//-------------------------------------------------
		public Vector3 GetTrackedObjectVelocity()
		{
			if ( controller != null )
			{
				return transform.parent.TransformVector( controller.velocity );
			}

			return Vector3.zero;
		}


		//-------------------------------------------------
		// Get the world angular velocity of the VR Hand.
		// Note: controller velocity value only updates on controller events (Button but and down) so good for throwing
		//-------------------------------------------------
		public Vector3 GetTrackedObjectAngularVelocity()
		{
			if ( controller != null )
			{
				return transform.parent.TransformVector( controller.angularVelocity );
			}

			return Vector3.zero;
		}


		//-------------------------------------------------
		private void CleanUpAttachedObjectStack()
		{
			attachedObjects.RemoveAll( l => l.attachedObject == null );
		}


		//-------------------------------------------------
		void Awake()
		{
			inputFocusAction = SteamVR_Events.InputFocusAction( OnInputFocus );

			if ( hoverSphereTransform == null )
			{
				hoverSphereTransform = this.transform;
			}

			applicationLostFocusObject = new GameObject( "_application_lost_focus" );
			applicationLostFocusObject.transform.parent = transform;
			applicationLostFocusObject.SetActive( false );
		}


		//-------------------------------------------------
		IEnumerator Start()
		{
			// save off player instance
			playerInstance = Player.instance;
			if ( !playerInstance )
			{
				Debug.LogError( "No player instance found in Hand Start()" );
			}

			// allocate array for colliders
			overlappingColliders = new Collider[ColliderArraySize];

			// We are a "no SteamVR fallback hand" if we have this camera set
			// we'll use the right mouse to look around and left mouse to interact
			// - don't need to find the device
			if ( noSteamVRFallbackCamera )
			{
				yield break;
			}

			//Debug.Log( "Hand - initializing connection routine" );

			// Acquire the correct device index for the hand we want to be
			// Also for the other hand if we get there first
			while ( true )
			{
				// Don't need to run this every frame
				yield return new WaitForSeconds( 1.0f );

				// We have a controller now, break out of the loop!
				if ( controller != null )
					break;

				//Debug.Log( "Hand - checking controllers..." );

				// Initialize both hands simultaneously
				if ( startingHandType == HandType.Left || startingHandType == HandType.Right )
				{
					// Left/right relationship.
					// Wait until we have a clear unique left-right relationship to initialize.
					int leftIndex = SteamVR_Controller.GetDeviceIndex( SteamVR_Controller.DeviceRelation.Leftmost );
					int rightIndex = SteamVR_Controller.GetDeviceIndex( SteamVR_Controller.DeviceRelation.Rightmost );
					if ( leftIndex == -1 || rightIndex == -1 || leftIndex == rightIndex )
					{
						//Debug.Log( string.Format( "...Left/right hand relationship not yet established: leftIndex={0}, rightIndex={1}", leftIndex, rightIndex ) );
						continue;
					}

					int myIndex = ( startingHandType == HandType.Right ) ? rightIndex : leftIndex;
					int otherIndex = ( startingHandType == HandType.Right ) ? leftIndex : rightIndex;

					InitController( myIndex );
					if ( otherHand )
					{
						otherHand.InitController( otherIndex );
					}
				}
				else
				{
					// No left/right relationship. Just wait for a connection

					var vr = SteamVR.instance;
					for ( int i = 0; i < Valve.VR.OpenVR.k_unMaxTrackedDeviceCount; i++ )
					{
						if ( vr.hmd.GetTrackedDeviceClass( (uint)i ) != Valve.VR.ETrackedDeviceClass.Controller )
						{
							//Debug.Log( string.Format( "Hand - device {0} is not a controller", i ) );
							continue;
						}

						var device = SteamVR_Controller.Input( i );
						if ( !device.valid )
						{
							//Debug.Log( string.Format( "Hand - device {0} is not valid", i ) );
							continue;
						}

						if ( ( otherHand != null ) && ( otherHand.controller != null ) )
						{
							// Other hand is using this index, so we cannot use it.
							if ( i == (int)otherHand.controller.index )
							{
								//Debug.Log( string.Format( "Hand - device {0} is owned by the other hand", i ) );
								continue;
							}
						}

						InitController( i );
					}
				}
			}
		}


		//-------------------------------------------------
		private void UpdateHovering()
		{
			if ( ( noSteamVRFallbackCamera == null ) && ( controller == null ) )
			{
				return;
			}

			if ( hoverLocked )
				return;

			if ( applicationLostFocusObject.activeSelf )
				return;

			float closestDistance = float.MaxValue;
			Interactable closestInteractable = null;

			// Pick the closest hovering
			float flHoverRadiusScale = playerInstance.transform.lossyScale.x;
			float flScaledSphereRadius = hoverSphereRadius * flHoverRadiusScale;

			// if we're close to the floor, increase the radius to make things easier to pick up
			float handDiff = Mathf.Abs( transform.position.y - playerInstance.trackingOriginTransform.position.y );
			float boxMult = Util.RemapNumberClamped( handDiff, 0.0f, 0.5f * flHoverRadiusScale, 5.0f, 1.0f ) * flHoverRadiusScale;

			// null out old vals
			for ( int i = 0; i < overlappingColliders.Length; ++i )
			{
				overlappingColliders[i] = null;
			}

			Physics.OverlapBoxNonAlloc(
				hoverSphereTransform.position - new Vector3( 0, flScaledSphereRadius * boxMult - flScaledSphereRadius, 0 ),
				new Vector3( flScaledSphereRadius, flScaledSphereRadius * boxMult * 2.0f, flScaledSphereRadius ),
				overlappingColliders,
				Quaternion.identity,
				hoverLayerMask.value
			);

			// DebugVar
			int iActualColliderCount = 0;

			foreach ( Collider collider in overlappingColliders )
			{
				if ( collider == null )
					continue;

				Interactable contacting = collider.GetComponentInParent<Interactable>();

				// Yeah, it's null, skip
				if ( contacting == null )
					continue;

				// Ignore this collider for hovering
				IgnoreHovering ignore = collider.GetComponent<IgnoreHovering>();
				if ( ignore != null )
				{
					if ( ignore.onlyIgnoreHand == null || ignore.onlyIgnoreHand == this )
					{
						continue;
					}
				}

				// Can't hover over the object if it's attached
				if ( attachedObjects.FindIndex( l => l.attachedObject == contacting.gameObject ) != -1 )
					continue;

				// Occupied by another hand, so we can't touch it
				if ( otherHand && otherHand.hoveringInteractable == contacting )
					continue;

				// Best candidate so far...
				float distance = Vector3.Distance( contacting.transform.position, hoverSphereTransform.position );
				if ( distance < closestDistance )
				{
					closestDistance = distance;
					closestInteractable = contacting;
				}
				iActualColliderCount++;
			}

			// Hover on this one
			hoveringInteractable = closestInteractable;

			if ( iActualColliderCount > 0 && iActualColliderCount != prevOverlappingColliders )
			{
				prevOverlappingColliders = iActualColliderCount;
				HandDebugLog( "Found " + iActualColliderCount + " overlapping colliders." );
			}
		}


		//-------------------------------------------------
		private void UpdateNoSteamVRFallback()
		{
			if ( noSteamVRFallbackCamera )
			{
				Ray ray = noSteamVRFallbackCamera.ScreenPointToRay( Input.mousePosition );

				if ( attachedObjects.Count > 0 )
				{
					// Holding down the mouse:
					// move around a fixed distance from the camera
					transform.position = ray.origin + noSteamVRFallbackInteractorDistance * ray.direction;
				}
				else
				{
					// Not holding down the mouse:
					// cast out a ray to see what we should mouse over

					// Don't want to hit the hand and anything underneath it
					// So move it back behind the camera when we do the raycast
					Vector3 oldPosition = transform.position;
					transform.position = noSteamVRFallbackCamera.transform.forward * ( -1000.0f );

					RaycastHit raycastHit;
					if ( Physics.Raycast( ray, out raycastHit, noSteamVRFallbackMaxDistanceNoItem ) )
					{
						transform.position = raycastHit.point;

						// Remember this distance in case we click and drag the mouse
						noSteamVRFallbackInteractorDistance = Mathf.Min( noSteamVRFallbackMaxDistanceNoItem, raycastHit.distance );
					}
					else if ( noSteamVRFallbackInteractorDistance > 0.0f )
					{
						// Move it around at the distance we last had a hit
						transform.position = ray.origin + Mathf.Min( noSteamVRFallbackMaxDistanceNoItem, noSteamVRFallbackInteractorDistance ) * ray.direction;
					}
					else
					{
						// Didn't hit, just leave it where it was
						transform.position = oldPosition;
					}
				}
			}
		}


		//-------------------------------------------------
		private void UpdateDebugText()
		{
			if ( showDebugText )
			{
				if ( debugText == null )
				{
					debugText = new GameObject( "_debug_text" ).AddComponent<TextMesh>();
					debugText.fontSize = 120;
					debugText.characterSize = 0.001f;
					debugText.transform.parent = transform;

					debugText.transform.localRotation = Quaternion.Euler( 90.0f, 0.0f, 0.0f );
				}

				if ( GuessCurrentHandType() == HandType.Right )
				{
					debugText.transform.localPosition = new Vector3( -0.05f, 0.0f, 0.0f );
					debugText.alignment = TextAlignment.Right;
					debugText.anchor = TextAnchor.UpperRight;
				}
				else
				{
					debugText.transform.localPosition = new Vector3( 0.05f, 0.0f, 0.0f );
					debugText.alignment = TextAlignment.Left;
					debugText.anchor = TextAnchor.UpperLeft;
				}

				debugText.text = string.Format(
					"Hovering: {0}\n" +
					"Hover Lock: {1}\n" +
					"Attached: {2}\n" +
					"Total Attached: {3}\n" +
					"Type: {4}\n",
					( hoveringInteractable ? hoveringInteractable.gameObject.name : "null" ),
					hoverLocked,
					( currentAttachedObject ? currentAttachedObject.name : "null" ),
					attachedObjects.Count,
					GuessCurrentHandType().ToString() );
			}
			else
			{
				if ( debugText != null )
				{
					Destroy( debugText.gameObject );
				}
			}
		}


		//-------------------------------------------------
		void OnEnable()
		{
			inputFocusAction.enabled = true;

			// Stagger updates between hands
			float hoverUpdateBegin = ( ( otherHand != null ) && ( otherHand.GetInstanceID() < GetInstanceID() ) ) ? ( 0.5f * hoverUpdateInterval ) : ( 0.0f );
			InvokeRepeating( "UpdateHovering", hoverUpdateBegin, hoverUpdateInterval );
			InvokeRepeating( "UpdateDebugText", hoverUpdateBegin, hoverUpdateInterval );
		}


		//-------------------------------------------------
		void OnDisable()
		{
			inputFocusAction.enabled = false;

			CancelInvoke();
		}


		//-------------------------------------------------
		void Update()
		{
			UpdateNoSteamVRFallback();

			GameObject attached = currentAttachedObject;
			if ( attached )
			{
				attached.SendMessage( "HandAttachedUpdate", this, SendMessageOptions.DontRequireReceiver );
			}

			if ( hoveringInteractable )
			{
				hoveringInteractable.SendMessage( "HandHoverUpdate", this, SendMessageOptions.DontRequireReceiver );
			}
		}


		//-------------------------------------------------
		void LateUpdate()
		{
			//Re-attach the controller if nothing else is attached to the hand
			if ( controllerObject != null && attachedObjects.Count == 0 )
			{
				AttachObject( controllerObject );
			}
		}


		//-------------------------------------------------
		private void OnInputFocus( bool hasFocus )
		{
			if ( hasFocus )
			{
				DetachObject( applicationLostFocusObject, true );
				applicationLostFocusObject.SetActive( false );
				UpdateHandPoses();
				UpdateHovering();
				BroadcastMessage( "OnParentHandInputFocusAcquired", SendMessageOptions.DontRequireReceiver );
			}
			else
			{
				applicationLostFocusObject.SetActive( true );
				AttachObject( applicationLostFocusObject, AttachmentFlags.ParentToHand );
				BroadcastMessage( "OnParentHandInputFocusLost", SendMessageOptions.DontRequireReceiver );
			}
		}


		//-------------------------------------------------
		void FixedUpdate()
		{
			UpdateHandPoses();
		}


		//-------------------------------------------------
		void OnDrawGizmos()
		{
			Gizmos.color = new Color( 0.5f, 1.0f, 0.5f, 0.9f );
			Transform sphereTransform = hoverSphereTransform ? hoverSphereTransform : this.transform;
			Gizmos.DrawWireSphere( sphereTransform.position, hoverSphereRadius );
		}


		//-------------------------------------------------
		private void HandDebugLog( string msg )
		{
			if ( spewDebugText )
			{
				Debug.Log( "Hand (" + this.name + "): " + msg );
			}
		}


		//-------------------------------------------------
		private void UpdateHandPoses()
		{
			if ( controller != null )
			{
				SteamVR vr = SteamVR.instance;
				if ( vr != null )
				{
					var pose = new Valve.VR.TrackedDevicePose_t();
					var gamePose = new Valve.VR.TrackedDevicePose_t();
					var err = vr.compositor.GetLastPoseForTrackedDeviceIndex( controller.index, ref pose, ref gamePose );
					if ( err == Valve.VR.EVRCompositorError.None )
					{
						var t = new SteamVR_Utils.RigidTransform( gamePose.mDeviceToAbsoluteTracking );
						transform.localPosition = t.pos;
						transform.localRotation = t.rot;
					}
				}
			}
		}


		//-------------------------------------------------
		// Continue to hover over this object indefinitely, whether or not the Hand moves out of its interaction trigger volume.
		//
		// interactable - The Interactable to hover over indefinitely.
		//-------------------------------------------------
		public void HoverLock( Interactable interactable )
		{
			HandDebugLog( "HoverLock " + interactable );
			hoverLocked = true;
			hoveringInteractable = interactable;
		}


		//-------------------------------------------------
		// Stop hovering over this object indefinitely.
		//
		// interactable - The hover-locked Interactable to stop hovering over indefinitely.
		//-------------------------------------------------
		public void HoverUnlock( Interactable interactable )
		{
			HandDebugLog( "HoverUnlock " + interactable );
			if ( hoveringInteractable == interactable )
			{
				hoverLocked = false;
			}
		}

		//-------------------------------------------------
		// Was the standard interaction button just pressed? In VR, this is a trigger press. In 2D fallback, this is a mouse left-click.
		//-------------------------------------------------
		public bool GetStandardInteractionButtonDown()
		{
			if ( noSteamVRFallbackCamera )
			{
				return Input.GetMouseButtonDown( 0 );
			}
			else if ( controller != null )
			{
				return controller.GetHairTriggerDown();
			}

			return false;
		}


		//-------------------------------------------------
		// Was the standard interaction button just released? In VR, this is a trigger press. In 2D fallback, this is a mouse left-click.
		//-------------------------------------------------
		public bool GetStandardInteractionButtonUp()
		{
			if ( noSteamVRFallbackCamera )
			{
				return Input.GetMouseButtonUp( 0 );
			}
			else if ( controller != null )
			{
				return controller.GetHairTriggerUp();
			}

			return false;
		}


		//-------------------------------------------------
		// Is the standard interaction button being pressed? In VR, this is a trigger press. In 2D fallback, this is a mouse left-click.
		//-------------------------------------------------
		public bool GetStandardInteractionButton()
		{
			if ( noSteamVRFallbackCamera )
			{
				return Input.GetMouseButton( 0 );
			}
			else if ( controller != null )
			{
				return controller.GetHairTrigger();
			}

			return false;
		}


		//-------------------------------------------------
		private void InitController( int index )
		{
			if ( controller == null )
			{
				controller = SteamVR_Controller.Input( index );

				HandDebugLog( "Hand " + name + " connected with device index " + controller.index );

				controllerObject = GameObject.Instantiate( controllerPrefab );
				controllerObject.SetActive( true );
				controllerObject.name = controllerPrefab.name + "_" + this.name;
				controllerObject.layer = gameObject.layer;
				controllerObject.tag = gameObject.tag;
				AttachObject( controllerObject );
				controller.TriggerHapticPulse( 800 );

				// If the player's scale has been changed the object to attach will be the wrong size.
				// To fix this we change the object's scale back to its original, pre-attach scale.
				controllerObject.transform.localScale = controllerPrefab.transform.localScale;

				this.BroadcastMessage( "OnHandInitialized", index, SendMessageOptions.DontRequireReceiver ); // let child objects know we've initialized
			}
		}
	}

#if UNITY_EDITOR
	//-------------------------------------------------------------------------
	[UnityEditor.CustomEditor( typeof( Hand ) )]
	public class HandEditor : UnityEditor.Editor
	{
		//-------------------------------------------------
		// Custom Inspector GUI allows us to click from within the UI
		//-------------------------------------------------
		public override void OnInspectorGUI()
		{
			DrawDefaultInspector();

			Hand hand = (Hand)target;

			if ( hand.otherHand )
			{
				if ( hand.otherHand.otherHand != hand )
				{
					UnityEditor.EditorGUILayout.HelpBox( "The otherHand of this Hand's otherHand is not this Hand.", UnityEditor.MessageType.Warning );
				}

				if ( hand.startingHandType == Hand.HandType.Left && hand.otherHand.startingHandType != Hand.HandType.Right )
				{
					UnityEditor.EditorGUILayout.HelpBox( "This is a left Hand but otherHand is not a right Hand.", UnityEditor.MessageType.Warning );
				}

				if ( hand.startingHandType == Hand.HandType.Right && hand.otherHand.startingHandType != Hand.HandType.Left )
				{
					UnityEditor.EditorGUILayout.HelpBox( "This is a right Hand but otherHand is not a left Hand.", UnityEditor.MessageType.Warning );
				}

				if ( hand.startingHandType == Hand.HandType.Any && hand.otherHand.startingHandType != Hand.HandType.Any )
				{
					UnityEditor.EditorGUILayout.HelpBox( "This is an any-handed Hand but otherHand is not an any-handed Hand.", UnityEditor.MessageType.Warning );
				}
			}
		}
	}
#endif
}
