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#
235 lines
10 KiB
C#
6 months ago
|
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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|