using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Autohand { public class HandFingerTouch : MonoBehaviour { [Tooltip("Reference to the Hand component")] public Hand hand; [Tooltip("Reference to the Index Finger component")] public Finger index; [Tooltip("Reference to the Middle Finger component")] public Finger middle; [Tooltip("Reference to the Ring Finger component")] public Finger ring; [Tooltip("Reference to the Pinky Finger component")] public Finger pinky; [Tooltip("Reference to the Thumb component")] public Finger thumb; [Tooltip("Layer mask for determining what objects the fingers can interact with")] public LayerMask touchMask; [Tooltip("Speed at which finger offset adjusts - These default settings are tuned for the default hands physics settings and results might vary based on hands set follow speed and rigidbody settings\"")] public float offsetSpeed = 0.65f; [Tooltip("Maximum offset for the index finger")] public float maxIndexOffset = 0.5f; [Tooltip("Maximum offset for the middle finger")] public float maxMiddleOffset = 0.5f; [Tooltip("Maximum offset for the ring finger")] public float maxRingOffset = 0.5f; [Tooltip("Maximum offset for the pinky finger")] public float maxPinkyOffset = 0.5f; [Tooltip("Maximum offset for the thumb")] public float maxThumbOffset = 0.5f; [Tooltip("Minimum pressure required for interaction - These default settings are tuned for the default hands physics settings and results might vary based on hands set follow speed and rigidbody settings")] public float minPressure = 300; [Tooltip("Maximum pressure for interaction - These default settings are tuned for the default hands physics settings and results might vary based on hands set follow speed and rigidbody settings")] public float maxPressure = 1200; [Tooltip("Curve to represent the relationship between pressure applied and finger bend")] public AnimationCurve pressureBendCurve = AnimationCurve.Linear(0, 0, 1, 1); // Internal fields for handling finger touch and pressure private float fingerRadiusMultiplier = 4f; private float[] maxOffsets; private float[] currentPressure; private bool[] isTouching; private Vector3 largeSphereCheckerPoint; private float largeSphereCheckerRadius; private Collider[] collidersNonAlloc = new Collider[100]; private Vector3[] fingerTips = new Vector3[5]; private Finger[] fingers = new Finger[5]; private Vector3 smoothAngularVelocity; /// /// Returns a value between 0 and 1 representing the current pressure applied to the finger /// public float GetCurrentFingerPressure(FingerEnum finger) { return currentPressure[(int)finger]; } void Awake() { fingerTips[0] = (hand.transform.InverseTransformPoint(index.tip.position)); fingerTips[1] =(hand.transform.InverseTransformPoint(middle.tip.position)); fingerTips[2] =(hand.transform.InverseTransformPoint(ring.tip.position)); fingerTips[3] =(hand.transform.InverseTransformPoint(pinky.tip.position)); fingerTips[4] =(hand.transform.InverseTransformPoint(thumb.tip.position)); fingers[0] = (index); fingers[1] =(middle); fingers[2] =(ring); fingers[3] =(pinky); fingers[4] =(thumb); maxOffsets = new float[] { maxIndexOffset, maxMiddleOffset, maxRingOffset, maxPinkyOffset, maxThumbOffset }; currentPressure = new float[] { 0, 0, 0, 0, 0 }; isTouching = new bool[] { false, false, false, false, false }; CreateEncapsulatingSphere(fingerTips, out largeSphereCheckerPoint, out largeSphereCheckerRadius); largeSphereCheckerRadius += index.tipRadius; largeSphereCheckerRadius += middle.tipRadius; } void OnEnable() { StartCoroutine(SlowUpdateCoroutine()); } void OnDisable() { StopAllCoroutines(); } void LateUpdate() { MoveTowardsBendTarget(); } IEnumerator SlowUpdateCoroutine() { while (true) { CheckFingerTouch(); yield return new WaitForFixedUpdate(); } } void MoveTowardsBendTarget() { for(int i = 0; i < fingers.Length; i++) { if(fingers[i].secondaryOffset == 0 && currentPressure[i] == 0) continue; float currentTarget = currentPressure[i]*maxOffsets[i]; float targetDistance = Mathf.Abs(currentTarget - fingers[i].secondaryOffset)/maxOffsets[i]; targetDistance = Mathf.Pow(targetDistance, 2f); float distanceSpeed = Time.deltaTime * offsetSpeed * targetDistance; currentTarget = Mathf.Clamp(currentTarget, -maxOffsets[i], maxOffsets[i]); fingers[i].secondaryOffset = Mathf.MoveTowards(fingers[i].secondaryOffset, currentTarget, distanceSpeed + Time.deltaTime * offsetSpeed/2f); if(fingers[i].GetCurrentBend() < 0) { fingers[i].secondaryOffset += -fingers[i].GetCurrentBend(); } } } void CheckFingerTouch() { //var angularVelocity = CalculateCustomAngularVelocity(hand.transform.rotation, hand.moveTo.transform.rotation, 2000f); var targetAngularVelocity = !hand.holdingObj ? hand.lastAngularVelocity : Vector3.zero; //var lerpDelta = Vector3.SqrMagnitude(targetAngularVelocity - smoothAngularVelocity) * Time.fixedDeltaTime * 1200f + Time.fixedDeltaTime * 360f; var lerpDelta = Time.fixedDeltaTime * 1200f; //smoothAngularVelocity = Vector3.Lerp(smoothAngularVelocity, targetAngularVelocity, lerpDelta); smoothAngularVelocity = targetAngularVelocity;//Vector3.MoveTowards(smoothAngularVelocity, targetAngularVelocity, lerpDelta); //Check for overlap with large sphere int numColliders = Physics.OverlapSphereNonAlloc(hand.transform.TransformPoint(largeSphereCheckerPoint), largeSphereCheckerRadius, collidersNonAlloc, touchMask, QueryTriggerInteraction.Ignore); if (numColliders > 0) { //Check for overlap with each finger for(int i = 0; i < fingerTips.Length; i++) { fingerTips[i] = hand.transform.InverseTransformPoint(fingers[i].tip.position); Vector3 fingerTipWorld = hand.transform.TransformPoint(fingerTips[i]); float radius = fingers[i].tipRadius*fingerRadiusMultiplier*Mathf.Abs(hand.transform.lossyScale.x) * (isTouching[i] ? 2f : 1f); int numCollidersFinger = Physics.OverlapSphereNonAlloc(fingerTipWorld, radius, collidersNonAlloc, touchMask, QueryTriggerInteraction.Ignore); if (numCollidersFinger > 0) { var pressure = CalculateFingerTipPressure(fingerTipWorld, hand.transform.position, smoothAngularVelocity, hand.palmTransform.forward, out bool isForceTop, fingers[i].tipRadius); var evaluatedPressure = pressureBendCurve.Evaluate((pressure-minPressure)/maxPressure); isTouching[i] = true; if(pressure < minPressure) continue; //Difference between currentPressure[i] and evaluatedPressure float pressureDifference = Mathf.Abs(currentPressure[i] - evaluatedPressure); pressureDifference = Mathf.Pow(pressureDifference, 2f); currentPressure[i] = Mathf.MoveTowards(currentPressure[i], evaluatedPressure * (isForceTop ? -1 : 1), pressureDifference * Time.fixedDeltaTime * 30f + Time.fixedDeltaTime * 5f); } else { currentPressure[i] = Mathf.MoveTowards(currentPressure[i], 0, maxOffsets[i]*Time.fixedDeltaTime*30f); isTouching[i] = false; } } } else { for(int i = 0; i < fingers.Length; i++) { isTouching[i] = false; if(currentPressure[i] == 0) continue; currentPressure[i] = Mathf.MoveTowards(currentPressure[i], 0, maxOffsets[i]*Time.fixedDeltaTime*30f); } } } Vector3 CalculateCustomAngularVelocity(Quaternion currentRotation, Quaternion targetRotation, float maxAngularVelocity) { Quaternion rotationDifference = targetRotation * Quaternion.Inverse(currentRotation); var eularAngularVelocity = rotationDifference.eulerAngles; return eularAngularVelocity; } float CalculateFingerTipPressure(Vector3 fingertipPoint, Vector3 wristPoint, Vector3 angularVelocity, Vector3 palmDirection, out bool isForceOnTop, float fingertipArea) { Vector3 leverArm = fingertipPoint - wristPoint; Vector3 torque = Vector3.Cross(leverArm, angularVelocity); float forceMagnitude = torque.magnitude / leverArm.magnitude; float pressure = forceMagnitude / fingertipArea; Vector3 relativePoint; if(float.IsNaN(angularVelocity.x) || float.IsNaN(angularVelocity.y) || float.IsNaN(angularVelocity.z)) relativePoint = fingertipPoint + hand.body.velocity*Time.fixedDeltaTime; else relativePoint = Quaternion.Euler(angularVelocity)*leverArm + hand.body.velocity*Time.fixedDeltaTime; var plane = new Plane(palmDirection, Vector3.zero); isForceOnTop = plane.GetSide(relativePoint); return pressure; } void CreateEncapsulatingSphere(Vector3[] fingerTips, out Vector3 sphereCenter, out float sphereRadius) { sphereCenter = new Vector3(); foreach(var point in fingerTips) { sphereCenter += point; } sphereCenter /= fingerTips.Length; sphereRadius = 0f; foreach(var point in fingerTips) { float distance = Vector3.Distance(sphereCenter, point); sphereRadius = Mathf.Max(sphereRadius, distance); } } } }