using System;
using UnityEngine;
namespace FIMSpace.FTools
{
public partial class FimpIK_Limb : FIK_ProcessorBase
{
// Foot/End Bone rotation helper with root reference
public Quaternion EndBoneMapping { get; protected set; }
public IKBone FeetIKBone { get { return IKBones[3]; } }
/// Assigning helpful reference to main root transform of body to help IK rotations
public virtual void SetRootReference(Transform mainParentTransform)
{
Root = mainParentTransform;
EndBoneMapping = Quaternion.FromToRotation(EndIKBone.right, Vector3.right);
EndBoneMapping *= Quaternion.FromToRotation(EndIKBone.up, Vector3.up);
if (mainParentTransform) hasRoot = true;
}
/// Reference scale for computations - active length from start bone to middle knee
public float ScaleReference { get; protected set; }
public void RefreshLength()
{
ScaleReference = (StartIKBone.transform.position - MiddleIKBone.transform.position).magnitude;
}
public void RefreshScaleReference()
{
ScaleReference = (StartIKBone.transform.position - MiddleIKBone.transform.position).magnitude;
}
float GetCurrentLegToAnkleLength()
{
float fullLength = Mathf.Epsilon;
fullLength += (StartIKBone.transform.position - MiddleIKBone.transform.position).magnitude;
fullLength += (MiddleIKBone.transform.position - EndIKBone.transform.position).magnitude;
return fullLength;
}
/// Returning >= 1f when max range for IK point is reached
public float GetStretchValue(Vector3 targetPos)
{
float toGoal = (StartIKBone.transform.position - targetPos).magnitude;
return toGoal / GetCurrentLegToAnkleLength();
}
public Vector3 GetNotStretchedPositionTowards(Vector3 targetPos, float maxStretch)
{
Vector3 toGoal = (targetPos - StartIKBone.transform.position);
return StartIKBone.transform.position + toGoal.normalized * (GetCurrentLegToAnkleLength() * maxStretch);
}
public void ApplyMaxStretchingPreprocessing(float maxStretch, float allowIKRotationFadeout = 2f)
{
if (maxStretch < 1.1f)
{
float toGoal = (StartIKBone.transform.position - IKTargetPosition).magnitude;
float limbUnitLength = GetCurrentLegToAnkleLength();
float stretch = toGoal / limbUnitLength;
if (stretch > maxStretch)
{
if (hasFeet && FeetStretchWeight > 0f)
{
#region Feet stretch helper
if (maxFeetAngle > 0f)
{
// Feet angle factor helpers
Vector3 thighToTarget = IKTargetPosition - StartIKBone.transform.position;
thighToTarget.Normalize();
Vector3 ankleToFeet = FeetIKBone.transform.position - EndIKBone.transform.position;
ankleToFeet.Normalize();
float feetDot = Vector3.Dot(thighToTarget, ankleToFeet);
feetDot = Mathf.Clamp01(feetDot);
// Feet bone rotation helpers
float feetLength = (FeetIKBone.transform.position - EndIKBone.transform.position).magnitude;
float stretchDiff = toGoal - limbUnitLength * Mathf.Min(maxStretch, 1f);
stretchDiff /= (feetLength * FeetFadeQuicker);
float stretchDiff2 = stretchDiff;
stretchDiff *= maxFeetAngleFactor * FeetStretchSensitivity;
if (stretchDiff > 1f) stretchDiff = 1f;
if (stretchDiff2 < 1f) stretchDiff2 = 1f; else { if (stretchDiff2 > 2f) stretchDiff2 = 2f; stretchDiff2 -= 1f; stretchDiff2 *= stretchDiff2; stretchDiff2 = 1f - stretchDiff2; }
// Apply
float heelFactor = Mathf.Min(FeetStretchLimit, (1f - feetDot) * (90f / maxFeetAngle) * stretchDiff * FeetStretchWeight);
if (stretch > 1.09f)
{
stretchDiff2 *= 1f - Mathf.InverseLerp(1.09f, 1.23f, stretch);
}
if (heelFactor != 0f) OffsetHeel(heelFactor, stretchDiff2);
// Recompute
toGoal = (StartIKBone.transform.position - IKTargetPosition).magnitude;
stretch = toGoal / limbUnitLength;
}
#endregion
if (stretch > maxStretch)
{
float len = (maxStretch * limbUnitLength);
IKTargetPosition = StartIKBone.transform.position + (IKTargetPosition - StartIKBone.transform.position).normalized * len;
}
}
else
{
float len = (maxStretch * limbUnitLength);
IKTargetPosition = StartIKBone.transform.position + (IKTargetPosition - StartIKBone.transform.position).normalized * len;
}
if (allowIKRotationFadeout > 0f)
{
float stretchDiff = stretch - maxStretch;
stretchDiff = Mathf.Clamp01(stretchDiff * allowIKRotationFadeout);
internalRotationWeightMul = (1f - stretchDiff);
}
}
else
{
internalRotationWeightMul = 1f;
}
}
}
#region Prepare Feet
[NonSerialized] public float FeetStretchWeight = 1f;
[NonSerialized] public float FeetStretchSensitivity = 1f;
[NonSerialized] public float FeetStretchLimit = 1f;
[NonSerialized] public float FeetFadeQuicker = 1f;
[NonSerialized] public bool disableFeet = false;
float maxFeetAngle = 0f;
float maxFeetAngleFactor = 0f;
Vector3 ankleToFeet;
void PrepareFeet()
{
Vector3 kneeToAnkle = EndIKBone.transform.position - MiddleIKBone.transform.position;
kneeToAnkle.Normalize();
ankleToFeet = FeetIKBone.transform.position - EndIKBone.transform.position;
ankleToFeet.Normalize();
maxFeetAngle = Vector3.Angle(ankleToFeet, kneeToAnkle);
maxFeetAngleFactor = 90f / maxFeetAngle;
//if ( Root == null)
//{
// UnityEngine.Debug.Log("[IK] Feet requires Root Transform defined!");
// hasFeet = false;
//}
}
#endregion
internal void OffsetHeel(float heelRot, float feetCompensate = 1f)
{
if (hasFeet == false) return;
if (disableFeet) return;
Quaternion preAnkleRot = IKTargetRotation;
Vector3 toFeet = (FeetIKBone.transform.position - EndIKBone.transform.position);
Vector3 rotatedOffset = Quaternion.Inverse(preAnkleRot) * (toFeet);
Vector3 rightAxis;
if (UseEndBoneMapping) rightAxis = IKTargetRotation * Vector3.right;
else rightAxis = IKTargetRotation * (EndIKBone.right);// Root.right;
Quaternion rotationOffset = Quaternion.AngleAxis(heelRot * maxFeetAngle, rightAxis);
Quaternion newAnkleRot = rotationOffset * preAnkleRot;
IKTargetRotation = newAnkleRot;
Vector3 newOffset = newAnkleRot * rotatedOffset;
rotatedOffset = newOffset - toFeet;
if (feetCompensate > 0f)
{
Quaternion newFeetRot = Quaternion.Inverse(rotationOffset) * (FeetIKBone.transform.rotation);
if (feetCompensate >= 1f)
FeetIKBone.transform.rotation = newFeetRot;
else
FeetIKBone.transform.rotation = Quaternion.Lerp(FeetIKBone.transform.rotation, newFeetRot, feetCompensate);
}
IKTargetPosition -= rotatedOffset;
}
}
}