142 lines
5.1 KiB
C#
142 lines
5.1 KiB
C#
// Designed by KINEMATION, 2024.
|
|
|
|
using UnityEngine;
|
|
|
|
namespace KINEMATION.MotionWarping.Runtime.Core
|
|
{
|
|
public class TwoBoneIk
|
|
{
|
|
private const float FloatMin = 1e-10f;
|
|
private const float SqrEpsilon = 1e-8f;
|
|
|
|
public static float TriangleAngle(float aLen, float aLen1, float aLen2)
|
|
{
|
|
float c = Mathf.Clamp((aLen1 * aLen1 + aLen2 * aLen2 - aLen * aLen) / (aLen1 * aLen2) / 2.0f, -1.0f, 1.0f);
|
|
return Mathf.Acos(c);
|
|
}
|
|
|
|
public static Quaternion FromToRotation(Vector3 from, Vector3 to)
|
|
{
|
|
float theta = Vector3.Dot(from.normalized, to.normalized);
|
|
if (theta >= 1f)
|
|
return Quaternion.identity;
|
|
|
|
if (theta <= -1f)
|
|
{
|
|
Vector3 axis = Vector3.Cross(from, Vector3.right);
|
|
if (axis.sqrMagnitude == 0f)
|
|
axis = Vector3.Cross(from, Vector3.up);
|
|
|
|
return Quaternion.AngleAxis(180f, axis);
|
|
}
|
|
|
|
return Quaternion.AngleAxis(Mathf.Acos(theta) * Mathf.Rad2Deg, Vector3.Cross(from, to).normalized);
|
|
}
|
|
|
|
public static Quaternion NormalizeSafe(Quaternion q)
|
|
{
|
|
float dot = Quaternion.Dot(q, q);
|
|
if (dot > FloatMin)
|
|
{
|
|
float rsqrt = 1.0f / Mathf.Sqrt(dot);
|
|
return new Quaternion(q.x * rsqrt, q.y * rsqrt, q.z * rsqrt, q.w * rsqrt);
|
|
}
|
|
|
|
return Quaternion.identity;
|
|
}
|
|
|
|
public static void SolveTwoBoneIK(
|
|
Transform root,
|
|
Transform mid,
|
|
Transform tip,
|
|
(Vector3, Quaternion) target,
|
|
Transform hint,
|
|
float targetWeight,
|
|
float hintWeight
|
|
)
|
|
{
|
|
if (Mathf.Approximately(targetWeight, 0f))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Vector3 aPosition = root.position;
|
|
Vector3 bPosition = mid.position;
|
|
Vector3 cPosition = tip.position;
|
|
Vector3 tPosition = Vector3.Lerp(cPosition, target.Item1, targetWeight);
|
|
Quaternion tRotation = Quaternion.Lerp(tip.rotation, target.Item2, targetWeight);
|
|
bool hasHint = hint != null && hintWeight > 0f;
|
|
|
|
Vector3 ab = bPosition - aPosition;
|
|
Vector3 bc = cPosition - bPosition;
|
|
Vector3 ac = cPosition - aPosition;
|
|
Vector3 at = tPosition - aPosition;
|
|
|
|
float abLen = ab.magnitude;
|
|
float bcLen = bc.magnitude;
|
|
float acLen = ac.magnitude;
|
|
float atLen = at.magnitude;
|
|
|
|
float oldAbcAngle = TriangleAngle(acLen, abLen, bcLen);
|
|
float newAbcAngle = TriangleAngle(atLen, abLen, bcLen);
|
|
|
|
// Bend normal strategy is to take whatever has been provided in the animation
|
|
// stream to minimize configuration changes, however if this is collinear
|
|
// try computing a bend normal given the desired target position.
|
|
// If this also fails, try resolving axis using hint if provided.
|
|
Vector3 axis = Vector3.Cross(ab, bc);
|
|
if (axis.sqrMagnitude < SqrEpsilon)
|
|
{
|
|
axis = hasHint ? Vector3.Cross(hint.position - aPosition, bc) : Vector3.zero;
|
|
|
|
if (axis.sqrMagnitude < SqrEpsilon)
|
|
axis = Vector3.Cross(at, bc);
|
|
|
|
if (axis.sqrMagnitude < SqrEpsilon)
|
|
axis = Vector3.up;
|
|
}
|
|
|
|
axis = Vector3.Normalize(axis);
|
|
|
|
float a = 0.5f * (oldAbcAngle - newAbcAngle);
|
|
float sin = Mathf.Sin(a);
|
|
float cos = Mathf.Cos(a);
|
|
Quaternion deltaR = new Quaternion(axis.x * sin, axis.y * sin, axis.z * sin, cos);
|
|
mid.rotation = deltaR * mid.rotation;
|
|
|
|
cPosition = tip.position;
|
|
ac = cPosition - aPosition;
|
|
root.rotation = FromToRotation(ac, at) * root.rotation;
|
|
|
|
if (hasHint)
|
|
{
|
|
float acSqrMag = ac.sqrMagnitude;
|
|
if (acSqrMag > 0f)
|
|
{
|
|
bPosition = mid.position;
|
|
cPosition = tip.position;
|
|
ab = bPosition - aPosition;
|
|
ac = cPosition - aPosition;
|
|
|
|
Vector3 acNorm = ac / Mathf.Sqrt(acSqrMag);
|
|
Vector3 ah = hint.position - aPosition;
|
|
Vector3 abProj = ab - acNorm * Vector3.Dot(ab, acNorm);
|
|
Vector3 ahProj = ah - acNorm * Vector3.Dot(ah, acNorm);
|
|
|
|
float maxReach = abLen + bcLen;
|
|
if (abProj.sqrMagnitude > (maxReach * maxReach * 0.001f) && ahProj.sqrMagnitude > 0f)
|
|
{
|
|
Quaternion hintR = FromToRotation(abProj, ahProj);
|
|
hintR.x *= hintWeight;
|
|
hintR.y *= hintWeight;
|
|
hintR.z *= hintWeight;
|
|
hintR = NormalizeSafe(hintR);
|
|
root.rotation = hintR * root.rotation;
|
|
}
|
|
}
|
|
}
|
|
|
|
tip.rotation = tRotation;
|
|
}
|
|
}
|
|
} |