You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

235 lines
10 KiB
C#

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;
/// <summary>
/// Returns a value between 0 and 1 representing the current pressure applied to the finger
/// </summary>
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);
}
}
}
}