using UnityEngine;
namespace Invector.IK
{
[System.Serializable]
public class vIKSolver
{
public Transform rootTransform;
public Transform rootBone;
public Transform middleBone;
public Transform endBone;
[Header("Optional")]
public Transform endBoneRef;
public Transform middleBoneRef;
public Transform endBoneOffset;
public Transform middleBoneOffset;
string middleTag, endTag;
float _weight;
Vector3? hintPosition;
///
/// Manual creation of the bone targets
///
///
///
///
public vIKSolver(Transform rootTransform, Transform rootBone, Transform middleBone, Transform endBone)
{
this.rootTransform = rootTransform;
this.rootBone = rootBone;
this.middleBone = middleBone;
this.endBone = endBone;
}
///
/// Auto creation of the bone targets
///
///
///
public vIKSolver(Animator animator, AvatarIKGoal ikGoal)
{
if (animator == null) { return; }
this.rootTransform = animator.transform;
if (animator.isHuman)
{
switch (ikGoal)
{
case AvatarIKGoal.LeftHand:
rootBone = animator.GetBoneTransform(HumanBodyBones.LeftUpperArm);
middleBone = animator.GetBoneTransform(HumanBodyBones.LeftLowerArm);
endBone = animator.GetBoneTransform(HumanBodyBones.LeftHand);
endTag = "LeftHand";
middleTag = "LeftHint";
break;
case AvatarIKGoal.RightHand:
rootBone = animator.GetBoneTransform(HumanBodyBones.RightUpperArm);
middleBone = animator.GetBoneTransform(HumanBodyBones.RightLowerArm);
endBone = animator.GetBoneTransform(HumanBodyBones.RightHand);
endTag = "RightHand";
middleTag = "RightHint";
break;
case AvatarIKGoal.LeftFoot:
rootBone = animator.GetBoneTransform(HumanBodyBones.LeftUpperLeg);
middleBone = animator.GetBoneTransform(HumanBodyBones.LeftLowerLeg);
endBone = animator.GetBoneTransform(HumanBodyBones.LeftFoot);
endTag = "LeftFoot";
middleTag = "LeftHint";
break;
case AvatarIKGoal.RightFoot:
rootBone = animator.GetBoneTransform(HumanBodyBones.RightUpperLeg);
middleBone = animator.GetBoneTransform(HumanBodyBones.RightLowerLeg);
endBone = animator.GetBoneTransform(HumanBodyBones.RightFoot);
endTag = "RightFoot";
middleTag = "RightHint";
break;
}
}
CreateBones();
}
public bool isValidBones
{
get
{
return rootBone &&
middleBone &&
endBone && endBoneRef &&
middleBoneRef &&
endBoneOffset &&
middleBoneOffset;
}
}
void CreateBones()
{
if (rootTransform && rootBone && middleBone && endBone)
{
if (!endBoneRef)
{
endBoneRef = new GameObject(endTag + "Ref").transform;
endBoneRef.hideFlags = HideFlags.HideInHierarchy;
endBoneRef.SetParent(rootTransform);
}
if (!middleBoneRef)
{
middleBoneRef = new GameObject(middleTag + "Ref").transform;
middleBoneRef.hideFlags = HideFlags.HideInHierarchy;
middleBoneRef.SetParent(rootTransform);
}
if (!endBoneOffset)
{
endBoneOffset = new GameObject(endTag + "Offset").transform;
endBoneOffset.SetParent(endBoneRef);
endBoneOffset.localPosition = Vector3.zero;
endBoneOffset.localEulerAngles = Vector3.zero;
}
if (!middleBoneOffset)
{
middleBoneOffset = new GameObject(middleTag + "Offset").transform;
middleBoneOffset.SetParent(middleBoneRef);
middleBoneOffset.localPosition = Vector3.zero;
middleBoneOffset.localEulerAngles = Vector3.zero;
}
}
}
///
/// Get IK Weight
///
public virtual float ikWeight { get { return _weight; } }
///
/// Set IK Weight
///
///
public virtual void SetIKWeight(float weight)
{
_weight = weight;
}
public void UpdateIK()
{
if (endBoneRef)
{
endBoneRef.position = endBone.position;
endBoneRef.rotation = endBone.rotation;
}
if (middleBoneRef)
{
middleBoneRef.position = middleBone.position;
middleBoneRef.rotation = middleBone.rotation;
}
}
public virtual void AnimationToIK()
{
if (!isValidBones)
{
CreateBones();
return;
}
UpdateIK();
SetIKHintPosition(middleBoneOffset.position);
SetIKPosition(endBoneOffset.position);
SetIKRotation(endBoneOffset.rotation);
}
///
/// Set IK Position
///
///
public virtual void SetIKPosition(Vector3 ikPosition)
{
if (ikWeight <= 0.0f) return;
// Calculate middleBone Direction
Vector3 middleBoneDirection = Vector3.zero;
if (hintPosition != null)
{
// if middleBoneGoal is null, the direction will be calculated with forearm's point
middleBoneDirection = (Vector3)hintPosition - rootBone.position;
}
else
{
middleBoneDirection = Vector3.Cross(endBone.position - rootBone.position, Vector3.Cross(endBone.position - rootBone.position, endBone.position - middleBone.position));
}
// Get lengths of Arm
float rootBoneLength = (middleBone.position - rootBone.position).magnitude;
float middleBoneLength = (endBone.position - middleBone.position).magnitude;
// Calculate the desired middleBone position
Vector3 middleBonePos = GetHintPosition(rootBone.position, ikPosition, rootBoneLength, middleBoneLength, middleBoneDirection);
// Rotate the bone transformations to align correctly
Quaternion upperarmRotation = Quaternion.FromToRotation(middleBone.position - rootBone.position, middleBonePos - rootBone.position) * rootBone.rotation;
if (!(System.Single.IsNaN(upperarmRotation.x) || System.Single.IsNaN(upperarmRotation.y) || System.Single.IsNaN(upperarmRotation.z)))
{
//Rotate with transition
rootBone.rotation = Quaternion.Slerp(rootBone.rotation, upperarmRotation, ikWeight);
Quaternion middleBoneRotation = Quaternion.FromToRotation(endBone.position - middleBone.position, ikPosition - middleBonePos) * middleBone.rotation;
middleBone.rotation = Quaternion.Slerp(middleBone.rotation, middleBoneRotation, ikWeight);
}
hintPosition = null;
}
///
/// Set IK Rotation
///
///
public virtual void SetIKRotation(Quaternion rotation)
{
if (!(rootBone && middleBone && endBone) || ikWeight <= 0.0f) return;
var _rotation = rotation;
endBone.rotation = Quaternion.Slerp(endBone.rotation, _rotation, ikWeight);
}
///
/// Set IK Hint Position
/// ps: Call before SetIKPosition
///
///
public virtual void SetIKHintPosition(Vector3 hintPosition)
{
this.hintPosition = hintPosition;
}
///
/// Get IK Hint Position
///
///
///
///
///
///
///
protected virtual Vector3 GetHintPosition(Vector3 rootPos, Vector3 endPos, float rootBoneLength, float middleBoneLength, Vector3 middleBoneDirection)
{
Vector3 rootToEndDir = endPos - rootPos;
float rootToEndMag = rootToEndDir.magnitude;
float maxDist = (rootBoneLength + middleBoneLength) * 0.999f;
if (rootToEndMag > maxDist)
{
endPos = rootPos + (rootToEndDir.normalized * maxDist);
rootToEndDir = endPos - rootPos;
rootToEndMag = maxDist;
}
float minDist = Mathf.Abs(rootBoneLength - middleBoneLength) * 1.001f;
if (rootToEndMag < minDist)
{
endPos = rootPos + (rootToEndDir.normalized * minDist);
rootToEndDir = endPos - rootPos;
rootToEndMag = minDist;
}
float aa = ((rootToEndMag * rootToEndMag + rootBoneLength * rootBoneLength - middleBoneLength * middleBoneLength) * 0.5f) / rootToEndMag;
float bb = Mathf.Sqrt(rootBoneLength * rootBoneLength - aa * aa);
Vector3 crossElbow = Vector3.Cross(rootToEndDir, Vector3.Cross(middleBoneDirection, rootToEndDir));
return rootPos + (aa * rootToEndDir.normalized) + (bb * crossElbow.normalized);
}
}
}