Быстрая инверсная кинематика в 2D

Часто бывает необходимо сделать процедурную анимацию для цепочки сочлененных сегментов, когда координаты узлов не известны, а есть только набор правил, которым эта цепочка должна следовать (длина сегментов, углы поворота и т.п). На помощь приходит инверсная кинематика.

Задачей инверсной (обратной) кинематики является поиск такого набора позиций сочленений (решения), который обеспечил бы непротиворечивое размещение всей связанной цепочки сегментов на основе ограничений. Базовые ограничения обычно следующие:

  • “Цель” - точка в пространстве, к которой должна “тянуться” цепочка концом своего последнего сегмента, “подтягивая” за собой все остальные сегменты. Пример - предмет, к которому мы можем подтягивать кисть руки, а за ней и всю остальную руку.
  • Длины сегментов - обычно считается, что сегменты являются жесткими связями, имеющими фиксированную длину.
  • Углы поворота - каждый сегмент может быть ограничен в углах вращения вокруг оси сочленения, причем как по часовой, так и против часовой стрелки на разные значения. Пример - голень ноги человека может сгибаться назад почти на 90 градусов, а вперед - не может.

Все ограничения применяются одновременно и решение считается верным только тогда, когда все правила выполнены и нет противоречий.

Существует несколько способов находить решения, большинство из них либо тяжелы в расчетах, либо дают приближенное итеративное решение - за один цикл выполняется корректировка позиций каждого сегмента на выполнение условий ограничений, а потом это повторяется N-раз. Обычно это недостаточно быстро работает на большом количестве цепочек и требуется что-то более быстрое, пусть и менее стабильное в плане плавности переходов между пограничными условиями. Одним из таких подходов является FABRIK - Forward And Backward Reaching Inverse Kinematics, “инверсной кинематики методом прямого и обратного следования”.

Пакет Leopotam.Ik предоставляет механизм решения инверсной кинематики для цепочки сегментов в 2D пространстве с учетом длин сегментов и ограничений на их вращение. Например, нам в unity необходимо симулировать “щупальце”, тянущуюся к нужной точке:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class IkSolver : MonoBehaviour {
// "Цель", которую будем стремиться достичь,
// назначается через инспектор.
[SerializeField] Transform _target;
// Сохраненный список GameObject-ов,
// являющихся узлами цепочки.
List<Transform> _segs = new ();

FabrikSolver2 _ik = new ();

void Start () {
// Ставим стартовую точку ik-цепочки в позицию
// GameObject-а, на котором висит этот компонент.
// Считаем, что сочленение направлено
// вертикально вверх по оси Y.
_ik.SetBase (
transform.position.x, transform.position.y,
transform.up.x, transform.up.y);
// Собираем все дочерние GameObject-ы и считаем их
// сочленениями цепочки, разрешаем вращение
// на +/-60 градусов относительно родителя.
foreach (Transform tr in transform) {
_segs.Add (tr);
_ik.Add (
tr.position.x, tr.position.y,
-60f * Mathf.Deg2Rad, 60f * Mathf.Deg2Rad);
}
}

void Update () {
// Каждый кадр обновляем стартовую точку.
_ik.SetBase (
transform.position.x, transform.position.y,
transform.up.x, transform.up.y);
// И выполняем расчет всех добавленных узлов,
// стремящихся к "Цели" без ограничения углов.
_ik.Solve (_target.position.x, _target.position.y, false);
// По расчитанным результатам обновляем позиции GameObject-ов.
for (int i = 0; i < _ik.Len (); i++) {
(float x, float y) = _ik.Get (i);
_segs[i].position = new (x, y, 0f);
}
}

void OnDrawGizmos () {
// Выполняем отладочную визуализацию
// сегментов между узлами ik-цепочки.
if (_segs != null) {
Vector3 p0 = transform.position;
foreach (Transform s in _segs) {
Vector3 p1 = s.position;
Debug.DrawLine (p0, p1);
p0 = p1;
}
}
}
}

В результате получим следующее поведение:

Подключим ограничение на углы вращения:

1
2
3
4
5
void Update () {
//...
_ik.Solve (_target.position.x, _target.position.y, true);
// ...
}

В результате поведение изменится на следующее:

Актуальные версии пакетов доступны в закрытом discord-сервере для vk/boosty-подписчиков.
Оформить подписку можно здесь: