using UnityEngine;
namespace Autohand{
[HelpURL("https://app.gitbook.com/s/5zKO0EvOjzUDeT2aiFk3/auto-hand/hand/finger-component")]
public class Finger : MonoBehaviour{
[Header("Tips")]
[Tooltip("This transfrom will represent the tip/stopper of the finger")]
public Transform tip;
[Tooltip("This determines the radius of the spherecast check when bending fingers")]
public float tipRadius = 0.01f;
[Tooltip("This will offset the fingers bend (0 is no bend, 1 is full bend)")]
[Range(0, 1f)]
public float bendOffset;
public float fingerSmoothSpeed = 1;
[HideInInspector]
public float secondaryOffset = 0;
float currBendOffset = 0;
float bend = 0;
[SerializeField]
[HideInInspector]
Quaternion[] minGripRotPose;
[SerializeField]
[HideInInspector]
Vector3[] minGripPosPose;
[SerializeField]
[HideInInspector]
Quaternion[] maxGripRotPose;
[SerializeField]
[HideInInspector]
Vector3[] maxGripPosPose;
[SerializeField]
[HideInInspector]
Transform[] fingerJoints;
public Transform[] FingerJoints { get { return fingerJoints; } }
float lastHitBend;
Collider[] results = new Collider[2];
void Update() {
SlowBend();
}
/// Forces the finger to a bend until it hits something on the given physics layer
/// The number of steps and physics checks it will make lerping from 0 to 1
public bool BendFingerUntilHit(int steps, int layermask) {
ResetBend();
lastHitBend = 0;
for(float i = 0; i <= steps / 5f; i++) {
results[0] = null;
lastHitBend = i / (steps / 5f);
for(int j = 0; j < fingerJoints.Length; j++) {
fingerJoints[j].localPosition = Vector3.Lerp(minGripPosPose[j], maxGripPosPose[j], lastHitBend);
fingerJoints[j].localRotation = Quaternion.Lerp(minGripRotPose[j], maxGripRotPose[j], lastHitBend);
}
Physics.OverlapSphereNonAlloc(tip.transform.position, tipRadius, results, layermask, QueryTriggerInteraction.Ignore);
if(results[0] != null) {
lastHitBend = Mathf.Clamp01(lastHitBend);
if(i == 0)
return true;
break;
}
}
lastHitBend -= (5f / steps);
for(int i = 0; i <= steps / 10f; i++) {
results[0] = null;
lastHitBend += (1f / steps);
for(int j = 0; j < fingerJoints.Length; j++) {
fingerJoints[j].localPosition = Vector3.Lerp(minGripPosPose[j], maxGripPosPose[j], lastHitBend);
fingerJoints[j].localRotation = Quaternion.Lerp(minGripRotPose[j], maxGripRotPose[j], lastHitBend);
}
Physics.OverlapSphereNonAlloc(tip.transform.position, tipRadius, results, layermask, QueryTriggerInteraction.Ignore);
if(results[0] != null) {
bend = lastHitBend;
currBendOffset = lastHitBend;
lastHitBend = Mathf.Clamp01(lastHitBend);
return true;
}
if(lastHitBend >= 1) {
lastHitBend = Mathf.Clamp01(lastHitBend);
return true;
}
}
return false;
}
/// Bends the finger unless its hitting something
/// 0 is no bend / 1 is full bend
public bool UpdateFingerBend(float bend, int layermask) {
var results = new Collider[]{ null };
Physics.OverlapSphereNonAlloc(tip.transform.position, tipRadius, results, layermask, QueryTriggerInteraction.Ignore);
if(this.bend > bend || results[0] == null){
this.bend = bend;
for(int i = 0; i < fingerJoints.Length; i++) {
fingerJoints[i].localPosition = Vector3.Lerp(minGripPosPose[i], maxGripPosPose[i], currBendOffset+secondaryOffset);
fingerJoints[i].localRotation = Quaternion.Lerp(minGripRotPose[i], maxGripRotPose[i], currBendOffset+secondaryOffset);
}
return true;
}
return false;
}
public void UpdateFinger() {
for(int i = 0; i < fingerJoints.Length; i++) {
fingerJoints[i].localPosition = Vector3.Lerp(minGripPosPose[i], maxGripPosPose[i], currBendOffset+secondaryOffset);
fingerJoints[i].localRotation = Quaternion.Lerp(minGripRotPose[i], maxGripRotPose[i], currBendOffset+secondaryOffset);
}
}
public void UpdateFinger(float bend) {
this.bend = bend;
for(int i = 0; i < fingerJoints.Length; i++) {
fingerJoints[i].localPosition = Vector3.Lerp(minGripPosPose[i], maxGripPosPose[i], currBendOffset+secondaryOffset);
fingerJoints[i].localRotation = Quaternion.Lerp(minGripRotPose[i], maxGripRotPose[i], currBendOffset+secondaryOffset);
}
}
/// Forces the finger to a bend ignoring physics and offset
/// 0 is no bend / 1 is full bend
public void SetFingerBend(float bend) {
this.bend = bend;
for(int i = 0; i < fingerJoints.Length; i++) {
fingerJoints[i].localPosition = Vector3.Lerp(minGripPosPose[i], maxGripPosPose[i], bend);
fingerJoints[i].localRotation = Quaternion.Lerp(minGripRotPose[i], maxGripRotPose[i], bend);
}
}
/// Sets the current finger to a bend without interfering with the target
/// 0 is no bend / 1 is full bend
public void SetCurrentFingerBend(float bend) {
currBendOffset = bend;
for(int i = 0; i < fingerJoints.Length; i++) {
fingerJoints[i].localPosition = Vector3.Lerp(minGripPosPose[i], maxGripPosPose[i], bend);
fingerJoints[i].localRotation = Quaternion.Lerp(minGripRotPose[i], maxGripRotPose[i], bend);
}
}
//This function smooths the finger bend so you can change the grip over a frame and wont be a jump
void SlowBend(){
var offsetValue = bendOffset + bend;
if(currBendOffset != offsetValue)
currBendOffset = Mathf.MoveTowards(currBendOffset, offsetValue, Mathf.Pow(Mathf.Abs(currBendOffset - offsetValue)*2, 0.5f) * Time.deltaTime * fingerSmoothSpeed * 6 + fingerSmoothSpeed * Time.deltaTime);
}
[ContextMenu("ResetBend")]
public void ResetBend() {
for(int i = 0; i < fingerJoints.Length; i++) {
fingerJoints[i].localPosition = minGripPosPose[i];
fingerJoints[i].localRotation = minGripRotPose[i];
}
}
[ContextMenu("Grip")]
public void Grip() {
for(int i = 0; i < fingerJoints.Length; i++) {
fingerJoints[i].localPosition = maxGripPosPose[i];
fingerJoints[i].localRotation = maxGripRotPose[i];
}
}
/// Returns the bend the finger ended with from the last BendFingerUntilHit() call
public float GetLastHitBend() {
return lastHitBend;
}
[ContextMenu("Set Open Finger Pose")]
public void SetMinPose(){
int GetKidsCount(Transform obj, ref int count) {
if(obj != tip){
count++;
for(int k = 0; k < obj.childCount; k++) {
GetKidsCount(obj.GetChild(k), ref count);
}
}
return count;
}
int points = 0;
GetKidsCount(transform, ref points);
minGripPosPose = new Vector3[points];
minGripRotPose = new Quaternion[points];
fingerJoints = new Transform[points];
int i = 0;
AssignChildrenPose(transform, ref i);
void AssignChildrenPose(Transform obj, ref int index) {
if(obj != tip){
AssignPoint(index, obj.localPosition, obj.localRotation, obj);
index++;
for(int j = 0; j < obj.childCount; j++) {
AssignChildrenPose(obj.GetChild(j), ref index);
}
}
}
void AssignPoint(int point, Vector3 pos, Quaternion rot, Transform joint) {
minGripPosPose[point] = pos;
minGripRotPose[point] = rot;
fingerJoints[point] = joint;
}
}
[ContextMenu("Set Closed Finger Pose")]
public void SetMaxPose(){
int GetKidsCount(Transform obj, ref int count) {
if(obj != tip){
count++;
for(int k = 0; k < obj.childCount; k++) {
GetKidsCount(obj.GetChild(k), ref count);
}
}
return count;
}
int points = 0;
GetKidsCount(transform, ref points);
maxGripPosPose = new Vector3[points];
maxGripRotPose = new Quaternion[points];
fingerJoints = new Transform[points];
int i = 0;
AssignChildrenPose(transform, ref i);
void AssignChildrenPose(Transform obj, ref int index){
if(obj != tip){
AssignPoint(index, obj.localPosition, obj.localRotation, obj);
index++;
for(int j = 0; j < obj.childCount; j++) {
AssignChildrenPose(obj.GetChild(j), ref index);
}
}
}
void AssignPoint(int point, Vector3 pos, Quaternion rot, Transform joint) {
maxGripPosPose[point] = pos;
maxGripRotPose[point] = rot;
fingerJoints[point] = joint;
}
}
public void CopyPose(Finger finger)
{
maxGripPosPose = new Vector3[finger.maxGripPosPose.Length];
finger.maxGripPosPose.CopyTo(maxGripPosPose, 0);
maxGripRotPose = new Quaternion[finger.maxGripRotPose.Length];
finger.maxGripRotPose.CopyTo(maxGripRotPose, 0);
minGripPosPose = new Vector3[finger.minGripPosPose.Length];
finger.minGripPosPose.CopyTo(minGripPosPose, 0);
minGripRotPose = new Quaternion[finger.minGripRotPose.Length];
finger.minGripRotPose.CopyTo(minGripRotPose, 0);
fingerJoints = new Transform[finger.fingerJoints.Length];
finger.fingerJoints.CopyTo(fingerJoints, 0);
}
public bool IsMinPoseSaved()
{
return minGripPosPose.Length != 0;
}
public bool IsMaxPoseSaved()
{
return maxGripPosPose.Length != 0;
}
public float GetCurrentBend() {
return currBendOffset+secondaryOffset;
}
private void OnDrawGizmos() {
if(tip == null)
return;
Gizmos.color = Color.cyan;
Gizmos.DrawWireSphere(tip.transform.position, tipRadius);
}
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.blue;
DrawSphereBetweenChild(transform);
void DrawSphereBetweenChild(Transform transform){
for (int i = 0; i < transform.childCount; i++)
{
var childTransform = transform.GetChild(i);
if (childTransform.TryGetComponent(out CapsuleCollider cap))
{
Gizmos.DrawWireSphere(Vector3.Lerp(transform.position, cap.bounds.center, 0.5f), tipRadius);
}
DrawSphereBetweenChild(childTransform);
}
}
}
}
}