Newer
Older
HoloAnatomy / Assets / HoloToolkit-Examples / MotionControllers-GrabMechanics / Scripts / BaseGrabbable.cs
SURFACEBOOK2\jackwynne on 25 May 2018 8 KB v1
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

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

namespace HoloToolkit.Unity.InputModule.Examples.Grabbables
{
    /// <summary>
    /// //Intended Usage//
    /// Attach a "grabbable_x" script (a script that inherits from this) to any object that is meant to be grabbed
    /// create more specific grab behavior by adding additional scripts/components to the game object, such as scalableObject, rotatableObject, throwableObject 
    /// </summary>
    public abstract class BaseGrabbable : MonoBehaviour
    {
        public event Action<BaseGrabbable> OnGrabStateChange;
        public event Action<BaseGrabbable> OnContactStateChange;
        public event Action<BaseGrabbable> OnGrabbed;
        public event Action<BaseGrabbable> OnReleased;

        public BaseGrabber GrabberPrimary
        {
            get
            {
                return activeGrabbers.Count > 0 ? activeGrabbers[activeGrabbers.Count - 1] : null;
            }
        }

        public BaseGrabber[] ActiveGrabbers
        {
            get
            {
                return activeGrabbers.ToArray();
            }
        }

        public Vector3 GrabPoint
        {
            get
            {
                return grabSpot != null ? grabSpot.position : transform.position;
            }
        }

        /// <summary>
        /// Changes based on how many grabbers are grabbing this object
        /// </summary>
        public GrabStateEnum GrabState
        {
            get
            {
                if (activeGrabbers.Count > 1)
                {
                    return GrabStateEnum.Multi;
                }

                return activeGrabbers.Count > 0 ? GrabStateEnum.Single : GrabStateEnum.Inactive;
            }
        }

        /// <summary>
        /// Changes based on how many grabbers are intersecting with this object
        /// </summary>
        public GrabStateEnum ContactState
        {
            get
            {
                if (availableGrabbers.Count > 1)
                    return GrabStateEnum.Multi;
                else if (availableGrabbers.Count > 0)
                    return GrabStateEnum.Single;
                else
                    return GrabStateEnum.Inactive;
            }
        }

        /// <summary>
        /// Grabbers that could potentially grab this object
        /// This list is maintained by the grabbers
        /// </summary>
        protected HashSet<BaseGrabber> availableGrabbers = new HashSet<BaseGrabber>();

        /// <summary>
        /// Grabbers that are currently grabbing this object
        /// The top-most grabber is the primary grabber
        /// </summary>
        protected List<BaseGrabber> activeGrabbers = new List<BaseGrabber>();

        //left protected unless we have the occasion to use them publicly, then switch to public access
        [SerializeField]
        protected Transform grabSpot;

        [SerializeField]
        protected GrabStyleEnum grabStyle = GrabStyleEnum.Exclusive;

        private GrabStateEnum prevGrabState = GrabStateEnum.Inactive;
        private GrabStateEnum prevContactState = GrabStateEnum.Inactive;
        private Vector3 velocity;
        private Vector3 averageVelocity;
        private Vector3 previousVel;

        public virtual bool TryGrabWith(BaseGrabber grabber)
        {
            // TODO error checking, multi-grab checking
            if (GrabState != GrabStateEnum.Inactive)
            {
                switch (grabStyle)
                {
                    case GrabStyleEnum.Exclusive:
                        // Try to transfer ownership of grabbed object
                        BaseGrabber primary = GrabberPrimary;
                        if (GrabberPrimary.CanTransferOwnershipTo(this, grabber))
                        {
                            // Remove from grabbable list and detach
                            activeGrabbers.Remove(primary);
                            DetachFromGrabber(primary);
                        }
                        else
                        {
                            // If we can't, it's a no-go
                            return false;
                        }
                        break;
                    case GrabStyleEnum.Multi:
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }

            StartGrab(grabber);
            return true;
        }

        /// <summary>
        /// Adds a grabber object to the list of available grabbers
        /// </summary>
        public void AddContact(BaseGrabber availableObject)
        {
            availableGrabbers.Add(availableObject);
        }

        /// <summary>
        /// Removes a grabber object from the list of available grabbers
        /// </summary>
        public void RemoveContact(BaseGrabber availableObject)
        {
            availableGrabbers.Remove(availableObject);
        }

        // The next three functions provide basic behavior. Extend from this base script in order to provide more specific functionality.

        protected virtual void AttachToGrabber(BaseGrabber grabber)
        {
            // By default this does nothing
            // In most cases this will parent or create a joint
        }

        protected virtual void DetachFromGrabber(BaseGrabber grabber)
        {
            // By default this does nothing
            // In most cases this will un-parent or destroy a joint
        }

        protected virtual void StartGrab(BaseGrabber grabber)
        {
            Debug.Log("Start grab");
            if (GrabState == GrabStateEnum.Inactive)
            {
                Debug.Log("State is inactive");
                // If we're not already updating our grab state, start now
                activeGrabbers.Add(grabber);
                StartCoroutine(StayGrab());
            }
            else
            {
                Debug.Log("State is not inactive");
                // Otherwise just push the grabber
                activeGrabbers.Add(grabber);
            }

            // Attach ourselves to this grabber
            AttachToGrabber(grabber);
            if (OnGrabbed != null)
            {
                OnGrabbed(this);
            }
        }

        /// <summary>
        /// As long as the grabber script (usually attached to the controller, but not always) reports more than one grabber,
        /// we stay inside of StayGrab. 
        /// </summary>
        protected virtual IEnumerator StayGrab()
        {
            yield return null;

            // While grabbers are grabbing
            while (GrabState != GrabStateEnum.Inactive)
            {
                // Call on grab stay in case this grabbable wants to update itself
                OnGrabStay();
                for (int i = activeGrabbers.Count - 1; i >= 0; i--)
                {
                    if (activeGrabbers[i] == null || !activeGrabbers[i].IsGrabbing(this))
                    {
                        Debug.Log("no longer being grabbed by active grabber");
                        if (activeGrabbers[i] != null)
                        {
                            DetachFromGrabber(activeGrabbers[i]);
                        }

                        activeGrabbers.RemoveAt(i);
                    }
                }
                yield return null;
            }

            EndGrab();
        }
        /// <summary>
        /// Grab end fires off a GrabEnded event, but also cleans up some of the variables associated with an active grab, such
        /// as which grabber was grabbing this object and so forth. 
        /// </summary>
        protected virtual void EndGrab()
        {
            if (OnReleased != null)
            {
                OnReleased(this);
            }
        }

        /// <summary>
        /// Called every frame while StayGrab is active
        /// </summary>
        protected virtual void OnGrabStay()
        {
        }

        protected virtual void Start()
        {
        }

        protected virtual void Update()
        {
            if (prevGrabState != GrabState && OnGrabStateChange != null)
            {
                Debug.Log("Calling on grab change in grabbable");
                OnGrabStateChange(this);
            }

            if (prevContactState != ContactState && OnContactStateChange != null)
            {
                Debug.Log("Calling on contact change in grabbable");
                OnContactStateChange(this);
            }

            prevGrabState = GrabState;
            prevContactState = ContactState;
        }
    }
}