Привет, читатель!
Однажды, после просмотра ролика про эволюцию Вселенной, я загорелся идеей создать свою, со своими правилами. В итоге я написал собственный физический движок на С++, и в этой статье я расскажу про него, историю создания, и разные нюансы.

День, который всё изменил
Началось всё с желания создать вселенную со своими правилами, посмотреть на её эволюцию. Начал писать я на Python, и всё бы ничего, если бы не его скорость, а она совсем не велика. Понял я это не сразу, а после того, как написал уже рабочую версию движка, поэтому код я переносил советуясь с нейронкой, тем более язык С++ для меня пока новый. Транспортировал проект я за 1 день и был очень рад: во-первых тем, что оно вообще работает, во-вторых скоростью - то, ради чего я и затеял это.

Как это устроено
В общем существуют:
Набор законов — все в отдельном пространстве имён
Lawsв виде функций;Сам движок —
PhysicsEngine, хранит список частиц и просчитывает все законы;Визуализатор — рисует на SFML;
Вспомогательные штуки —
FrameSaverдля сохранения кадров в PNG,ConfigLoaderдля подгрузки настроек из txt.
Как это работает
В пространстве создаются частицы, каждая со своими свойствами(заданными или случайными в диапазоне).
С определённым шагом dt, просчитывается набор из правил для списка всех частиц, такие как: гравитация, инерция, коллизия, трение...
После расчётов — визуализация: частицы(цвет зависит от массы), векторы скорости(цвет зависит от скорости), активные ячейки гексагональной сетки, про которую я расскажу чуть подробнее ниже;)
Трение и эластичные столкновения
Вот две симуляции, где в одной столкновение эластичное и с маленьким трением, а во второй трение увеличено:


Оптимизация
Ну куда же в программировании физики без оптимизации. На Python 50 частиц уже предел, на C++ получилось сдвинуть порог до ~5000 частиц, но на этом я не хотел останавливаться, поэтому стал рыться в этой теме.
Алгоритмическая сложность
Скорость расчётов можно оценивать через асимптотическую сложность — то, как быстро растёт время работы при увеличении количества входных данных.
Линейная сложность —
O(N)— время растёт пропорционально числу частиц.Квадратичная сложность —
O(N²)— время растёт как квадрат числа частиц.
Так, например, для 10 000 частиц разница в количестве операций будет колоссальной: 10 000 против 100 000 000.
Что было сначала
Изначально у меня была квадратичная сложность O(N²) для всех законов. В коде это выглядело как вложенный цикл:
for (size_t i = 0; i < particles.size(); ++i) { for (size_t j = i + 1; j < particles.size(); ++j) { // проверяем взаимодействие каждой пары } }
Каждая частица "смотрела" на все остальные. Это самый медленный из возможных вариантов для парных взаимодействий.
Частично решить эту проблему мне помогла пространственная сетка!
Пространственная сетка
Я решил использовать именно гексагональную сетку, по причине её преимуществ на фоне других, и просто интереса к математике работы с ней. Подробно посмотреть про такую сетку вы можете посмотреть на этом сайте. Рекомендую, там очень классный интерактив)
Математика перевода из декартовых координат в аксиальные(с округлением в кубических) и назад:
Hex HexGrid::pixel_to_hex(const Vec2& pos) const { float q = pos.x * inv_hex_D + pos.y * inv_hex_D_sqrt3; float r = -2 * pos.y * inv_hex_D_sqrt3; float& x = q; float& z = r; float y = -x -z; int rx = round(x); int ry = round(y); int rz = round(z); float dx = abs(rx -x); float dy = abs(ry -y); float dz = abs(rz -z); if (dx > dy && dx > dz) { rx = -ry -rz; } else if (dy > dx && dy > dz) { ry = -rx -rz; } else if (dz > dy && dz > dx) { rz = -rx - ry; } return Hex(rx, rz); } Vec2 HexGrid::hex_to_pixel(const Hex& hex) const { float x = hex_D * (hex.q + hex.r * 0.5f); float y = -hex_R * 1.5f * hex.r; return Vec2(x, y); }
Я сначала это всё на листочке расписывал даже, что бы максимально понять:)
Преимущества гексагональной сетки:
Каждая ячейка равноудалена от соседей — равномерное заполнение.
Минимальное количество соседей.
Изотропность расчётов (одинаковость во всех направлениях).
Суть работы сетки
Основная суть — не просчитывать взаимодействия между всеми частицами, а "смотреть" только в соседние ячейки, что снижает сложность до линейной.
Данный фокус работает только с близкими взаимодействиями, по типу трения, столкновений. Гравитация в свою очередь дальнодействующая, поэтому такая сетка здесь ничем не поможет. Есть способы её оптимизации, но у меня она всё ещё имеет квадратичную сложность.

Я храню в классе сетки словарь:
хэш_адреса_ячейки(отдельная структура данных): список_индексов_партиклов_в_этой_ячейке
И записываю партикл в ячейку при его малейшем пересечении описанного вокруг шестиугольника круга. Затем в функции не просчитываю взаимодействие между всеми частицами, а только каждую с соседями(кроме гравитации). Чпок — и всё!
p.s. Я немного костылю, тем что записываю не id а индексы партиклов, но оно работает и пофиг как-то. А делаю так, потому что... не помню, но чёт сложно или затратно было доставать id из каждой частицы, а брать просто i из самого же цикла норм было.
Какая скорость в итоге
Я сравнил скорость расчётов физики при 1000 и 5000 элементах до и после оптимизации и получил такие циферки:
Частиц |
Без сетки |
С HexGrid |
|---|---|---|
1 000 |
~2 мс |
~1 мс |
5 000 |
~55 мс |
~7 мс |
Ну и куда без нагрузки пк до максимума:) Если что здесь без гравитации.

