Алгоритм GJK (Gilbert-Johnson-Keerthi, Гилберта — Джонсона — Кирти) — это эффективный метод для решения задачи о ближайших точках (closest point problems) между выпуклыми множествами. Он широко используется в компьютерных играх, робототехнике и других областях для обнаружения [[Пересечение|столкновений]] между выпуклыми объектами.
### Принцип работы:
1. **Инициализация**: Выбирается начальная точка в [[Simplex|симплексе]] — это может быть, например, точка на одном из объектов.
2. **Итерация**: На каждом шаге алгоритма строится новый симплекс, который либо содержит начало координат, либо направлен к нему.
3. **Проверка**: Если новый симплекс содержит начало координат, объекты пересекаются. В противном случае, итерация продолжается с новым симплексом.
### Производительность:
GJK считается одним из самых быстрых алгоритмов для [[Collision Detection (Обнаружение Столкновений)|обнаружения столкновений]], особенно для объектов с небольшим числом вершин. Он может работать на [[Asymptotic Complexity и Time Complexity (Асимптотическая сложность и Временная сложность)|сублинейном времени]] в зависимости от размера входных данных. Однако, сложность алгоритма теоретически не определена и может варьироваться.
### Пример кода на чистом C\#
###### Производительность:
- `FurthestPointInDirection`: Линейная сложность \( O(n) \), где \( n \) — количество вершин.
- `SupportFunc`: Также линейная сложность, так как вызывает `FurthestPointInDirection` дважды.
- `GJK`: Время выполнения варьируется, но в большинстве случаев является эффективным, особенно для объектов с небольшим числом вершин.
Этот код — упрощенный пример, и в реальной реализации могут быть дополнительные оптимизации и обработка различных случаев. Надеюсь, теперь стало понятнее.
```csharp
// Функция для нахождения наиболее удаленной точки в заданном направлении для выпуклого объекта
Vector3 FurthestPointInDirection(Vector3 direction, Vector3[] vertices)
{
// Инициализация максимального произведения и соответствующей точки
float maxProduct = Vector3.Dot(vertices[0], direction);
Vector3 furthestPoint = vertices[0];
// Перебор всех вершин для нахождения наиболее удаленной в заданном направлении
for (int i = 1; i < vertices.Length; ++i)
{
float product = Vector3.Dot(vertices[i], direction);
if (product > maxProduct)
{
maxProduct = product;
furthestPoint = vertices[i];
}
}
return furthestPoint;
}
// Функция поддержки, используемая в алгоритме GJK
Vector3 SupportFunc(Vector3 direction, Vector3[] verticesA, Vector3[] verticesB)
{
// Находим наиболее удаленную точку на первом объекте в заданном направлении
Vector3 pointA = FurthestPointInDirection(direction, verticesA);
// Находим наиболее удаленную точку на втором объекте в противоположном направлении
Vector3 pointB = FurthestPointInDirection(-direction, verticesB);
// Возвращаем разницу между этими точками
return pointA - pointB;
}
// Основная функция алгоритма GJK для обнаружения столкновений
bool GJK(Vector3[] verticesA, Vector3[] verticesB)
{
// Инициализация начальной точки и направления
Vector3 initialPoint = SupportFunc(new Vector3(1, 0, 0), verticesA, verticesB);
Simplex simplex = new Simplex(initialPoint);
Vector3 direction = -initialPoint;
// Основной цикл алгоритма
while (true)
{
// Находим новую поддерживающую точку в заданном направлении
Vector3 newPoint = SupportFunc(direction, verticesA, verticesB);
// Проверка, пересекаются ли объекты
if (Vector3.Dot(newPoint, direction) < 0)
return false;
// Добавляем новую точку в симплекс
simplex.Add(newPoint);
// Проверка, содержит ли симплекс начало координат, и обновление направления если необходимо
if (simplex.ContainsOrigin(ref direction))
return true;
}
}
```
Класс `Simplex` — это структура данных, используемая в алгоритме GJK для хранения "простейшей" фигуры (simplex), которая может быть точкой, отрезком, треугольником или тетраэдром. Этот объект используется для аппроксимации формы двух других объектов и позволяет определить, пересекаются ли они.
###### Производительность:
1. Добавление вершины (`Add`): Операция имеет константную сложность \(O(1)\), так как это добавление элемента в список.
2. `ContainsOrigin`: Сложность зависит от реализации. В общем случае, это может быть операция с линейной или константной сложностью, в зависимости от размера симплекса (который ограничен: 1-4 вершины).
Вот простая реализация класса `Simplex` на C#:
```csharp
using System.Collections.Generic;
using UnityEngine;
public class Simplex
{
private List<Vector3> vertices;
public Simplex(Vector3 initialPoint)
{
vertices = new List<Vector3> { initialPoint };
}
// Добавление точки к симплексу
public void Add(Vector3 vertex)
{
vertices.Add(vertex);
}
// Проверка, содержит ли симплекс начало координат, и, если необходимо, обновление направления
public bool ContainsOrigin(ref Vector3 direction)
{
// Берем последнюю вершину симплекса, которую обычно обозначают как A
Vector3 A = vertices[vertices.Count - 1];
// Вектор AO направлен от A к началу координат
Vector3 AO = -A;
// Обработка симплекса в виде отрезка (2 вершины)
if (vertices.Count == 2)
{
// Вершина B - первая вершина симплекса
Vector3 B = vertices[0];
// Вектор от A к B
Vector3 AB = B - A;
// Новое направление для следующего шага GJK
direction = Vector3.Cross(Vector3.Cross(AB, AO), AB);
}
// Обработка симплекса в виде треугольника (3 вершины)
else if (vertices.Count == 3)
{
Vector3 B = vertices[0];
Vector3 C = vertices[1];
Vector3 AB = B - A;
Vector3 AC = C - A;
// Вектор, перпендикулярный плоскости ABC
Vector3 ABC = Vector3.Cross(AB, AC);
// Проверки для определения, в какую сторону двигаться дальше
if (Vector3.Dot(Vector3.Cross(AB, ABC), AO) > 0)
{
direction = Vector3.Cross(Vector3.Cross(AB, AO), AB);
}
else if (Vector3.Dot(Vector3.Cross(ABC, AC), AO) > 0)
{
direction = Vector3.Cross(Vector3.Cross(AC, AO), AC);
}
else
{
direction = ABC;
}
}
// Обработка симплекса в виде тетраэдра (4 вершины)
else if (vertices.Count == 4)
{
Vector3 B = vertices[0];
Vector3 C = vertices[1];
Vector3 D = vertices[2];
// Плоскости ABC, ABD и ACD
Vector3 ABC = Vector3.Cross(B - A, C - A);
Vector3 ABD = Vector3.Cross(B - A, D - A);
Vector3 ACD = Vector3.Cross(C - A, D - A);
// Проверка, находится ли начало координат с той же стороны всех трех плоскостей
if (Vector3.Dot(ABC, D - A) > 0 && Vector3.Dot(ABD, C - A) > 0 && Vector3.Dot(ACD, B - A) > 0)
{
return true; // Тетраэдр содержит начало координат, объекты пересекаются
}
// Иначе, определить новое направление для поиска (это опущено для упрощения)
}
// Возвращаем false, если начало координат не содержится в симплексе
return false;
}
}
```