//======= Copyright (c) Valve Corporation, All rights reserved. =============== // // Purpose: Basic throwable object // //============================================================================= using UnityEngine; using UnityEngine.Events; using System.Collections; namespace Valve.VR.InteractionSystem { //------------------------------------------------------------------------- [RequireComponent( typeof( Interactable ) )] [RequireComponent( typeof( Rigidbody ) )] public class Throwable : MonoBehaviour { [EnumFlags] [Tooltip( "The flags used to attach this object to the hand." )] public Hand.AttachmentFlags attachmentFlags = Hand.AttachmentFlags.ParentToHand | Hand.AttachmentFlags.DetachFromOtherHand | Hand.AttachmentFlags.TurnOnKinematic; [Tooltip("The local point which acts as a positional and rotational offset to use while held")] public Transform attachmentOffset; [Tooltip( "How fast must this object be moving to attach due to a trigger hold instead of a trigger press? (-1 to disable)" )] public float catchingSpeedThreshold = -1; public ReleaseStyle releaseVelocityStyle = ReleaseStyle.GetFromHand; [Tooltip("The time offset used when releasing the object with the RawFromHand option")] public float releaseVelocityTimeOffset = -0.011f; public float scaleReleaseVelocity = 1.1f; [Tooltip("The release velocity magnitude representing the end of the scale release velocity curve. (-1 to disable)")] public float scaleReleaseVelocityThreshold = -1.0f; [Tooltip("Use this curve to ease into the scaled release velocity based on the magnitude of the measured release velocity. This allows greater differentiation between a drop, toss, and throw.")] public AnimationCurve scaleReleaseVelocityCurve = AnimationCurve.EaseInOut(0.0f, 0.1f, 1.0f, 1.0f); [Tooltip( "When detaching the object, should it return to its original parent?" )] public bool restoreOriginalParent = false; protected VelocityEstimator velocityEstimator; protected bool attached = false; protected float attachTime; protected Vector3 attachPosition; protected Quaternion attachRotation; protected Transform attachEaseInTransform; public UnityEvent onPickUp; public UnityEvent onDetachFromHand; public HandEvent onHeldUpdate; protected RigidbodyInterpolation hadInterpolation = RigidbodyInterpolation.None; protected new Rigidbody rigidbody; [HideInInspector] public Interactable interactable; //------------------------------------------------- protected virtual void Awake() { velocityEstimator = GetComponent(); interactable = GetComponent(); rigidbody = GetComponent(); rigidbody.maxAngularVelocity = 50.0f; if(attachmentOffset != null) { // remove? //interactable.handFollowTransform = attachmentOffset; } } //------------------------------------------------- protected virtual void OnHandHoverBegin( Hand hand ) { bool showHint = false; // "Catch" the throwable by holding down the interaction button instead of pressing it. // Only do this if the throwable is moving faster than the prescribed threshold speed, // and if it isn't attached to another hand if ( !attached && catchingSpeedThreshold != -1) { float catchingThreshold = catchingSpeedThreshold * SteamVR_Utils.GetLossyScale(Player.instance.trackingOriginTransform); GrabTypes bestGrabType = hand.GetBestGrabbingType(); if ( bestGrabType != GrabTypes.None ) { if (rigidbody.velocity.magnitude >= catchingThreshold) { hand.AttachObject( gameObject, bestGrabType, attachmentFlags ); showHint = false; } } } if ( showHint ) { hand.ShowGrabHint(); } } //------------------------------------------------- protected virtual void OnHandHoverEnd( Hand hand ) { hand.HideGrabHint(); } //------------------------------------------------- protected virtual void HandHoverUpdate( Hand hand ) { GrabTypes startingGrabType = hand.GetGrabStarting(); if (startingGrabType != GrabTypes.None) { hand.AttachObject( gameObject, startingGrabType, attachmentFlags, attachmentOffset ); hand.HideGrabHint(); } } //------------------------------------------------- protected virtual void OnAttachedToHand( Hand hand ) { //Debug.Log("[SteamVR Interaction] Pickup: " + hand.GetGrabStarting().ToString()); hadInterpolation = this.rigidbody.interpolation; attached = true; onPickUp.Invoke(); hand.HoverLock( null ); rigidbody.interpolation = RigidbodyInterpolation.None; if (velocityEstimator != null) velocityEstimator.BeginEstimatingVelocity(); attachTime = Time.time; attachPosition = transform.position; attachRotation = transform.rotation; } //------------------------------------------------- protected virtual void OnDetachedFromHand(Hand hand) { attached = false; onDetachFromHand.Invoke(); hand.HoverUnlock(null); rigidbody.interpolation = hadInterpolation; Vector3 velocity; Vector3 angularVelocity; GetReleaseVelocities(hand, out velocity, out angularVelocity); rigidbody.velocity = velocity; rigidbody.angularVelocity = angularVelocity; } public virtual void GetReleaseVelocities(Hand hand, out Vector3 velocity, out Vector3 angularVelocity) { if (hand.noSteamVRFallbackCamera && releaseVelocityStyle != ReleaseStyle.NoChange) releaseVelocityStyle = ReleaseStyle.ShortEstimation; // only type that works with fallback hand is short estimation. switch (releaseVelocityStyle) { case ReleaseStyle.ShortEstimation: if (velocityEstimator != null) { velocityEstimator.FinishEstimatingVelocity(); velocity = velocityEstimator.GetVelocityEstimate(); angularVelocity = velocityEstimator.GetAngularVelocityEstimate(); } else { Debug.LogWarning("[SteamVR Interaction System] Throwable: No Velocity Estimator component on object but release style set to short estimation. Please add one or change the release style."); velocity = rigidbody.velocity; angularVelocity = rigidbody.angularVelocity; } break; case ReleaseStyle.AdvancedEstimation: hand.GetEstimatedPeakVelocities(out velocity, out angularVelocity); break; case ReleaseStyle.GetFromHand: velocity = hand.GetTrackedObjectVelocity(releaseVelocityTimeOffset); angularVelocity = hand.GetTrackedObjectAngularVelocity(releaseVelocityTimeOffset); break; default: case ReleaseStyle.NoChange: velocity = rigidbody.velocity; angularVelocity = rigidbody.angularVelocity; break; } if (releaseVelocityStyle != ReleaseStyle.NoChange) { float scaleFactor = 1.0f; if (scaleReleaseVelocityThreshold > 0) { scaleFactor = Mathf.Clamp01(scaleReleaseVelocityCurve.Evaluate(velocity.magnitude / scaleReleaseVelocityThreshold)); } velocity *= (scaleFactor * scaleReleaseVelocity); } } //------------------------------------------------- protected virtual void HandAttachedUpdate(Hand hand) { if (hand.IsGrabEnding(this.gameObject)) { hand.DetachObject(gameObject, restoreOriginalParent); // Uncomment to detach ourselves late in the frame. // This is so that any vehicles the player is attached to // have a chance to finish updating themselves. // If we detach now, our position could be behind what it // will be at the end of the frame, and the object may appear // to teleport behind the hand when the player releases it. //StartCoroutine( LateDetach( hand ) ); } if (onHeldUpdate != null) onHeldUpdate.Invoke(hand); } //------------------------------------------------- protected virtual IEnumerator LateDetach( Hand hand ) { yield return new WaitForEndOfFrame(); hand.DetachObject( gameObject, restoreOriginalParent ); } //------------------------------------------------- protected virtual void OnHandFocusAcquired( Hand hand ) { gameObject.SetActive( true ); if (velocityEstimator != null) velocityEstimator.BeginEstimatingVelocity(); } //------------------------------------------------- protected virtual void OnHandFocusLost( Hand hand ) { gameObject.SetActive( false ); if (velocityEstimator != null) velocityEstimator.FinishEstimatingVelocity(); } } public enum ReleaseStyle { NoChange, GetFromHand, ShortEstimation, AdvancedEstimation, } }