Не добавленные фишки
В какой-то момент я добавил искривлённое пространство, и в принципе оно было оптимизировано, но мне показалось, что задавать искривление с помощью формул слишком мало, поэтому удалил. На самом деле я думаю, верну эту штуку обратно, как будет время и желание.
У меня просто есть идея задавать искривление не формулой, которую я в виде функции пишу, а читать карту высот кастомную и в зависимости от цветов пикселей высчитывать кривизну, но не знаю как это будет оптимизировано и всё такое...
Заключение
Я получил незаменимый опыт и эмоции, делая этот проект! Всегда было желание сделать что-то подобное самому, а полученный опыт в программировании пригодится в будущих проектах.
Активно развивать в ближайшее время я его не планирую — может быть небольшие дополнения и дебаги(скорее всего верну искривление пространства). А всё потому, что скоро учебный год и времени будет меньше, но если будет желание... кто знает:)
Спасибо, что прочитали эту статью, пишите в комментариях свои мнения, идеи. Буду рад обратной связи!
Ссылки
Telegram: https://t.me/chumarno — тут я выкладываю новости по проектам чаще.
Комментарии (45)

Mavolio-Bent
11.08.2026 20:04Проект шикарный, одно замечание -- вы в гитхабе в репозиторий закинули ярлыки, аа не конфиги)

FRIMID Автор
11.08.2026 20:04Спасибо за замечание:) Я заметил, но там на всякий случай конфиги автоматом в bin рядом с exe появляются, если их нет. Но думаю действительно стоит добавить, чтобы не было путаницы.

ViskasSP1vom
11.08.2026 20:04Добавьте сразу нормальный .gitignore, чтоб бинарники и ярлыки больше не летели в репу)

Cheshir_zip
11.08.2026 20:04Видимо следующие пару месяцев буду переносить физику своего фентези рассказа на виртуальный движок. Спасибо за вдохновение.

hauserich
11.08.2026 20:04Пишете фэнтези? Я больше фантастику и мистику. плюс там много философии и психологии. Переносить нечего, отдельные законы не изобретал. :) Но вообще данный пост действительно вдохновляет... :) Вам тоже удачи!

FRIMID Автор
11.08.2026 20:04Мне очень приятно, что кого-то это вдохновило! Не могли бы вы поделиться, что за фэнтези рассказ, если это не секрет? Очень интересно было бы:)

kolabaister
11.08.2026 20:04Я думаю дальнейшее понятно. Где то там в мини-вселенной есть программист, который пишет свою микро-вселенную, а в той потом появится программист, который создаст кроха-вселенную...

kox
11.08.2026 20:04Ну тогда согласно теории Ника Бострома нас тоже кто-то довольно таки активно симулирует вместе со всеми физическими законами, числами пи, блекджеками и прочими распутными девами.)

hauserich
11.08.2026 20:04нас тоже кто-то довольно таки активно симулирует
Так это почти доказано... :)
https://erichware.name/mysli/simulator.htm

ogost
11.08.2026 20:04"ТВОЙ БЫДЛОКОД НАС ОГОРЧАЕТ" (c)

AnyKey80lvl
11.08.2026 20:04Да неплохо получилось же. Народ балансом ещё немного поработать.

ogost
11.08.2026 20:04Это отсылка к рассказу неизвестного (по крайней мере мне) автора. Если не читали, нагуглите, не разочаруетесь.

ViskasSP1vom
11.08.2026 20:04Главное чтобы этот микро-программист не начал майнить крипту в своей мини-вселенной, иначе наш хост просто сгорит от перегрева)

wataru
11.08.2026 20:04Так нельзя же тупо смотреть только на соседние ячейки. Гравитация так не работает. Так можно делать только для локальных взаимодейсвий вроде столкновений.
Есть честные O(n log n) методы для просчета гравитации. Например Barnes-Hut. Там, конечно, тоже есть неточности, но не настолько ужасные. Там тоже использутеся сетка, но все далекие объекты в далеких ячейках групперуются и заменяются точечной массой и все-таки учитываются хотя бы аггрегированно.
Если же к этой идее приложить еще и жесткую математику, то получается O(n) метод для просчета гравитации глобально (fast multipole method). В отличии от вашего хака, там есть далекое взаимодействие и точность можно приближать сколь угодно близко к идеальной.

FRIMID Автор
11.08.2026 20:04Я не сакцентировал внимание, но к гравитации я не применял никакие методы оптимизации, сетку в том числе. Я как-нибудь думаю займусь и её оптимизацией и воспользуюсь как раз способами, которые вы назвали.

wataru
11.08.2026 20:04Тогда вам стоит отредактировать статью. Пока выглядит, как будто вы код в 1000 раз соптимизировали, а по факту всего в 2-3 раза (ускорив в 1000 только 2 из 3 правил).

FRIMID Автор
11.08.2026 20:04Согласен, теперь я постарался упомянуть разницу между близкими и далёкими взаимодействиями.

Stanislavvv
11.08.2026 20:04Вспомнилась старая книжка "Этюды для программистов". Там в том числе описывалось моделирование гравитационного взаимодействия большого числа звёзд на слабых компах. Чем-то сиё напомнило.
novoku
Простой и увлечённый технический пост о переносе математической симуляции с Python на C++ для получения высокой частоты кадров при просчёте большого количества объектов. Автору респект, молодца!