Skip to content

Instantly share code, notes, and snippets.

@MaximovInk
Last active April 17, 2020 16:04
Show Gist options
  • Save MaximovInk/b93b4e589e084040ae9ac5afd2866219 to your computer and use it in GitHub Desktop.
Save MaximovInk/b93b4e589e084040ae9ac5afd2866219 to your computer and use it in GitHub Desktop.
2D IK Fabrik solver with inverse/forward direction for Unity
using UnityEngine;
[ExecuteAlways]
public class IK2DFabrik : MonoBehaviour
{
public Transform Target;
public bool Inverse = false;
[SerializeField]
private int ChainLength = 2;
[SerializeField]
private int Iterations = 10;
[SerializeField]
private float Delta = 0.01f;
private float[] BonesLength;
private float CompleteLength;
private Transform[] Bones;
private Vector2[] Positions;
#if UNITY_EDITOR
private void OnDrawGizmos()
{
var current = transform;
for (int i = 0; i < ChainLength && current != null && current.childCount > 0; i++)
{
var child = current.GetChild(0);
var scale = Vector3.Distance(current.position, child.position) * 0.1f;
UnityEditor.Handles.matrix = Matrix4x4.TRS(current.position, Quaternion.FromToRotation(Vector3.up, child.position - current.position), new Vector3(scale, Vector3.Distance(child.position, current.position), scale));
UnityEditor.Handles.color = Color.blue;
UnityEditor.Handles.DrawWireCube(Vector3.up * 0.5f, Vector3.one);
current = child;
}
}
#endif
private void Awake()
{
Init();
}
private Vector2 PerpendicularLine(Vector2 P1, Vector2 P2)
{
var v = P2 - P1;
return new Vector2(-v.y, v.x) / Mathf.Sqrt(v.x * v.x + v.y * v.y);
}
private void Init()
{
Bones = new Transform[ChainLength + 1];
Positions = new Vector2[ChainLength + 1];
BonesLength = new float[ChainLength];
var current = transform;
CompleteLength = 0;
for (var i = 0; i < Bones.Length; i++)
{
Bones[i] = current;
Positions[i] = Bones[i].position;
if (i < ChainLength)
{
current = current.GetChild(0);
}
if (i > 0)
{
BonesLength[i - 1] = Vector2.Distance(Bones[i - 1].position, Bones[i].position);
CompleteLength += BonesLength[i - 1];
}
}
}
private void Update()
{
ResolveIK();
}
private void ResolveIK()
{
if (Target == null)
return;
if (BonesLength.Length != ChainLength || !Target.gameObject.activeSelf)
{
Init();
}
GetBonesPositions();
Vector2 targetPos = Target.position;
if (Vector2.Distance(transform.position, targetPos) > CompleteLength)
{
LookToTarget(targetPos);
}
else
{
ApplyFabrik(targetPos);
NormilizePositions();
}
ApplyPositionsToBones();
}
private void GetBonesPositions()
{
for (int i = 0; i < Bones.Length; i++)
{
Positions[i] = Bones[i].position;
}
}
private void LookToTarget(Vector2 targetPos)
{
var direction = (targetPos - Positions[0]).normalized;
for (int i = 1; i < Positions.Length; i++)
Positions[i] = Positions[i - 1] + direction * BonesLength[i - 1];
}
private void ApplyFabrik(Vector2 targetPos)
{
for (int iteration = 0; iteration < Iterations; iteration++)
{
for (int i = Positions.Length - 1; i > 0; i--)
{
if (i == Positions.Length - 1)
{
Positions[i] = targetPos;
}
else
{
Positions[i] = Positions[i + 1] + (Positions[i] - Positions[i + 1]).normalized * BonesLength[i];
}
}
for (int i = 1; i < Positions.Length; i++)
Positions[i] = Positions[i - 1] + (Positions[i] - Positions[i - 1]).normalized * BonesLength[i - 1];
if (Vector2.Distance(Positions[Positions.Length - 1], targetPos) < Delta)
break;
}
}
private void NormilizePositions()
{
for (int i = 1; i < Positions.Length - 1; i++)
{
var center = (Positions[i - 1] + Positions[i + 1]) / 2;
var distToCenter = Vector2.Distance(Positions[i], center);
Positions[i] = center + distToCenter * (PerpendicularLine(Positions[i - 1], Positions[i + 1]) * (Inverse ? -1 : 1)).normalized;
}
}
private void ApplyPositionsToBones()
{
for (int i = 0; i < Positions.Length; i++)
{
Bones[i].position = Positions[i];
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment