1686 lines
70 KiB
C#
1686 lines
70 KiB
C#
//======= 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;
|
|
using UnityEngine.Events;
|
|
using System.Threading;
|
|
|
|
namespace Valve.VR.InteractionSystem
|
|
{
|
|
//-------------------------------------------------------------------------
|
|
// Links with an appropriate SteamVR controller and facilitates
|
|
// interactions with objects in the virtual world.
|
|
//-------------------------------------------------------------------------
|
|
public class Hand : MonoBehaviour
|
|
{
|
|
// 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.
|
|
VelocityMovement = 1 << 4, // The object will attempt to move to match the position and rotation of the hand.
|
|
TurnOnKinematic = 1 << 5, // The object will not respond to external physics.
|
|
TurnOffGravity = 1 << 6, // The object will not respond to external physics.
|
|
AllowSidegrade = 1 << 7, // The object is able to switch from a pinch grab to a grip grab. Decreases likelyhood of a good throw but also decreases likelyhood of accidental drop
|
|
};
|
|
|
|
public const AttachmentFlags defaultAttachmentFlags = AttachmentFlags.ParentToHand |
|
|
AttachmentFlags.DetachOthers |
|
|
AttachmentFlags.DetachFromOtherHand |
|
|
AttachmentFlags.TurnOnKinematic |
|
|
AttachmentFlags.SnapOnAttach;
|
|
|
|
public Hand otherHand;
|
|
public SteamVR_Input_Sources handType;
|
|
|
|
public SteamVR_Behaviour_Pose trackedObject;
|
|
|
|
public SteamVR_Action_Boolean grabPinchAction = SteamVR_Input.GetAction<SteamVR_Action_Boolean>("GrabPinch");
|
|
|
|
public SteamVR_Action_Boolean grabGripAction = SteamVR_Input.GetAction<SteamVR_Action_Boolean>("GrabGrip");
|
|
|
|
public SteamVR_Action_Vibration hapticAction = SteamVR_Input.GetAction<SteamVR_Action_Vibration>("Haptic");
|
|
|
|
public SteamVR_Action_Boolean uiInteractAction = SteamVR_Input.GetAction<SteamVR_Action_Boolean>("InteractUI");
|
|
|
|
public bool useHoverSphere = true;
|
|
public Transform hoverSphereTransform;
|
|
public float hoverSphereRadius = 0.05f;
|
|
public LayerMask hoverLayerMask = -1;
|
|
public float hoverUpdateInterval = 0.1f;
|
|
|
|
public bool useControllerHoverComponent = true;
|
|
public string controllerHoverComponent = "tip";
|
|
public float controllerHoverRadius = 0.075f;
|
|
|
|
public bool useFingerJointHover = true;
|
|
public SteamVR_Skeleton_JointIndexEnum fingerJointHover = SteamVR_Skeleton_JointIndexEnum.indexTip;
|
|
public float fingerJointHoverRadius = 0.025f;
|
|
|
|
[Tooltip("A transform on the hand to center attached objects on")]
|
|
public Transform objectAttachmentPoint;
|
|
|
|
public Camera noSteamVRFallbackCamera;
|
|
public float noSteamVRFallbackMaxDistanceNoItem = 10.0f;
|
|
public float noSteamVRFallbackMaxDistanceWithItem = 0.5f;
|
|
private float noSteamVRFallbackInteractorDistance = -1.0f;
|
|
|
|
public GameObject renderModelPrefab;
|
|
[HideInInspector]
|
|
public List<RenderModel> renderModels = new List<RenderModel>();
|
|
[HideInInspector]
|
|
public RenderModel mainRenderModel;
|
|
[HideInInspector]
|
|
public RenderModel hoverhighlightRenderModel;
|
|
|
|
public bool showDebugText = false;
|
|
public bool spewDebugText = false;
|
|
public bool showDebugInteractables = false;
|
|
|
|
public struct AttachedObject
|
|
{
|
|
public GameObject attachedObject;
|
|
public Interactable interactable;
|
|
public Rigidbody attachedRigidbody;
|
|
public CollisionDetectionMode collisionDetectionMode;
|
|
public bool attachedRigidbodyWasKinematic;
|
|
public bool attachedRigidbodyUsedGravity;
|
|
public GameObject originalParent;
|
|
public bool isParentedToHand;
|
|
public GrabTypes grabbedWithType;
|
|
public AttachmentFlags attachmentFlags;
|
|
public Vector3 initialPositionalOffset;
|
|
public Quaternion initialRotationalOffset;
|
|
public Transform attachedOffsetTransform;
|
|
public Transform handAttachmentPointTransform;
|
|
public Vector3 easeSourcePosition;
|
|
public Quaternion easeSourceRotation;
|
|
public float attachTime;
|
|
public AllowTeleportWhileAttachedToHand allowTeleportWhileAttachedToHand;
|
|
|
|
public bool HasAttachFlag(AttachmentFlags flag)
|
|
{
|
|
return (attachmentFlags & flag) == flag;
|
|
}
|
|
}
|
|
|
|
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 = 32;
|
|
private Collider[] overlappingColliders;
|
|
|
|
private Player playerInstance;
|
|
|
|
private GameObject applicationLostFocusObject;
|
|
|
|
private SteamVR_Events.Action inputFocusAction;
|
|
|
|
public bool isActive
|
|
{
|
|
get
|
|
{
|
|
if (trackedObject != null)
|
|
return trackedObject.isActive;
|
|
|
|
return this.gameObject.activeInHierarchy;
|
|
}
|
|
}
|
|
|
|
public bool isPoseValid
|
|
{
|
|
get
|
|
{
|
|
return trackedObject.isValid;
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// The Interactable object this Hand is currently hovering over
|
|
//-------------------------------------------------
|
|
public Interactable hoveringInteractable
|
|
{
|
|
get { return _hoveringInteractable; }
|
|
set
|
|
{
|
|
if (_hoveringInteractable != value)
|
|
{
|
|
if (_hoveringInteractable != null)
|
|
{
|
|
if (spewDebugText)
|
|
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)
|
|
{
|
|
if (spewDebugText)
|
|
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 AttachedObject? currentAttachedObjectInfo
|
|
{
|
|
get
|
|
{
|
|
CleanUpAttachedObjectStack();
|
|
|
|
if (attachedObjects.Count > 0)
|
|
{
|
|
return attachedObjects[attachedObjects.Count - 1];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public AllowTeleportWhileAttachedToHand currentAttachedTeleportManager
|
|
{
|
|
get
|
|
{
|
|
if (currentAttachedObjectInfo.HasValue)
|
|
return currentAttachedObjectInfo.Value.allowTeleportWhileAttachedToHand;
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public SteamVR_Behaviour_Skeleton skeleton
|
|
{
|
|
get
|
|
{
|
|
if (mainRenderModel != null)
|
|
return mainRenderModel.GetSkeleton();
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public void ShowController(bool permanent = false)
|
|
{
|
|
if (mainRenderModel != null)
|
|
mainRenderModel.SetControllerVisibility(true, permanent);
|
|
|
|
if (hoverhighlightRenderModel != null)
|
|
hoverhighlightRenderModel.SetControllerVisibility(true, permanent);
|
|
}
|
|
|
|
public void HideController(bool permanent = false)
|
|
{
|
|
if (mainRenderModel != null)
|
|
mainRenderModel.SetControllerVisibility(false, permanent);
|
|
|
|
if (hoverhighlightRenderModel != null)
|
|
hoverhighlightRenderModel.SetControllerVisibility(false, permanent);
|
|
}
|
|
|
|
public void ShowSkeleton(bool permanent = false)
|
|
{
|
|
if (mainRenderModel != null)
|
|
mainRenderModel.SetHandVisibility(true, permanent);
|
|
|
|
if (hoverhighlightRenderModel != null)
|
|
hoverhighlightRenderModel.SetHandVisibility(true, permanent);
|
|
}
|
|
|
|
public void HideSkeleton(bool permanent = false)
|
|
{
|
|
if (mainRenderModel != null)
|
|
mainRenderModel.SetHandVisibility(false, permanent);
|
|
|
|
if (hoverhighlightRenderModel != null)
|
|
hoverhighlightRenderModel.SetHandVisibility(false, permanent);
|
|
}
|
|
|
|
public bool HasSkeleton()
|
|
{
|
|
return mainRenderModel != null && mainRenderModel.GetSkeleton() != null;
|
|
}
|
|
|
|
public void Show()
|
|
{
|
|
SetVisibility(true);
|
|
}
|
|
|
|
public void Hide()
|
|
{
|
|
SetVisibility(false);
|
|
}
|
|
|
|
public void SetVisibility(bool visible)
|
|
{
|
|
if (mainRenderModel != null)
|
|
mainRenderModel.SetVisibility(visible);
|
|
}
|
|
|
|
public void SetSkeletonRangeOfMotion(EVRSkeletalMotionRange newRangeOfMotion, float blendOverSeconds = 0.1f)
|
|
{
|
|
for (int renderModelIndex = 0; renderModelIndex < renderModels.Count; renderModelIndex++)
|
|
{
|
|
renderModels[renderModelIndex].SetSkeletonRangeOfMotion(newRangeOfMotion, blendOverSeconds);
|
|
}
|
|
}
|
|
|
|
public void SetTemporarySkeletonRangeOfMotion(SkeletalMotionRangeChange temporaryRangeOfMotionChange, float blendOverSeconds = 0.1f)
|
|
{
|
|
for (int renderModelIndex = 0; renderModelIndex < renderModels.Count; renderModelIndex++)
|
|
{
|
|
renderModels[renderModelIndex].SetTemporarySkeletonRangeOfMotion(temporaryRangeOfMotionChange, blendOverSeconds);
|
|
}
|
|
}
|
|
|
|
public void ResetTemporarySkeletonRangeOfMotion(float blendOverSeconds = 0.1f)
|
|
{
|
|
for (int renderModelIndex = 0; renderModelIndex < renderModels.Count; renderModelIndex++)
|
|
{
|
|
renderModels[renderModelIndex].ResetTemporarySkeletonRangeOfMotion(blendOverSeconds);
|
|
}
|
|
}
|
|
|
|
public void SetAnimationState(int stateValue)
|
|
{
|
|
for (int renderModelIndex = 0; renderModelIndex < renderModels.Count; renderModelIndex++)
|
|
{
|
|
renderModels[renderModelIndex].SetAnimationState(stateValue);
|
|
}
|
|
}
|
|
|
|
public void StopAnimation()
|
|
{
|
|
for (int renderModelIndex = 0; renderModelIndex < renderModels.Count; renderModelIndex++)
|
|
{
|
|
renderModels[renderModelIndex].StopAnimation();
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// 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, GrabTypes grabbedWithType, AttachmentFlags flags = defaultAttachmentFlags, Transform attachmentOffset = null)
|
|
{
|
|
AttachedObject attachedObject = new AttachedObject();
|
|
attachedObject.attachmentFlags = flags;
|
|
attachedObject.attachedOffsetTransform = attachmentOffset;
|
|
attachedObject.attachTime = Time.time;
|
|
|
|
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
|
|
if (ObjectIsAttached(objectToAttach))
|
|
DetachObject(objectToAttach);
|
|
|
|
//Detach from the other hand if requested
|
|
if (attachedObject.HasAttachFlag(AttachmentFlags.DetachFromOtherHand))
|
|
{
|
|
if (otherHand != null)
|
|
otherHand.DetachObject(objectToAttach);
|
|
}
|
|
|
|
if (attachedObject.HasAttachFlag(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 = objectToAttach;
|
|
attachedObject.interactable = objectToAttach.GetComponent<Interactable>();
|
|
attachedObject.allowTeleportWhileAttachedToHand = objectToAttach.GetComponent<AllowTeleportWhileAttachedToHand>();
|
|
attachedObject.handAttachmentPointTransform = this.transform;
|
|
|
|
if (attachedObject.interactable != null)
|
|
{
|
|
if (attachedObject.interactable.attachEaseIn)
|
|
{
|
|
attachedObject.easeSourcePosition = attachedObject.attachedObject.transform.position;
|
|
attachedObject.easeSourceRotation = attachedObject.attachedObject.transform.rotation;
|
|
attachedObject.interactable.snapAttachEaseInCompleted = false;
|
|
}
|
|
|
|
if (attachedObject.interactable.useHandObjectAttachmentPoint)
|
|
attachedObject.handAttachmentPointTransform = objectAttachmentPoint;
|
|
|
|
if (attachedObject.interactable.hideHandOnAttach)
|
|
Hide();
|
|
|
|
if (attachedObject.interactable.hideSkeletonOnAttach && mainRenderModel != null && mainRenderModel.displayHandByDefault)
|
|
HideSkeleton();
|
|
|
|
if (attachedObject.interactable.hideControllerOnAttach && mainRenderModel != null && mainRenderModel.displayControllerByDefault)
|
|
HideController();
|
|
|
|
if (attachedObject.interactable.handAnimationOnPickup != 0)
|
|
SetAnimationState(attachedObject.interactable.handAnimationOnPickup);
|
|
|
|
if (attachedObject.interactable.setRangeOfMotionOnPickup != SkeletalMotionRangeChange.None)
|
|
SetTemporarySkeletonRangeOfMotion(attachedObject.interactable.setRangeOfMotionOnPickup);
|
|
|
|
}
|
|
|
|
attachedObject.originalParent = objectToAttach.transform.parent != null ? objectToAttach.transform.parent.gameObject : null;
|
|
|
|
attachedObject.attachedRigidbody = objectToAttach.GetComponent<Rigidbody>();
|
|
if (attachedObject.attachedRigidbody != null)
|
|
{
|
|
if (attachedObject.interactable.attachedToHand != null) //already attached to another hand
|
|
{
|
|
//if it was attached to another hand, get the flags from that hand
|
|
|
|
for (int attachedIndex = 0; attachedIndex < attachedObject.interactable.attachedToHand.attachedObjects.Count; attachedIndex++)
|
|
{
|
|
AttachedObject attachedObjectInList = attachedObject.interactable.attachedToHand.attachedObjects[attachedIndex];
|
|
if (attachedObjectInList.interactable == attachedObject.interactable)
|
|
{
|
|
attachedObject.attachedRigidbodyWasKinematic = attachedObjectInList.attachedRigidbodyWasKinematic;
|
|
attachedObject.attachedRigidbodyUsedGravity = attachedObjectInList.attachedRigidbodyUsedGravity;
|
|
attachedObject.originalParent = attachedObjectInList.originalParent;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
attachedObject.attachedRigidbodyWasKinematic = attachedObject.attachedRigidbody.isKinematic;
|
|
attachedObject.attachedRigidbodyUsedGravity = attachedObject.attachedRigidbody.useGravity;
|
|
}
|
|
}
|
|
|
|
attachedObject.grabbedWithType = grabbedWithType;
|
|
|
|
if (attachedObject.HasAttachFlag(AttachmentFlags.ParentToHand))
|
|
{
|
|
//Parent the object to the hand
|
|
objectToAttach.transform.parent = this.transform;
|
|
attachedObject.isParentedToHand = true;
|
|
}
|
|
else
|
|
{
|
|
attachedObject.isParentedToHand = false;
|
|
}
|
|
|
|
if (attachedObject.HasAttachFlag(AttachmentFlags.SnapOnAttach))
|
|
{
|
|
if (attachedObject.interactable != null && attachedObject.interactable.skeletonPoser != null && HasSkeleton())
|
|
{
|
|
SteamVR_Skeleton_PoseSnapshot pose = attachedObject.interactable.skeletonPoser.GetBlendedPose(skeleton);
|
|
|
|
//snap the object to the center of the attach point
|
|
objectToAttach.transform.position = this.transform.TransformPoint(pose.position);
|
|
objectToAttach.transform.rotation = this.transform.rotation * pose.rotation;
|
|
|
|
attachedObject.initialPositionalOffset = attachedObject.handAttachmentPointTransform.InverseTransformPoint(objectToAttach.transform.position);
|
|
attachedObject.initialRotationalOffset = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * objectToAttach.transform.rotation;
|
|
}
|
|
else
|
|
{
|
|
if (attachmentOffset != null)
|
|
{
|
|
//offset the object from the hand by the positional and rotational difference between the offset transform and the attached object
|
|
Quaternion rotDiff = Quaternion.Inverse(attachmentOffset.transform.rotation) * objectToAttach.transform.rotation;
|
|
objectToAttach.transform.rotation = attachedObject.handAttachmentPointTransform.rotation * rotDiff;
|
|
|
|
Vector3 posDiff = objectToAttach.transform.position - attachmentOffset.transform.position;
|
|
objectToAttach.transform.position = attachedObject.handAttachmentPointTransform.position + posDiff;
|
|
}
|
|
else
|
|
{
|
|
//snap the object to the center of the attach point
|
|
objectToAttach.transform.rotation = attachedObject.handAttachmentPointTransform.rotation;
|
|
objectToAttach.transform.position = attachedObject.handAttachmentPointTransform.position;
|
|
}
|
|
|
|
Transform followPoint = objectToAttach.transform;
|
|
|
|
attachedObject.initialPositionalOffset = attachedObject.handAttachmentPointTransform.InverseTransformPoint(followPoint.position);
|
|
attachedObject.initialRotationalOffset = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * followPoint.rotation;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (attachedObject.interactable != null && attachedObject.interactable.skeletonPoser != null && HasSkeleton())
|
|
{
|
|
attachedObject.initialPositionalOffset = attachedObject.handAttachmentPointTransform.InverseTransformPoint(objectToAttach.transform.position);
|
|
attachedObject.initialRotationalOffset = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * objectToAttach.transform.rotation;
|
|
}
|
|
else
|
|
{
|
|
if (attachmentOffset != null)
|
|
{
|
|
//get the initial positional and rotational offsets between the hand and the offset transform
|
|
Quaternion rotDiff = Quaternion.Inverse(attachmentOffset.transform.rotation) * objectToAttach.transform.rotation;
|
|
Quaternion targetRotation = attachedObject.handAttachmentPointTransform.rotation * rotDiff;
|
|
Quaternion rotationPositionBy = targetRotation * Quaternion.Inverse(objectToAttach.transform.rotation);
|
|
|
|
Vector3 posDiff = (rotationPositionBy * objectToAttach.transform.position) - (rotationPositionBy * attachmentOffset.transform.position);
|
|
|
|
attachedObject.initialPositionalOffset = attachedObject.handAttachmentPointTransform.InverseTransformPoint(attachedObject.handAttachmentPointTransform.position + posDiff);
|
|
attachedObject.initialRotationalOffset = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * (attachedObject.handAttachmentPointTransform.rotation * rotDiff);
|
|
}
|
|
else
|
|
{
|
|
attachedObject.initialPositionalOffset = attachedObject.handAttachmentPointTransform.InverseTransformPoint(objectToAttach.transform.position);
|
|
attachedObject.initialRotationalOffset = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * objectToAttach.transform.rotation;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (attachedObject.HasAttachFlag(AttachmentFlags.TurnOnKinematic))
|
|
{
|
|
if (attachedObject.attachedRigidbody != null)
|
|
{
|
|
attachedObject.collisionDetectionMode = attachedObject.attachedRigidbody.collisionDetectionMode;
|
|
if (attachedObject.collisionDetectionMode == CollisionDetectionMode.Continuous)
|
|
attachedObject.attachedRigidbody.collisionDetectionMode = CollisionDetectionMode.Discrete;
|
|
|
|
attachedObject.attachedRigidbody.isKinematic = true;
|
|
}
|
|
}
|
|
|
|
if (attachedObject.HasAttachFlag(AttachmentFlags.TurnOffGravity))
|
|
{
|
|
if (attachedObject.attachedRigidbody != null)
|
|
{
|
|
attachedObject.attachedRigidbody.useGravity = false;
|
|
}
|
|
}
|
|
|
|
if (attachedObject.interactable != null && attachedObject.interactable.attachEaseIn)
|
|
{
|
|
attachedObject.attachedObject.transform.position = attachedObject.easeSourcePosition;
|
|
attachedObject.attachedObject.transform.rotation = attachedObject.easeSourceRotation;
|
|
}
|
|
|
|
attachedObjects.Add(attachedObject);
|
|
|
|
UpdateHovering();
|
|
|
|
if (spewDebugText)
|
|
HandDebugLog("AttachObject " + objectToAttach);
|
|
objectToAttach.SendMessage("OnAttachedToHand", this, SendMessageOptions.DontRequireReceiver);
|
|
}
|
|
|
|
public bool ObjectIsAttached(GameObject go)
|
|
{
|
|
for (int attachedIndex = 0; attachedIndex < attachedObjects.Count; attachedIndex++)
|
|
{
|
|
if (attachedObjects[attachedIndex].attachedObject == go)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void ForceHoverUnlock()
|
|
{
|
|
hoverLocked = false;
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// 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)
|
|
{
|
|
if (spewDebugText)
|
|
HandDebugLog("DetachObject " + objectToDetach);
|
|
|
|
GameObject prevTopObject = currentAttachedObject;
|
|
|
|
|
|
if (attachedObjects[index].interactable != null)
|
|
{
|
|
if (attachedObjects[index].interactable.hideHandOnAttach)
|
|
Show();
|
|
|
|
if (attachedObjects[index].interactable.hideSkeletonOnAttach && mainRenderModel != null && mainRenderModel.displayHandByDefault)
|
|
ShowSkeleton();
|
|
|
|
if (attachedObjects[index].interactable.hideControllerOnAttach && mainRenderModel != null && mainRenderModel.displayControllerByDefault)
|
|
ShowController();
|
|
|
|
if (attachedObjects[index].interactable.handAnimationOnPickup != 0)
|
|
StopAnimation();
|
|
|
|
if (attachedObjects[index].interactable.setRangeOfMotionOnPickup != SkeletalMotionRangeChange.None)
|
|
ResetTemporarySkeletonRangeOfMotion();
|
|
}
|
|
|
|
Transform parentTransform = null;
|
|
if (attachedObjects[index].isParentedToHand)
|
|
{
|
|
if (restoreOriginalParent && (attachedObjects[index].originalParent != null))
|
|
{
|
|
parentTransform = attachedObjects[index].originalParent.transform;
|
|
}
|
|
|
|
if (attachedObjects[index].attachedObject != null)
|
|
{
|
|
attachedObjects[index].attachedObject.transform.parent = parentTransform;
|
|
}
|
|
}
|
|
|
|
if (attachedObjects[index].HasAttachFlag(AttachmentFlags.TurnOnKinematic))
|
|
{
|
|
if (attachedObjects[index].attachedRigidbody != null)
|
|
{
|
|
attachedObjects[index].attachedRigidbody.isKinematic = attachedObjects[index].attachedRigidbodyWasKinematic;
|
|
attachedObjects[index].attachedRigidbody.collisionDetectionMode = attachedObjects[index].collisionDetectionMode;
|
|
}
|
|
}
|
|
|
|
if (attachedObjects[index].HasAttachFlag(AttachmentFlags.TurnOffGravity))
|
|
{
|
|
if (attachedObjects[index].attachedObject != null)
|
|
{
|
|
if (attachedObjects[index].attachedRigidbody != null)
|
|
attachedObjects[index].attachedRigidbody.useGravity = attachedObjects[index].attachedRigidbodyUsedGravity;
|
|
}
|
|
}
|
|
|
|
if (attachedObjects[index].interactable != null && attachedObjects[index].interactable.handFollowTransform && HasSkeleton())
|
|
{
|
|
skeleton.transform.localPosition = Vector3.zero;
|
|
skeleton.transform.localRotation = Quaternion.identity;
|
|
}
|
|
|
|
if (attachedObjects[index].attachedObject != null)
|
|
{
|
|
if (attachedObjects[index].interactable == null || (attachedObjects[index].interactable != null && attachedObjects[index].interactable.isDestroying == false))
|
|
attachedObjects[index].attachedObject.SetActive(true);
|
|
|
|
attachedObjects[index].attachedObject.SendMessage("OnDetachedFromHand", this, SendMessageOptions.DontRequireReceiver);
|
|
}
|
|
|
|
attachedObjects.RemoveAt(index);
|
|
|
|
CleanUpAttachedObjectStack();
|
|
|
|
GameObject newTopObject = currentAttachedObject;
|
|
|
|
hoverLocked = false;
|
|
|
|
|
|
//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();
|
|
|
|
if (mainRenderModel != null)
|
|
mainRenderModel.MatchHandToTransform(mainRenderModel.transform);
|
|
if (hoverhighlightRenderModel != null)
|
|
hoverhighlightRenderModel.MatchHandToTransform(hoverhighlightRenderModel.transform);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// Get the world velocity of the VR Hand.
|
|
//-------------------------------------------------
|
|
public Vector3 GetTrackedObjectVelocity(float timeOffset = 0)
|
|
{
|
|
if (trackedObject == null)
|
|
{
|
|
Vector3 velocityTarget, angularTarget;
|
|
GetUpdatedAttachedVelocities(currentAttachedObjectInfo.Value, out velocityTarget, out angularTarget);
|
|
return velocityTarget;
|
|
}
|
|
|
|
if (isActive)
|
|
{
|
|
if (timeOffset == 0)
|
|
return Player.instance.trackingOriginTransform.TransformVector(trackedObject.GetVelocity());
|
|
else
|
|
{
|
|
Vector3 velocity;
|
|
Vector3 angularVelocity;
|
|
|
|
trackedObject.GetVelocitiesAtTimeOffset(timeOffset, out velocity, out angularVelocity);
|
|
return Player.instance.trackingOriginTransform.TransformVector(velocity);
|
|
}
|
|
}
|
|
|
|
return Vector3.zero;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// Get the world space angular velocity of the VR Hand.
|
|
//-------------------------------------------------
|
|
public Vector3 GetTrackedObjectAngularVelocity(float timeOffset = 0)
|
|
{
|
|
if (trackedObject == null)
|
|
{
|
|
Vector3 velocityTarget, angularTarget;
|
|
GetUpdatedAttachedVelocities(currentAttachedObjectInfo.Value, out velocityTarget, out angularTarget);
|
|
return angularTarget;
|
|
}
|
|
|
|
if (isActive)
|
|
{
|
|
if (timeOffset == 0)
|
|
return Player.instance.trackingOriginTransform.TransformDirection(trackedObject.GetAngularVelocity());
|
|
else
|
|
{
|
|
Vector3 velocity;
|
|
Vector3 angularVelocity;
|
|
|
|
trackedObject.GetVelocitiesAtTimeOffset(timeOffset, out velocity, out angularVelocity);
|
|
return Player.instance.trackingOriginTransform.TransformDirection(angularVelocity);
|
|
}
|
|
}
|
|
|
|
return Vector3.zero;
|
|
}
|
|
|
|
public void GetEstimatedPeakVelocities(out Vector3 velocity, out Vector3 angularVelocity)
|
|
{
|
|
trackedObject.GetEstimatedPeakVelocities(out velocity, out angularVelocity);
|
|
velocity = Player.instance.trackingOriginTransform.TransformVector(velocity);
|
|
angularVelocity = Player.instance.trackingOriginTransform.TransformDirection(angularVelocity);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
private void CleanUpAttachedObjectStack()
|
|
{
|
|
attachedObjects.RemoveAll(l => l.attachedObject == null);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
protected virtual void Awake()
|
|
{
|
|
inputFocusAction = SteamVR_Events.InputFocusAction(OnInputFocus);
|
|
|
|
if (hoverSphereTransform == null)
|
|
hoverSphereTransform = this.transform;
|
|
|
|
if (objectAttachmentPoint == null)
|
|
objectAttachmentPoint = this.transform;
|
|
|
|
applicationLostFocusObject = new GameObject("_application_lost_focus");
|
|
applicationLostFocusObject.transform.parent = transform;
|
|
applicationLostFocusObject.SetActive(false);
|
|
|
|
if (trackedObject == null)
|
|
{
|
|
trackedObject = this.gameObject.GetComponent<SteamVR_Behaviour_Pose>();
|
|
|
|
if (trackedObject != null)
|
|
trackedObject.onTransformUpdatedEvent += OnTransformUpdated;
|
|
}
|
|
}
|
|
|
|
protected virtual void OnDestroy()
|
|
{
|
|
if (trackedObject != null)
|
|
{
|
|
trackedObject.onTransformUpdatedEvent -= OnTransformUpdated;
|
|
}
|
|
}
|
|
|
|
protected virtual void OnTransformUpdated(SteamVR_Behaviour_Pose updatedPose, SteamVR_Input_Sources updatedSource)
|
|
{
|
|
HandFollowUpdate();
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
protected virtual IEnumerator Start()
|
|
{
|
|
// save off player instance
|
|
playerInstance = Player.instance;
|
|
if (!playerInstance)
|
|
{
|
|
Debug.LogError("<b>[SteamVR Interaction]</b> No player instance found in Hand Start()", this);
|
|
}
|
|
|
|
if (this.gameObject.layer == 0)
|
|
Debug.LogWarning("<b>[SteamVR Interaction]</b> Hand is on default layer. This puts unnecessary strain on hover checks as it is always true for hand colliders (which are then ignored).", this);
|
|
else
|
|
hoverLayerMask &= ~(1 << this.gameObject.layer); //ignore self for hovering
|
|
|
|
// 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( "<b>[SteamVR Interaction]</b> Hand - initializing connection routine" );
|
|
|
|
while (true)
|
|
{
|
|
if (isPoseValid)
|
|
{
|
|
InitController();
|
|
break;
|
|
}
|
|
|
|
yield return null;
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
protected virtual void UpdateHovering()
|
|
{
|
|
if ((noSteamVRFallbackCamera == null) && (isActive == false))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (hoverLocked)
|
|
return;
|
|
|
|
if (applicationLostFocusObject.activeSelf)
|
|
return;
|
|
|
|
float closestDistance = float.MaxValue;
|
|
Interactable closestInteractable = null;
|
|
|
|
if (useHoverSphere)
|
|
{
|
|
float scaledHoverRadius = hoverSphereRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(hoverSphereTransform));
|
|
CheckHoveringForTransform(hoverSphereTransform.position, scaledHoverRadius, ref closestDistance, ref closestInteractable, Color.green);
|
|
}
|
|
|
|
if (useControllerHoverComponent && mainRenderModel != null && mainRenderModel.IsControllerVisibile())
|
|
{
|
|
float scaledHoverRadius = controllerHoverRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(this.transform));
|
|
CheckHoveringForTransform(mainRenderModel.GetControllerPosition(controllerHoverComponent), scaledHoverRadius / 2f, ref closestDistance, ref closestInteractable, Color.blue);
|
|
}
|
|
|
|
if (useFingerJointHover && mainRenderModel != null && mainRenderModel.IsHandVisibile())
|
|
{
|
|
float scaledHoverRadius = fingerJointHoverRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(this.transform));
|
|
CheckHoveringForTransform(mainRenderModel.GetBonePosition((int)fingerJointHover), scaledHoverRadius / 2f, ref closestDistance, ref closestInteractable, Color.yellow);
|
|
}
|
|
|
|
// Hover on this one
|
|
hoveringInteractable = closestInteractable;
|
|
}
|
|
|
|
protected virtual bool CheckHoveringForTransform(Vector3 hoverPosition, float hoverRadius, ref float closestDistance, ref Interactable closestInteractable, Color debugColor)
|
|
{
|
|
bool foundCloser = false;
|
|
|
|
// null out old vals
|
|
for (int i = 0; i < overlappingColliders.Length; ++i)
|
|
{
|
|
overlappingColliders[i] = null;
|
|
}
|
|
|
|
int numColliding = Physics.OverlapSphereNonAlloc(hoverPosition, hoverRadius, overlappingColliders, hoverLayerMask.value);
|
|
|
|
if (numColliding >= ColliderArraySize)
|
|
Debug.LogWarning("<b>[SteamVR Interaction]</b> This hand is overlapping the max number of colliders: " + ColliderArraySize + ". Some collisions may be missed. Increase ColliderArraySize on Hand.cs");
|
|
|
|
// DebugVar
|
|
int iActualColliderCount = 0;
|
|
|
|
// Pick the closest hovering
|
|
for (int colliderIndex = 0; colliderIndex < overlappingColliders.Length; colliderIndex++)
|
|
{
|
|
Collider collider = overlappingColliders[colliderIndex];
|
|
|
|
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
|
|
bool hoveringOverAttached = false;
|
|
for (int attachedIndex = 0; attachedIndex < attachedObjects.Count; attachedIndex++)
|
|
{
|
|
if (attachedObjects[attachedIndex].attachedObject == contacting.gameObject)
|
|
{
|
|
hoveringOverAttached = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hoveringOverAttached)
|
|
continue;
|
|
|
|
// Best candidate so far...
|
|
float distance = Vector3.Distance(contacting.transform.position, hoverPosition);
|
|
//float distance = Vector3.Distance(collider.bounds.center, hoverPosition);
|
|
bool lowerPriority = false;
|
|
if (closestInteractable != null)
|
|
{ // compare to closest interactable to check priority
|
|
lowerPriority = contacting.hoverPriority < closestInteractable.hoverPriority;
|
|
}
|
|
bool isCloser = (distance < closestDistance);
|
|
if (isCloser && !lowerPriority)
|
|
{
|
|
closestDistance = distance;
|
|
closestInteractable = contacting;
|
|
foundCloser = true;
|
|
}
|
|
iActualColliderCount++;
|
|
}
|
|
|
|
if (showDebugInteractables && foundCloser)
|
|
{
|
|
Debug.DrawLine(hoverPosition, closestInteractable.transform.position, debugColor, .05f, false);
|
|
}
|
|
|
|
if (iActualColliderCount > 0 && iActualColliderCount != prevOverlappingColliders)
|
|
{
|
|
prevOverlappingColliders = iActualColliderCount;
|
|
|
|
if (spewDebugText)
|
|
HandDebugLog("Found " + iActualColliderCount + " overlapping colliders.");
|
|
}
|
|
|
|
return foundCloser;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
protected virtual 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 (handType == SteamVR_Input_Sources.RightHand)
|
|
{
|
|
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,
|
|
handType.ToString());
|
|
}
|
|
else
|
|
{
|
|
if (debugText != null)
|
|
{
|
|
Destroy(debugText.gameObject);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
protected virtual 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);
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
protected virtual void OnDisable()
|
|
{
|
|
inputFocusAction.enabled = false;
|
|
|
|
CancelInvoke();
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
protected virtual void Update()
|
|
{
|
|
UpdateNoSteamVRFallback();
|
|
|
|
GameObject attachedObject = currentAttachedObject;
|
|
if (attachedObject != null)
|
|
{
|
|
attachedObject.SendMessage("HandAttachedUpdate", this, SendMessageOptions.DontRequireReceiver);
|
|
}
|
|
|
|
if (hoveringInteractable)
|
|
{
|
|
hoveringInteractable.SendMessage("HandHoverUpdate", this, SendMessageOptions.DontRequireReceiver);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true when the hand is currently hovering over the interactable passed in
|
|
/// </summary>
|
|
public bool IsStillHovering(Interactable interactable)
|
|
{
|
|
return hoveringInteractable == interactable;
|
|
}
|
|
|
|
protected virtual void HandFollowUpdate()
|
|
{
|
|
GameObject attachedObject = currentAttachedObject;
|
|
if (attachedObject != null)
|
|
{
|
|
if (currentAttachedObjectInfo.Value.interactable != null)
|
|
{
|
|
SteamVR_Skeleton_PoseSnapshot pose = null;
|
|
|
|
if (currentAttachedObjectInfo.Value.interactable.skeletonPoser != null && HasSkeleton())
|
|
{
|
|
pose = currentAttachedObjectInfo.Value.interactable.skeletonPoser.GetBlendedPose(skeleton);
|
|
}
|
|
|
|
if (currentAttachedObjectInfo.Value.interactable.handFollowTransform)
|
|
{
|
|
Quaternion targetHandRotation;
|
|
Vector3 targetHandPosition;
|
|
|
|
if (pose == null)
|
|
{
|
|
Quaternion offset = Quaternion.Inverse(this.transform.rotation) * currentAttachedObjectInfo.Value.handAttachmentPointTransform.rotation;
|
|
targetHandRotation = currentAttachedObjectInfo.Value.interactable.transform.rotation * Quaternion.Inverse(offset);
|
|
|
|
Vector3 worldOffset = (this.transform.position - currentAttachedObjectInfo.Value.handAttachmentPointTransform.position);
|
|
Quaternion rotationDiff = mainRenderModel.GetHandRotation() * Quaternion.Inverse(this.transform.rotation);
|
|
Vector3 localOffset = rotationDiff * worldOffset;
|
|
targetHandPosition = currentAttachedObjectInfo.Value.interactable.transform.position + localOffset;
|
|
}
|
|
else
|
|
{
|
|
Transform objectT = currentAttachedObjectInfo.Value.attachedObject.transform;
|
|
Vector3 oldItemPos = objectT.position;
|
|
Quaternion oldItemRot = objectT.transform.rotation;
|
|
objectT.position = TargetItemPosition(currentAttachedObjectInfo.Value);
|
|
objectT.rotation = TargetItemRotation(currentAttachedObjectInfo.Value);
|
|
Vector3 localSkelePos = objectT.InverseTransformPoint(transform.position);
|
|
Quaternion localSkeleRot = Quaternion.Inverse(objectT.rotation) * transform.rotation;
|
|
objectT.position = oldItemPos;
|
|
objectT.rotation = oldItemRot;
|
|
|
|
targetHandPosition = objectT.TransformPoint(localSkelePos);
|
|
targetHandRotation = objectT.rotation * localSkeleRot;
|
|
}
|
|
|
|
if (mainRenderModel != null)
|
|
mainRenderModel.SetHandRotation(targetHandRotation);
|
|
if (hoverhighlightRenderModel != null)
|
|
hoverhighlightRenderModel.SetHandRotation(targetHandRotation);
|
|
|
|
if (mainRenderModel != null)
|
|
mainRenderModel.SetHandPosition(targetHandPosition);
|
|
if (hoverhighlightRenderModel != null)
|
|
hoverhighlightRenderModel.SetHandPosition(targetHandPosition);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void FixedUpdate()
|
|
{
|
|
if (currentAttachedObject != null)
|
|
{
|
|
AttachedObject attachedInfo = currentAttachedObjectInfo.Value;
|
|
if (attachedInfo.attachedObject != null)
|
|
{
|
|
if (attachedInfo.HasAttachFlag(AttachmentFlags.VelocityMovement))
|
|
{
|
|
if (attachedInfo.interactable.attachEaseIn == false || attachedInfo.interactable.snapAttachEaseInCompleted)
|
|
UpdateAttachedVelocity(attachedInfo);
|
|
|
|
/*if (attachedInfo.interactable.handFollowTransformPosition)
|
|
{
|
|
skeleton.transform.position = TargetSkeletonPosition(attachedInfo);
|
|
skeleton.transform.rotation = attachedInfo.attachedObject.transform.rotation * attachedInfo.skeletonLockRotation;
|
|
}*/
|
|
}
|
|
else
|
|
{
|
|
if (attachedInfo.HasAttachFlag(AttachmentFlags.ParentToHand))
|
|
{
|
|
attachedInfo.attachedObject.transform.position = TargetItemPosition(attachedInfo);
|
|
attachedInfo.attachedObject.transform.rotation = TargetItemRotation(attachedInfo);
|
|
}
|
|
}
|
|
|
|
|
|
if (attachedInfo.interactable != null && attachedInfo.interactable.attachEaseIn)
|
|
{
|
|
float t = Util.RemapNumberClamped(Time.time, attachedInfo.attachTime, attachedInfo.attachTime + attachedInfo.interactable.snapAttachEaseInTime, 0.0f, 1.0f);
|
|
if (t < 1.0f)
|
|
{
|
|
if (attachedInfo.HasAttachFlag(AttachmentFlags.VelocityMovement))
|
|
{
|
|
attachedInfo.attachedRigidbody.velocity = Vector3.zero;
|
|
attachedInfo.attachedRigidbody.angularVelocity = Vector3.zero;
|
|
}
|
|
t = attachedInfo.interactable.snapAttachEaseInCurve.Evaluate(t);
|
|
attachedInfo.attachedObject.transform.position = Vector3.Lerp(attachedInfo.easeSourcePosition, TargetItemPosition(attachedInfo), t);
|
|
attachedInfo.attachedObject.transform.rotation = Quaternion.Lerp(attachedInfo.easeSourceRotation, TargetItemRotation(attachedInfo), t);
|
|
}
|
|
else if (!attachedInfo.interactable.snapAttachEaseInCompleted)
|
|
{
|
|
attachedInfo.interactable.gameObject.SendMessage("OnThrowableAttachEaseInCompleted", this, SendMessageOptions.DontRequireReceiver);
|
|
attachedInfo.interactable.snapAttachEaseInCompleted = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected const float MaxVelocityChange = 10f;
|
|
protected const float VelocityMagic = 6000f;
|
|
protected const float AngularVelocityMagic = 50f;
|
|
protected const float MaxAngularVelocityChange = 20f;
|
|
|
|
protected void UpdateAttachedVelocity(AttachedObject attachedObjectInfo)
|
|
{
|
|
Vector3 velocityTarget, angularTarget;
|
|
bool success = GetUpdatedAttachedVelocities(attachedObjectInfo, out velocityTarget, out angularTarget);
|
|
if (success)
|
|
{
|
|
float scale = SteamVR_Utils.GetLossyScale(currentAttachedObjectInfo.Value.handAttachmentPointTransform);
|
|
float maxAngularVelocityChange = MaxAngularVelocityChange * scale;
|
|
float maxVelocityChange = MaxVelocityChange * scale;
|
|
|
|
attachedObjectInfo.attachedRigidbody.velocity = Vector3.MoveTowards(attachedObjectInfo.attachedRigidbody.velocity, velocityTarget, maxVelocityChange);
|
|
attachedObjectInfo.attachedRigidbody.angularVelocity = Vector3.MoveTowards(attachedObjectInfo.attachedRigidbody.angularVelocity, angularTarget, maxAngularVelocityChange);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Snap an attached object to its target position and rotation. Good for error correction.
|
|
/// </summary>
|
|
public void ResetAttachedTransform(AttachedObject attachedObject)
|
|
{
|
|
attachedObject.attachedObject.transform.position = TargetItemPosition(attachedObject);
|
|
attachedObject.attachedObject.transform.rotation = TargetItemRotation(attachedObject);
|
|
}
|
|
|
|
protected Vector3 TargetItemPosition(AttachedObject attachedObject)
|
|
{
|
|
if (attachedObject.interactable != null && attachedObject.interactable.skeletonPoser != null && HasSkeleton())
|
|
{
|
|
Vector3 tp = attachedObject.handAttachmentPointTransform.InverseTransformPoint(transform.TransformPoint(attachedObject.interactable.skeletonPoser.GetBlendedPose(skeleton).position));
|
|
//tp.x *= -1;
|
|
return currentAttachedObjectInfo.Value.handAttachmentPointTransform.TransformPoint(tp);
|
|
}
|
|
else
|
|
{
|
|
return currentAttachedObjectInfo.Value.handAttachmentPointTransform.TransformPoint(attachedObject.initialPositionalOffset);
|
|
}
|
|
}
|
|
|
|
protected Quaternion TargetItemRotation(AttachedObject attachedObject)
|
|
{
|
|
if (attachedObject.interactable != null && attachedObject.interactable.skeletonPoser != null && HasSkeleton())
|
|
{
|
|
Quaternion tr = Quaternion.Inverse(attachedObject.handAttachmentPointTransform.rotation) * (transform.rotation * attachedObject.interactable.skeletonPoser.GetBlendedPose(skeleton).rotation);
|
|
return currentAttachedObjectInfo.Value.handAttachmentPointTransform.rotation * tr;
|
|
}
|
|
else
|
|
{
|
|
return currentAttachedObjectInfo.Value.handAttachmentPointTransform.rotation * attachedObject.initialRotationalOffset;
|
|
}
|
|
}
|
|
|
|
protected bool GetUpdatedAttachedVelocities(AttachedObject attachedObjectInfo, out Vector3 velocityTarget, out Vector3 angularTarget)
|
|
{
|
|
bool realNumbers = false;
|
|
|
|
|
|
float velocityMagic = VelocityMagic;
|
|
float angularVelocityMagic = AngularVelocityMagic;
|
|
|
|
Vector3 targetItemPosition = TargetItemPosition(attachedObjectInfo);
|
|
Vector3 positionDelta = (targetItemPosition - attachedObjectInfo.attachedRigidbody.position);
|
|
velocityTarget = (positionDelta * velocityMagic * Time.deltaTime);
|
|
|
|
if (float.IsNaN(velocityTarget.x) == false && float.IsInfinity(velocityTarget.x) == false)
|
|
{
|
|
if (noSteamVRFallbackCamera)
|
|
velocityTarget /= 10; //hacky fix for fallback
|
|
|
|
realNumbers = true;
|
|
}
|
|
else
|
|
velocityTarget = Vector3.zero;
|
|
|
|
|
|
Quaternion targetItemRotation = TargetItemRotation(attachedObjectInfo);
|
|
Quaternion rotationDelta = targetItemRotation * Quaternion.Inverse(attachedObjectInfo.attachedObject.transform.rotation);
|
|
|
|
|
|
float angle;
|
|
Vector3 axis;
|
|
rotationDelta.ToAngleAxis(out angle, out axis);
|
|
|
|
if (angle > 180)
|
|
angle -= 360;
|
|
|
|
if (angle != 0 && float.IsNaN(axis.x) == false && float.IsInfinity(axis.x) == false)
|
|
{
|
|
angularTarget = angle * axis * angularVelocityMagic * Time.deltaTime;
|
|
|
|
if (noSteamVRFallbackCamera)
|
|
angularTarget /= 10; //hacky fix for fallback
|
|
|
|
realNumbers &= true;
|
|
}
|
|
else
|
|
angularTarget = Vector3.zero;
|
|
|
|
return realNumbers;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
protected virtual void OnInputFocus(bool hasFocus)
|
|
{
|
|
if (hasFocus)
|
|
{
|
|
DetachObject(applicationLostFocusObject, true);
|
|
applicationLostFocusObject.SetActive(false);
|
|
UpdateHovering();
|
|
BroadcastMessage("OnParentHandInputFocusAcquired", SendMessageOptions.DontRequireReceiver);
|
|
}
|
|
else
|
|
{
|
|
applicationLostFocusObject.SetActive(true);
|
|
AttachObject(applicationLostFocusObject, GrabTypes.Scripted, AttachmentFlags.ParentToHand);
|
|
BroadcastMessage("OnParentHandInputFocusLost", SendMessageOptions.DontRequireReceiver);
|
|
}
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
protected virtual void OnDrawGizmos()
|
|
{
|
|
if (useHoverSphere && hoverSphereTransform != null)
|
|
{
|
|
Gizmos.color = Color.green;
|
|
float scaledHoverRadius = hoverSphereRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(hoverSphereTransform));
|
|
Gizmos.DrawWireSphere(hoverSphereTransform.position, scaledHoverRadius / 2);
|
|
}
|
|
|
|
if (useControllerHoverComponent && mainRenderModel != null && mainRenderModel.IsControllerVisibile())
|
|
{
|
|
Gizmos.color = Color.blue;
|
|
float scaledHoverRadius = controllerHoverRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(this.transform));
|
|
Gizmos.DrawWireSphere(mainRenderModel.GetControllerPosition(controllerHoverComponent), scaledHoverRadius / 2);
|
|
}
|
|
|
|
if (useFingerJointHover && mainRenderModel != null && mainRenderModel.IsHandVisibile())
|
|
{
|
|
Gizmos.color = Color.yellow;
|
|
float scaledHoverRadius = fingerJointHoverRadius * Mathf.Abs(SteamVR_Utils.GetLossyScale(this.transform));
|
|
Gizmos.DrawWireSphere(mainRenderModel.GetBonePosition((int)fingerJointHover), scaledHoverRadius / 2);
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
private void HandDebugLog(string msg)
|
|
{
|
|
if (spewDebugText)
|
|
{
|
|
Debug.Log("<b>[SteamVR Interaction]</b> Hand (" + this.name + "): " + msg);
|
|
}
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// 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)
|
|
{
|
|
if (spewDebugText)
|
|
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)
|
|
{
|
|
if (spewDebugText)
|
|
HandDebugLog("HoverUnlock " + interactable);
|
|
|
|
if (hoveringInteractable == interactable)
|
|
{
|
|
hoverLocked = false;
|
|
}
|
|
}
|
|
|
|
public void TriggerHapticPulse(ushort microSecondsDuration)
|
|
{
|
|
float seconds = (float)microSecondsDuration / 1000000f;
|
|
hapticAction.Execute(0, seconds, 1f / seconds, 1, handType);
|
|
}
|
|
|
|
public void TriggerHapticPulse(float duration, float frequency, float amplitude)
|
|
{
|
|
hapticAction.Execute(0, duration, frequency, amplitude, handType);
|
|
}
|
|
|
|
public void ShowGrabHint()
|
|
{
|
|
ControllerButtonHints.ShowButtonHint(this, grabGripAction); //todo: assess
|
|
}
|
|
|
|
public void HideGrabHint()
|
|
{
|
|
ControllerButtonHints.HideButtonHint(this, grabGripAction); //todo: assess
|
|
}
|
|
|
|
public void ShowGrabHint(string text)
|
|
{
|
|
ControllerButtonHints.ShowTextHint(this, grabGripAction, text);
|
|
}
|
|
|
|
public GrabTypes GetGrabStarting(GrabTypes explicitType = GrabTypes.None)
|
|
{
|
|
if (explicitType != GrabTypes.None)
|
|
{
|
|
if (noSteamVRFallbackCamera)
|
|
{
|
|
if (Input.GetMouseButtonDown(0))
|
|
return explicitType;
|
|
else
|
|
return GrabTypes.None;
|
|
}
|
|
|
|
if (explicitType == GrabTypes.Pinch && grabPinchAction.GetStateDown(handType))
|
|
return GrabTypes.Pinch;
|
|
if (explicitType == GrabTypes.Grip && grabGripAction.GetStateDown(handType))
|
|
return GrabTypes.Grip;
|
|
}
|
|
else
|
|
{
|
|
if (noSteamVRFallbackCamera)
|
|
{
|
|
if (Input.GetMouseButtonDown(0))
|
|
return GrabTypes.Grip;
|
|
else
|
|
return GrabTypes.None;
|
|
}
|
|
|
|
if (grabPinchAction != null && grabPinchAction.GetStateDown(handType))
|
|
return GrabTypes.Pinch;
|
|
if (grabGripAction != null && grabGripAction.GetStateDown(handType))
|
|
return GrabTypes.Grip;
|
|
}
|
|
|
|
return GrabTypes.None;
|
|
}
|
|
|
|
public GrabTypes GetGrabEnding(GrabTypes explicitType = GrabTypes.None)
|
|
{
|
|
if (explicitType != GrabTypes.None)
|
|
{
|
|
if (noSteamVRFallbackCamera)
|
|
{
|
|
if (Input.GetMouseButtonUp(0))
|
|
return explicitType;
|
|
else
|
|
return GrabTypes.None;
|
|
}
|
|
|
|
if (explicitType == GrabTypes.Pinch && grabPinchAction.GetStateUp(handType))
|
|
return GrabTypes.Pinch;
|
|
if (explicitType == GrabTypes.Grip && grabGripAction.GetStateUp(handType))
|
|
return GrabTypes.Grip;
|
|
}
|
|
else
|
|
{
|
|
if (noSteamVRFallbackCamera)
|
|
{
|
|
if (Input.GetMouseButtonUp(0))
|
|
return GrabTypes.Grip;
|
|
else
|
|
return GrabTypes.None;
|
|
}
|
|
|
|
if (grabPinchAction.GetStateUp(handType))
|
|
return GrabTypes.Pinch;
|
|
if (grabGripAction.GetStateUp(handType))
|
|
return GrabTypes.Grip;
|
|
}
|
|
|
|
return GrabTypes.None;
|
|
}
|
|
|
|
public bool IsGrabEnding(GameObject attachedObject)
|
|
{
|
|
for (int attachedObjectIndex = 0; attachedObjectIndex < attachedObjects.Count; attachedObjectIndex++)
|
|
{
|
|
if (attachedObjects[attachedObjectIndex].attachedObject == attachedObject)
|
|
{
|
|
return IsGrabbingWithType(attachedObjects[attachedObjectIndex].grabbedWithType) == false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool IsGrabbingWithType(GrabTypes type)
|
|
{
|
|
if (noSteamVRFallbackCamera)
|
|
{
|
|
if (Input.GetMouseButton(0))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case GrabTypes.Pinch:
|
|
return grabPinchAction.GetState(handType);
|
|
|
|
case GrabTypes.Grip:
|
|
return grabGripAction.GetState(handType);
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool IsGrabbingWithOppositeType(GrabTypes type)
|
|
{
|
|
if (noSteamVRFallbackCamera)
|
|
{
|
|
if (Input.GetMouseButton(0))
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
switch (type)
|
|
{
|
|
case GrabTypes.Pinch:
|
|
return grabGripAction.GetState(handType);
|
|
|
|
case GrabTypes.Grip:
|
|
return grabPinchAction.GetState(handType);
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public GrabTypes GetBestGrabbingType()
|
|
{
|
|
return GetBestGrabbingType(GrabTypes.None);
|
|
}
|
|
|
|
public GrabTypes GetBestGrabbingType(GrabTypes preferred, bool forcePreference = false)
|
|
{
|
|
if (noSteamVRFallbackCamera)
|
|
{
|
|
if (Input.GetMouseButton(0))
|
|
return preferred;
|
|
else
|
|
return GrabTypes.None;
|
|
}
|
|
|
|
if (preferred == GrabTypes.Pinch)
|
|
{
|
|
if (grabPinchAction.GetState(handType))
|
|
return GrabTypes.Pinch;
|
|
else if (forcePreference)
|
|
return GrabTypes.None;
|
|
}
|
|
if (preferred == GrabTypes.Grip)
|
|
{
|
|
if (grabGripAction.GetState(handType))
|
|
return GrabTypes.Grip;
|
|
else if (forcePreference)
|
|
return GrabTypes.None;
|
|
}
|
|
|
|
if (grabPinchAction.GetState(handType))
|
|
return GrabTypes.Pinch;
|
|
if (grabGripAction.GetState(handType))
|
|
return GrabTypes.Grip;
|
|
|
|
return GrabTypes.None;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
private void InitController()
|
|
{
|
|
if (spewDebugText)
|
|
HandDebugLog("Hand " + name + " connected with type " + handType.ToString());
|
|
|
|
bool hadOldRendermodel = mainRenderModel != null;
|
|
EVRSkeletalMotionRange oldRM_rom = EVRSkeletalMotionRange.WithController;
|
|
if (hadOldRendermodel)
|
|
oldRM_rom = mainRenderModel.GetSkeletonRangeOfMotion;
|
|
|
|
|
|
foreach (RenderModel r in renderModels)
|
|
{
|
|
if (r != null)
|
|
Destroy(r.gameObject);
|
|
}
|
|
|
|
renderModels.Clear();
|
|
|
|
GameObject renderModelInstance = GameObject.Instantiate(renderModelPrefab);
|
|
renderModelInstance.layer = gameObject.layer;
|
|
renderModelInstance.tag = gameObject.tag;
|
|
renderModelInstance.transform.parent = this.transform;
|
|
renderModelInstance.transform.localPosition = Vector3.zero;
|
|
renderModelInstance.transform.localRotation = Quaternion.identity;
|
|
renderModelInstance.transform.localScale = renderModelPrefab.transform.localScale;
|
|
|
|
//TriggerHapticPulse(800); //pulse on controller init
|
|
|
|
int deviceIndex = trackedObject.GetDeviceIndex();
|
|
|
|
mainRenderModel = renderModelInstance.GetComponent<RenderModel>();
|
|
renderModels.Add(mainRenderModel);
|
|
|
|
if (hadOldRendermodel)
|
|
mainRenderModel.SetSkeletonRangeOfMotion(oldRM_rom);
|
|
|
|
this.BroadcastMessage("SetInputSource", handType, SendMessageOptions.DontRequireReceiver); // let child objects know we've initialized
|
|
this.BroadcastMessage("OnHandInitialized", deviceIndex, SendMessageOptions.DontRequireReceiver); // let child objects know we've initialized
|
|
}
|
|
|
|
public void SetRenderModel(GameObject prefab)
|
|
{
|
|
renderModelPrefab = prefab;
|
|
|
|
if (mainRenderModel != null && isPoseValid)
|
|
InitController();
|
|
}
|
|
|
|
public void SetHoverRenderModel(RenderModel hoverRenderModel)
|
|
{
|
|
hoverhighlightRenderModel = hoverRenderModel;
|
|
renderModels.Add(hoverRenderModel);
|
|
}
|
|
|
|
public int GetDeviceIndex()
|
|
{
|
|
return trackedObject.GetDeviceIndex();
|
|
}
|
|
}
|
|
|
|
|
|
[System.Serializable]
|
|
public class HandEvent : UnityEvent<Hand> { }
|
|
} |