Главный вопрос
Как понять, какие частицы могут столкнуться?
Если в системе 1000 частиц, обязательно ли на каждом шаге сравнивать каждую частицу со всеми остальными?
Во всех моделях одинаковы
- двумерная прямоугольная область и отражение от стенок;
- случайные небольшие изменения направления движения;
- два типа частиц разных размеров;
- общее число частиц и средняя скорость;
- правило изменения скоростей после столкновения.
Во всех анимациях половина частиц относится к типу 1, половина — к типу 2. Кнопка «Сброс» возвращает одинаковую исходную конфигурацию, а для первых трёх методов используется одинаковый фиксированный шаг времени. Поэтому при корректной работе алгоритмов число геометрических столкновений должно совпадать.
Общее правило движения
Каждый шаг направление немного случайно поворачивается, после чего частица перемещается по текущей скорости. При касании границы соответствующая компонента скорости меняет знак.
Эта часть одинакова во всех четырёх моделях. Поэтому сравнивается именно способ обработки взаимодействий, а не разные законы движения.
JavaScript · движение частицы
randomWalk(a, dt); // небольшой случайный поворот
a.x += a.vx * dt;
a.y += a.vy * dt;
if (a.x < a.r || a.x > width - a.r) a.vx *= -1;
if (a.y < a.r || a.y > height - a.r) a.vy *= -1;
0–10 минут · метод 1
Полный перебор: каждая с каждой
Самый прямой способ — для каждой частицы проверить расстояние до каждой другой частицы. Если расстояние между центрами меньше суммы радиусов, частицы пересеклись и требуется обработать столкновение.
- Берём частицу №1 и сравниваем её со всеми следующими.
- Переходим к частице №2 и снова сравниваем только со следующими.
- Для каждой пары вычисляем расстояние между центрами и сначала собираем все пересёкшиеся пары.
- После завершения поиска сортируем список пар и только затем одинаково обрабатываем все найденные столкновения.
JavaScript · поиск всех пар
const hits = [];
for (let i = 0; i < n; i++) {
for (let j = i + 1; j < n; j++) {
checks++;
if (isOverlapping(particles[i], particles[j])) {
hits.push([i, j]);
}
}
}
resolvePairs(hits);
Сильная сторона: алгоритм прост и почти невозможно «потерять» столкновение. Слабая сторона: число проверок растёт примерно как квадрат числа частиц.
10–20 минут · метод 2
Поиск по сетке: проверяем только соседние ячейки
Разобьём поверхность на квадратные ячейки. Если размер ячейки не меньше диаметра самой крупной частицы, столкнуться могут только частицы из одной и соседних ячеек. Далёкие частицы можно вообще не сравнивать.
- По координатам частицы определяем номер её ячейки.
- Для каждой занятой ячейки рассматриваем её саму и соседние ячейки.
- Точный тест расстояния выполняем только для найденных кандидатов и собираем пересёкшиеся пары.
- После поиска сортируем пары и применяем ту же реакцию на столкновение, что и в полном переборе.
JavaScript · поиск по соседним ячейкам
const hits = [];
for (const [key, ids] of grid) {
// рассматриваем только эту и соседние ячейки
for (const [i, j] of localCandidatePairs(key, ids, grid)) {
checks++;
if (isOverlapping(particles[i], particles[j])) {
hits.push([i, j]);
}
}
}
resolvePairs(hits);
Идея ускорения: точная физика столкновения не меняется. Меняется только способ быстро отбросить пары, которые находятся заведомо слишком далеко.
20–30 минут · метод 3
Список соседей: запоминаем ближайших
Если за один шаг частицы перемещаются недалеко, нет необходимости заново искать соседей каждый кадр. Для каждой частицы можно заранее составить список ближайших объектов с небольшим запасом по расстоянию и несколько шагов проверять только этот список.
- Периодически строим список пар, находящихся достаточно близко.
- Между перестроениями проверяем столкновения только внутри этого списка и собираем пересёкшиеся пары.
- Запас гарантирует, что частицы не успеют неожиданно войти в контакт.
- Найденные пары сортируются и обрабатываются в том же порядке, что и в двух предыдущих методах.
JavaScript · проверка списка соседей
buildNeighborList(); // перестраивается не каждый кадр
const hits = [];
for (const [i, j] of neighborPairs) {
checks++;
if (isOverlapping(particles[i], particles[j])) {
hits.push([i, j]);
}
}
resolvePairs(hits);
Когда это выгодно: при небольших перемещениях между соседними шагами. Сам список строить дороже, зато после построения несколько кадров выполняется мало проверок.
30–37 минут · метод 4
Вероятностное столкновение: моделируем событие, а не точную геометрию
Для задач переноса иногда важен не точный момент контакта двух нарисованных кружков, а статистическая частота взаимодействий. Тогда пространство можно разбить на ячейки и для частиц, оказавшихся в одной ячейке, разыгрывать столкновение с некоторой вероятностью.
- Размещаем частицы по ячейкам.
- Внутри каждой ячейки рассматриваем локальные пары.
- Чем больше относительная скорость и размеры частиц, тем выше вероятность события.
- Если случайное число меньше P, направления скоростей изменяются как после столкновения.
Формула в этой учебной модели не претендует на универсальный физический закон. Она показывает сам принцип: точное геометрическое событие заменяется статистическим правилом с управляемой вероятностью.
JavaScript · вероятностное событие
for (const ids of grid.values()) {
for (let a = 0; a < ids.length; a++)
for (let b = a + 1; b < ids.length; b++) {
const A = particles[ids[a]], B = particles[ids[b]];
checks++;
const vRel = Math.hypot(A.vx-B.vx, A.vy-B.vy);
const p = 1 - Math.exp(-7.5*vRel*(A.r+B.r)*dt
/(cellSize*cellSize));
if (collisionRng() < p) {
resolveStochastic(A, B);
collisions++;
}
}
}
Это уже другой уровень модели. Первые три метода меняют в основном алгоритм поиска кандидатов, сохраняя геометрическое условие столкновения. Здесь мы заменяем точное столкновение вероятностным событием и поэтому получаем приближённую статистическую модель.
Поэтому счётчик событий здесь нельзя напрямую сравнивать со счётчиком геометрических столкновений в первых трёх моделях.
37–39 минут · сравнение
Что именно отличается?
| Метод | Как ищутся взаимодействия | Что сохраняется | Главный компромисс |
|---|---|---|---|
| Все пары | Проверяется каждая возможная пара | Точная геометрия контакта | Очень быстро растёт число проверок |
| Сетка | Только частицы из близких ячеек | Точная геометрия контакта | Нужно каждый шаг распределять частицы по ячейкам |
| Список соседей | Проверяется заранее сохранённый список близких пар | Точная геометрия контакта | Список нужно периодически перестраивать |
| Вероятностный | Локальные пары внутри ячейки разыгрывают событие | Статистические свойства, а не точная траектория | Результат зависит от выбранного вероятностного правила |
39–40 минут · подведение итогов
Выходной билет
Краткий письменный ответ: 3–4 предложения
Почему сетка и список соседей могут ускорить моделирование, не изменяя само геометрическое правило столкновения? Чем принципиально отличается от них вероятностная модель?
Основной принцип: в большой системе важно разделять поиск возможных взаимодействий и правило, по которому взаимодействие действительно происходит.