void Update()
    {
        hand = FingoMain.Instance.GetHand(handType);

        // Track hand lost and recover time, as well as if palm is currently inside camera view.
        if (handVisibilityTracker != null)
        {
            handLostTime    = handVisibilityTracker.HandLostTime;
            handRecoverTime = handVisibilityTracker.HandRecoverTime;
            handIsLost      = handVisibilityTracker.HandIsLost;
            handOnscreen    = handVisibilityTracker.PalmOnScreen();
        }

        if (hand.IsDetected())
        {
            // Update data even if hand is currently outside camera view (but still detected)
            UpdateGrabTrackData();

            // Update grab states if and only if hand is currently onscreen
            if (handOnscreen)
            {
                UpdateGrabState();
            }

            // for calculating throw velocity
            if (velocityCalculator != null && grabState == GrabState.ObjectInHold)
            {
                Vector3 palmPos = hand.GetPalmPosition();
                velocityCalculator.UpdatePositionData(palmPos, handLostTime, handRecoverTime);
            }
        }

        if (handIsLost && !handIsLostLastFrame) // lost hand detection this frame
        {
            if (activeObject != null)
            {
                activeObject.SetOutlineColorTransparency(0);
            }
        }
        handIsLostLastFrame = handIsLost;
    }
    void UpdateGrabState()
    {
        GestureName currentGesture = hand.GetGestureName();

        bool isClosingFingers = (bendingAngularSpeed > anguarSpeedThresh);
        bool isOpeningFingers = (bendingAngularSpeed < -anguarSpeedThresh);

        if (activeObject != null && activeObject.IsAvailableForGrab() == false &&
            grabState != GrabState.ObjectInHold) // grabbed by the other hand
        {
            activeObject = null;
            grabState    = GrabState.ObjectOutOfReach;
        }

        if ((grabState == GrabState.ObjectOutOfReach) ||
            (grabState == GrabState.ObjectInReach && !isClosingFingers) ||
            (grabState == GrabState.ObjectReleased))
        {
            // Iterate all grabbable objects, find the closest available one.
            GrabbaleObject closestAvailableObj = null;
            float          minDistance         = 1000f;
            foreach (GrabbaleObject obj in GrabbaleObject.instances)
            {
                if (obj.IsAvailableForGrab())
                {
                    float distance = Vector3.Distance(this.transform.position, obj.transform.position);
                    if (distance < minDistance)
                    {
                        minDistance         = distance;
                        closestAvailableObj = obj;
                    }
                }
            }

            // Mark the closest available object as active
            activeObject = closestAvailableObj;

            // Check if it is range.
            bool inReach = (minDistance < grabEnableDistance);

            if (activeObject != null && (currentGesture == GestureName.Grab || currentGesture == GestureName.Palm ||
                                         currentBendingAngle < grabEnableAngle))
            {
                // To give user visual feedbacks, update the outline color on the active object
                // based on its current distance to the hand.
                activeObject.UpdateOutlineColorBasedOnDistance(minDistance);

                // Reset the outline color (fully transparent) on the last active object if it was different
                if (prevActiveObject != null && prevActiveObject != activeObject)
                {
                    prevActiveObject.SetOutlineColorTransparency(0);
                }

                // If we are close enough to grab the currently active object
                if (inReach && (grabState != GrabState.ObjectInReach || activeObject != prevActiveObject))
                {
                    activeObject.OnGetInReach.Invoke();
                    grabState = GrabState.ObjectInReach;
                }
            }

            // Note, we want to ignore movement (which can cause ObjectInReach state to change to ObjectOutOfReach)
            // if user is already in the middle of grabbing the object
            if (!inReach && !(grabState == GrabState.ObjectInReach && isClosingFingers))
            {
                if (grabState != GrabState.ObjectOutOfReach || activeObject != prevActiveObject)
                {
                    if (activeObject != null)
                    {
                        activeObject.OnOutOfReach.Invoke();
                    }

                    grabState = GrabState.ObjectOutOfReach;
                }
            }
        }

        if (grabState == GrabState.ObjectInReach)
        {
            if ((currentBendingAngle > grabStartAngle || currentGesture == GestureName.Fist) && isClosingFingers)
            {
                // To give user visual feedbacks, make the hand transparent.
                if (meshHandRenderMgr != null)
                {
                    meshHandRenderMgr.DisableHandOutline();
                    meshHandRenderMgr.FadeOut();
                }

                if (activeObject != null)
                {
                    activeObject.OnGrab.Invoke();
                    // Save the parent transform
                    grabbedObjectParent                  = activeObject.transform.parent;
                    activeObject.transform.parent        = this.transform;
                    activeObject.transform.localPosition = Vector3.zero;
                    // Disable the object for grab
                    activeObject.SetAvailableForGrab(false);
                }
                grabState = GrabState.ObjectInHold;
            }
        }

        if (grabState == GrabState.ObjectInHold)
        {
            if (currentBendingAngle < grabEndAngle && isOpeningFingers)
            {
                // To give user visual feedbacks, make the hand opaque (default).
                if (meshHandRenderMgr != null)
                {
                    meshHandRenderMgr.FadeIn();
                }

                if (activeObject != null)
                {
                    activeObject.OnRelease.Invoke();

                    Vector3 velocity = (velocityCalculator != null) ? velocityCalculator.CalculateVelocity() : Vector3.zero;
                    if (Vector3.Magnitude(velocity) > 0)
                    {
                        activeObject.OnThrow.Invoke(velocity);
                        // Enable the object for grab later
                        activeObject.SetAvailableForGrab(true, activeObject.FlyBackTime());
                    }
                    else
                    {
                        // Enable the object for grab right away
                        activeObject.SetAvailableForGrab(true, 0);
                    }
                    // Restore the parent transform
                    activeObject.transform.parent = grabbedObjectParent;
                }

                grabState = GrabState.ObjectReleased;
            }
        }

        //Debug.Log("Grab state = " + grabState);


        // To give user visual feedbacks, highlight hand outline if it is hovering an object, otherwise disable outline.
        bool isHovering = (grabState == GrabState.ObjectInReach);

        if (meshHandRenderMgr != null)
        {
            if (isHovering && !isHoveringLastFrame)
            {
                meshHandRenderMgr.EnableHandOutline();
            }
            else if (!isHovering && isHoveringLastFrame)
            {
                meshHandRenderMgr.DisableHandOutline();
            }
        }
        isHoveringLastFrame = isHovering;

        prevActiveObject = activeObject;
    }