В физическом движке Oimo.js широкая фаза (BroadPhase) отвечает за предварительное выявление пар объектов, которые потенциально могут столкнуться. Она является первой ступенью процесса обнаружения столкновений и работает до того, как вычисляются точные контакты. Эффективная реализация BroadPhase критически важна для производительности симуляции, особенно при большом количестве тел.
Широкая фаза не вычисляет точные столкновения. Её цель — сократить число пар для дальнейшей проверки в узкой фазе (NarrowPhase). Основная идея состоит в том, чтобы быстро определить, какие объекты могут пересекаться по пространству, используя упрощённые формы — обычно AABB (Axis-Aligned Bounding Box).
Ключевые задачи BroadPhase:
В Oimo.js каждый объект сцены имеет привязанный body с физическим телом. Для BroadPhase строится AABB, который охватывает тело. Этот прямоугольный параллелепипед выравнивается по осям координат (X, Y, Z), что позволяет быстро проверять пересечения с другими AABB.
Структура AABB в Oimo.js:
{
min: { x: Number, y: Number, z: Number },
max: { x: Number, y: Number, z: Number }
}
min — минимальные координаты по каждой оси.max — максимальные координаты по каждой оси.Проверка пересечения двух AABB сводится к простому сравнению координат:
function aabbIntersect(a, b) {
return (a.min.x <= b.max.x && a.max.x >= b.min.x) &&
(a.min.y <= b.max.y && a.max.y >= b.min.y) &&
(a.min.z <= b.max.z && a.max.z >= b.min.z);
}
В Oimo.js реализованы несколько методов широкофазного обнаружения столкновений. Основные из них:
Простейший подход — сравнивать каждое тело со всеми остальными. Прост в реализации, но имеет квадратичную сложность O(n²), что делает его непригодным для сцен с большим числом объектов.
for (let i = 0; i < bodies.length; i++) {
for (let j = i + 1; j < bodies.length; j++) {
if (aabbIntersect(bodies[i].aabb, bodies[j].aabb)) {
potentialPairs.push([bodies[i], bodies[j]]);
}
}
}
Sweep and Prune — оптимизированный метод, который использует проекцию AABB на оси координат. Основная идея:
Этапы:
min.x.bodies.sort((a, b) => a.aabb.min.x - b.aabb.min.x);
for (let i = 0; i < bodies.length; i++) {
let a = bodies[i];
for (let j = i + 1; j < bodies.length; j++) {
let b = bodies[j];
if (b.aabb.min.x > a.aabb.max.x) break; // выход из внутреннего цикла
if (aabbIntersect(a.aabb, b.aabb)) potentialPairs.push([a, b]);
}
}
Sweep and Prune позволяет существенно уменьшить количество проверок, особенно при линейном распределении объектов.
Метод разбивает пространство на ячейки фиксированного размера, и каждая AABB помещается в одну или несколько ячеек. Потенциальные пересечения проверяются только между объектами, находящимися в одной ячейке.
Преимущества:
Принцип работы:
cellSize.function getCellIndex(x, cellSize) {
return Math.floor(x / cellSize);
}
BVH строится как иерархия ограничивающих объёмов, где каждый узел охватывает несколько объектов. Узкая фаза проверяет пересечения только тех узлов, чьи объёмы пересекаются. В Oimo.js эта структура менее распространена, но используется в более сложных симуляциях.
Преимущества:
Для повышения производительности Oimo.js использует следующие приёмы:
После BroadPhase формируется список потенциальных пар объектов. Этот список передается в узкую фазу, где:
Сокращение числа пар на этапе BroadPhase напрямую снижает нагрузку на узкую фазу и повышает общую производительность симуляции.
Эффективное применение BroadPhase и правильный выбор алгоритма позволяют Oimo.js масштабировать физическую симуляцию до сотен и тысяч объектов, сохраняя плавность и стабильность.