//-------------------------------------------------
        private bool PhysicsDetach( IF_VR_Steam_Hand hand )
        {
            int i = holdingHands.IndexOf( hand );

            if ( i != -1 )
            {
                // Detach this object from the hand
                holdingHands[i].DetachObject( this.gameObject, false );

                // Allow the hand to do other things
                holdingHands[i].HoverUnlock( null );

                // Delete any existing joints from the hand
                if ( attachMode == AttachMode.FixedJoint )
                {
                    Destroy( holdingHands[i].GetComponent<FixedJoint>() );
                }

                IF_VR_Steam_Util.FastRemove( holdingHands, i );
                IF_VR_Steam_Util.FastRemove( holdingBodies, i );
                IF_VR_Steam_Util.FastRemove( holdingPoints, i );

                return true;
            }

            return false;
        }
        private void OnCollisionEnter(Collision collision)
        {
            bool touchingDynamic = false;

            if (collision.rigidbody != null)
            {
                if (collision.rigidbody.isKinematic == false)
                {
                    touchingDynamic = true;
                }
            }

            // low friction if touching static object, high friction if touching dynamic
            SetPhysicMaterial(touchingDynamic ? physicMaterial_highfriction : physicMaterial_lowfriction);



            float energy = collision.relativeVelocity.magnitude;

            if (energy > minCollisionEnergy && Time.time - lastCollisionHapticsTime > minCollisionHapticsTime)
            {
                lastCollisionHapticsTime = Time.time;

                float intensity = IF_VR_Steam_Util.RemapNumber(energy, minCollisionEnergy, maxCollisionEnergy, 0.3f, 1.0f);
                float length    = IF_VR_Steam_Util.RemapNumber(energy, minCollisionEnergy, maxCollisionEnergy, 0.0f, 0.06f);

                hand.hand.TriggerHapticPulse(length, 100, intensity);
            }
        }
        //-------------------------------------------------
        void Update()
        {
            float value = linearMapping.value;

            //No need to set the blend if our value hasn't changed.
            if (value != lastValue)
            {
                float blendValue = IF_VR_Steam_Util.RemapNumberClamped(value, 0f, 1f, 1f, 100f);
                skinnedMesh.SetBlendShapeWeight(0, blendValue);
            }

            lastValue = value;
        }
        //-------------------------------------------------
        public void Play()
        {
            if (thisAudioSource != null && thisAudioSource.isActiveAndEnabled && !IF_VR_Steam_Util.IsNullOrEmpty(waveFiles))
            {
                //randomly apply a volume between the volume min max
                thisAudioSource.volume = Random.Range(volMin, volMax);

                //randomly apply a pitch between the pitch min max
                thisAudioSource.pitch = Random.Range(pitchMin, pitchMax);

                // play the sound
                thisAudioSource.PlayOneShot(waveFiles[Random.Range(0, waveFiles.Length)]);
            }
        }
        //-------------------------------------------------
        private void PhysicsAttach( IF_VR_Steam_Hand hand, IF_VR_Steam_GrabTypes startingGrabType )
        {
            PhysicsDetach( hand );

            Rigidbody holdingBody = null;
            Vector3 holdingPoint = Vector3.zero;

            // The hand should grab onto the nearest rigid body
            float closestDistance = float.MaxValue;
            for ( int i = 0; i < rigidBodies.Count; i++ )
            {
                float distance = Vector3.Distance( rigidBodies[i].worldCenterOfMass, hand.transform.position );
                if ( distance < closestDistance )
                {
                    holdingBody = rigidBodies[i];
                    closestDistance = distance;
                }
            }

            // Couldn't grab onto a body
            if ( holdingBody == null )
                return;

            // Create a fixed joint from the hand to the holding body
            if ( attachMode == AttachMode.FixedJoint )
            {
                Rigidbody handRigidbody = IF_VR_Steam_Util.FindOrAddComponent<Rigidbody>( hand.gameObject );
                handRigidbody.isKinematic = true;

                FixedJoint handJoint = hand.gameObject.AddComponent<FixedJoint>();
                handJoint.connectedBody = holdingBody;
            }

            // Don't let the hand interact with other things while it's holding us
            hand.HoverLock( null );

            // Affix this point
            Vector3 offset = hand.transform.position - holdingBody.worldCenterOfMass;
            offset = Mathf.Min( offset.magnitude, 1.0f ) * offset.normalized;
            holdingPoint = holdingBody.transform.InverseTransformPoint( holdingBody.worldCenterOfMass + offset );

            hand.AttachObject( this.gameObject, startingGrabType, attachmentFlags );

            // Update holding list
            holdingHands.Add( hand );
            holdingBodies.Add( holdingBody );
            holdingPoints.Add( holdingPoint );
        }
        //-------------------------------------------------
        private IEnumerator HapticPulses(IF_VR_Steam_Hand hand, float flMagnitude, int nCount)
        {
            if (hand != null)
            {
                int nRangeMax = (int)IF_VR_Steam_Util.RemapNumberClamped(flMagnitude, 0.0f, 1.0f, 100.0f, 900.0f);
                nCount = Mathf.Clamp(nCount, 1, 10);

                //float hapticDuration = nRangeMax * nCount;

                //hand.TriggerHapticPulse(hapticDuration, nRangeMax, flMagnitude);

                for (ushort i = 0; i < nCount; ++i)
                {
                    ushort duration = (ushort)Random.Range(100, nRangeMax);
                    hand.TriggerHapticPulse(duration);
                    yield return(new WaitForSeconds(.01f));
                }
            }
        }
        //-------------------------------------------------
        // Computes the angle to rotate the game object based on the change in the transform
        //-------------------------------------------------
        private void ComputeAngle(IF_VR_Steam_Hand hand)
        {
            Vector3 toHandProjected = ComputeToTransformProjected(hand.hoverSphereTransform);

            if (!toHandProjected.Equals(lastHandProjected))
            {
                float absAngleDelta = Vector3.Angle(lastHandProjected, toHandProjected);

                if (absAngleDelta > 0.0f)
                {
                    if (frozen)
                    {
                        float frozenSqDist = (hand.hoverSphereTransform.position - frozenHandWorldPos).sqrMagnitude;
                        if (frozenSqDist > frozenSqDistanceMinMaxThreshold.x)
                        {
                            outAngle = frozenAngle + Random.Range(-1.0f, 1.0f);

                            float magnitude = IF_VR_Steam_Util.RemapNumberClamped(frozenSqDist, frozenSqDistanceMinMaxThreshold.x, frozenSqDistanceMinMaxThreshold.y, 0.0f, 1.0f);
                            if (magnitude > 0)
                            {
                                StartCoroutine(HapticPulses(hand, magnitude, 10));
                            }
                            else
                            {
                                StartCoroutine(HapticPulses(hand, 0.5f, 10));
                            }

                            if (frozenSqDist >= frozenSqDistanceMinMaxThreshold.y)
                            {
                                onFrozenDistanceThreshold.Invoke();
                            }
                        }
                    }
                    else
                    {
                        Vector3 cross = Vector3.Cross(lastHandProjected, toHandProjected).normalized;
                        float   dot   = Vector3.Dot(worldPlaneNormal, cross);

                        float signedAngleDelta = absAngleDelta;

                        if (dot < 0.0f)
                        {
                            signedAngleDelta = -signedAngleDelta;
                        }

                        if (limited)
                        {
                            float angleTmp = Mathf.Clamp(outAngle + signedAngleDelta, minAngle, maxAngle);

                            if (outAngle == minAngle)
                            {
                                if (angleTmp > minAngle && absAngleDelta < minMaxAngularThreshold)
                                {
                                    outAngle          = angleTmp;
                                    lastHandProjected = toHandProjected;
                                }
                            }
                            else if (outAngle == maxAngle)
                            {
                                if (angleTmp < maxAngle && absAngleDelta < minMaxAngularThreshold)
                                {
                                    outAngle          = angleTmp;
                                    lastHandProjected = toHandProjected;
                                }
                            }
                            else if (angleTmp == minAngle)
                            {
                                outAngle          = angleTmp;
                                lastHandProjected = toHandProjected;
                                onMinAngle.Invoke();
                                if (freezeOnMin)
                                {
                                    Freeze(hand);
                                }
                            }
                            else if (angleTmp == maxAngle)
                            {
                                outAngle          = angleTmp;
                                lastHandProjected = toHandProjected;
                                onMaxAngle.Invoke();
                                if (freezeOnMax)
                                {
                                    Freeze(hand);
                                }
                            }
                            else
                            {
                                outAngle          = angleTmp;
                                lastHandProjected = toHandProjected;
                            }
                        }
                        else
                        {
                            outAngle         += signedAngleDelta;
                            lastHandProjected = toHandProjected;
                        }
                    }
                }
            }
        }