Пилю физический движок и автосимулятор на нём.
Кажется, я уже перерос libgdx и он сейчас больше мешает, чем помогает. Кто-то писал что к godot легко прикрутить свой физический движок — жду в комментариях рассказа как это сделать и ссылок с примерами.
Штуки, которых не было из коробки и я писал их сам:
1. Поддержка руля с force feedback через SDL.
2. Привязка звука к объектам в 3д. Подключил miniaudio, теперь право-лево и ближе-дальше нормально слышно.
3. Нет быстрой ECS. (Тут больше претензии к особенностям JVM, чем к libgdx).
В чём проблема с ECS — в языках типа С++ ECS может разложить все компоненты одного типа просто в один массив, доступ к ним будет линейным чтением из памяти, а в случае java объекты раскиданы как попало и на это никак не повлиять.
В итоге у меня получился самописный франкенштейн, в котором почти всё удалось вынести в компоненты, но один тип пришлось оставить в виде поля в Entity. Потому что физический движок очень часто лезет в компонент для состояния физического тела, где хранятся положение, скорость, момент инерции и аккумулятор для сил. Причём доступ в случайном порядке. Например, для добавления силы от пружины надо посмотреть места двух точек крепления у двух разных нет.
Я сделал два эксперимента и в обоих производительность проседала в разы. В итоге я решил что скорость важнее красоты кода и оставил это поле в Entity.
Эксперимент 1: как и для остальных компоненов, просто использовал HashMap[Entity, PhysicsBody] и вместо обращеня к полю лазил туда. Ужасно медленно.
Эксперимент 2: Заменил тип Entity на Int, для PhysicsBody завёл массив, где по индексу лежал нужный объект.
Производительность просела раза в два-три, и кажется JIT стал хуже справляться, т.к. производительность была очень нестабильной и где-то через десяток секунд устаканаливалась на чём-то стабильном, но раза в два-три более медленном чем с доступом к полю в Entity.
В эту неделю попробовал ради эксперимента сделать в игре модель мотоцикла. И это офигенно получилось — почти сразу после начальных настроек он поехал. Причём, как и настоящий мотоцикл, на скорости он был устойчивый, а рулить надо было контррулением. Это достигается за счёт угла наклона вилки и трейла.
В целом это очень круто и значит, что мой физический движок вполне нормально работает и его можно использовать для чего-то серьёзного. Кажется, что он нормально обрабатывает штуки типа прецессии вращающихся тел, которую аналитически в случае с кучей тел фиг посчитаешь.

Тут могло бы быть видео, но форум не даёт его загрузить файлом.
Проблемы, которые я не решил (и почему хочу перейти к чему-то типа Godot/Unity/UE):
В libgdx нет редактора объектов. Я не могу просто взять сцену, накидать на неё компонентов колес, пружин, амортизаторов, двигателя, коробки передач, дифференциала и в редакторе движка собрать из них транспортное средство. И ассета из этой штуки я тоже не сделаю.
Вместо этого приходится описывать все взаимосвязи кодом, получается очень долго. Вдобавок приходится отвлекаться на всякие штуки типа звука, ввода-вывода, графики, логики движения камеры и т.п. Сейчас это всё самописное, хотелось бы использовать что-то готовое и не отвлекаться.
В качестве эксперимента накидал плоскую трассу из линий. Вообще мне понравился такой подход — не надо думать про текстуры и тратить время на детализацию. Можно просто накидать контуры в Blender и закинуть в движок. Этого достаточно, чтобы как-то оценить управляемость машины и сопоставить размеры моделей. Вся карта в obj весит около 50 килобайт и легко парсится самодельным парсером. Стандартный парсер obj от libgdx ожидает увидеть полигоны вместо линий и эти файлики не переваривает.

Попробовал загрузить ресурсы из Ассето Корсы. Формат хранения у них бинарный, но довольно простой и на гитхабе уже есть код, который его парсит: github.com/RaduMC/kn5-converter/blob/master/kn5%20converter/Program.cs
Осталось только под свой язык программирования переписать.
Если кратко по формату и организации сцены в формате kn5 — сцена состоит из дерева нодов. У каждой ноды есть имя и список дочерних. Видов нод три — нода с матрицей преобразования, нода с мешем и нода с анимированным мешем. В файлах попадаются пустые ноды — кажется, их ещё используют в качестве зашлушек, чтобы потом по этой позиции добавить колесо или ещё что-то.

Я пока в своём минималистичном стиле нарисовал только линии, без полигонов, каждую ноду своим рандомным цветом. Но кажется у меня какая-то ошибка в индексах — куски земли как-то очень плотно залиты линиями и треугольников не видно.
Так же прямо внутри хранятся текстуры в dds и какие-то параметры материалов для шейдеров. Но сами шейдеры хз где.

Поправил загрузку, добавил текстуры.
Много времени потратил на борьбу с libgdx. На мой взгляд в этом движке много оверинжиниринга и какие-то простые вещи делаются сложно, а вдобавок чтобы использовать libgdx, надо знать и как работает openGL, и как работают велосипеды в движке поверх него. А для чего-то сложного движок слабо подходит, например поддержки загрузки dds текстур в движке нет
. Я нашёл какую-то внешнюю библиотеку (gdx-dds), но она на попытке загрузки каких-то текстур кидается исключениями. В итоге я вместо вызова «загрузить текстуру» трачу время.
На мой взгляд пример оверинжиниринга: RenderContext.java — чтобы его использовать, надо целиком прочитать его код, и потом понять что под капотом он что-то запоминает и как-то вызывает openGL
Больше всего я сгорел с бага с текстурными координатами. Попробуйте найти ошибку:


Штуки, которых не было из коробки и я писал их сам:
1. Поддержка руля с force feedback через SDL.
2. Привязка звука к объектам в 3д. Подключил miniaudio, теперь право-лево и ближе-дальше нормально слышно.
3. Нет быстрой ECS. (Тут больше претензии к особенностям JVM, чем к libgdx).
В чём проблема с ECS — в языках типа С++ ECS может разложить все компоненты одного типа просто в один массив, доступ к ним будет линейным чтением из памяти, а в случае java объекты раскиданы как попало и на это никак не повлиять.
В итоге у меня получился самописный франкенштейн, в котором почти всё удалось вынести в компоненты, но один тип пришлось оставить в виде поля в Entity. Потому что физический движок очень часто лезет в компонент для состояния физического тела, где хранятся положение, скорость, момент инерции и аккумулятор для сил. Причём доступ в случайном порядке. Например, для добавления силы от пружины надо посмотреть места двух точек крепления у двух разных нет.
Я сделал два эксперимента и в обоих производительность проседала в разы. В итоге я решил что скорость важнее красоты кода и оставил это поле в Entity.
Эксперимент 1: как и для остальных компоненов, просто использовал HashMap[Entity, PhysicsBody] и вместо обращеня к полю лазил туда. Ужасно медленно.
Эксперимент 2: Заменил тип Entity на Int, для PhysicsBody завёл массив, где по индексу лежал нужный объект.
Производительность просела раза в два-три, и кажется JIT стал хуже справляться, т.к. производительность была очень нестабильной и где-то через десяток секунд устаканаливалась на чём-то стабильном, но раза в два-три более медленном чем с доступом к полю в Entity.
В эту неделю попробовал ради эксперимента сделать в игре модель мотоцикла. И это офигенно получилось — почти сразу после начальных настроек он поехал. Причём, как и настоящий мотоцикл, на скорости он был устойчивый, а рулить надо было контррулением. Это достигается за счёт угла наклона вилки и трейла.
В целом это очень круто и значит, что мой физический движок вполне нормально работает и его можно использовать для чего-то серьёзного. Кажется, что он нормально обрабатывает штуки типа прецессии вращающихся тел, которую аналитически в случае с кучей тел фиг посчитаешь.

Тут могло бы быть видео, но форум не даёт его загрузить файлом.
Проблемы, которые я не решил (и почему хочу перейти к чему-то типа Godot/Unity/UE):
В libgdx нет редактора объектов. Я не могу просто взять сцену, накидать на неё компонентов колес, пружин, амортизаторов, двигателя, коробки передач, дифференциала и в редакторе движка собрать из них транспортное средство. И ассета из этой штуки я тоже не сделаю.
Вместо этого приходится описывать все взаимосвязи кодом, получается очень долго. Вдобавок приходится отвлекаться на всякие штуки типа звука, ввода-вывода, графики, логики движения камеры и т.п. Сейчас это всё самописное, хотелось бы использовать что-то готовое и не отвлекаться.
UPD 2024-12-03
В качестве эксперимента накидал плоскую трассу из линий. Вообще мне понравился такой подход — не надо думать про текстуры и тратить время на детализацию. Можно просто накидать контуры в Blender и закинуть в движок. Этого достаточно, чтобы как-то оценить управляемость машины и сопоставить размеры моделей. Вся карта в obj весит около 50 килобайт и легко парсится самодельным парсером. Стандартный парсер obj от libgdx ожидает увидеть полигоны вместо линий и эти файлики не переваривает.

UPD 2024-12-04
Попробовал загрузить ресурсы из Ассето Корсы. Формат хранения у них бинарный, но довольно простой и на гитхабе уже есть код, который его парсит: github.com/RaduMC/kn5-converter/blob/master/kn5%20converter/Program.cs
Осталось только под свой язык программирования переписать.
Если кратко по формату и организации сцены в формате kn5 — сцена состоит из дерева нодов. У каждой ноды есть имя и список дочерних. Видов нод три — нода с матрицей преобразования, нода с мешем и нода с анимированным мешем. В файлах попадаются пустые ноды — кажется, их ещё используют в качестве зашлушек, чтобы потом по этой позиции добавить колесо или ещё что-то.

Я пока в своём минималистичном стиле нарисовал только линии, без полигонов, каждую ноду своим рандомным цветом. Но кажется у меня какая-то ошибка в индексах — куски земли как-то очень плотно залиты линиями и треугольников не видно.
Так же прямо внутри хранятся текстуры в dds и какие-то параметры материалов для шейдеров. Но сами шейдеры хз где.

UPD 2024-12-13
Поправил загрузку, добавил текстуры.
Много времени потратил на борьбу с libgdx. На мой взгляд в этом движке много оверинжиниринга и какие-то простые вещи делаются сложно, а вдобавок чтобы использовать libgdx, надо знать и как работает openGL, и как работают велосипеды в движке поверх него. А для чего-то сложного движок слабо подходит, например поддержки загрузки dds текстур в движке нет
. Я нашёл какую-то внешнюю библиотеку (gdx-dds), но она на попытке загрузки каких-то текстур кидается исключениями. В итоге я вместо вызова «загрузить текстуру» трачу время.
На мой взгляд пример оверинжиниринга: RenderContext.java — чтобы его использовать, надо целиком прочитать его код, и потом понять что под капотом он что-то запоминает и как-то вызывает openGL
Больше всего я сгорел с бага с текстурными координатами. Попробуйте найти ошибку:
val mesh = new Mesh(
true,
node.verticesData.length,
node.indices.length,
new VertexAttribute(VertexAttributes.Usage.Position, 3, ShaderProgram.POSITION_ATTRIBUTE),
new VertexAttribute(VertexAttributes.Usage.Normal, 3, ShaderProgram.NORMAL_ATTRIBUTE),
new VertexAttribute(VertexAttributes.Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE),
new VertexAttribute(VertexAttributes.Usage.Tangent, 3, ShaderProgram.TANGENT_ATTRIBUTE),
)
Ошибка в том, что для текстурный хоординат должно быть такShaderProgram.TEXCOORD_ATTRIBUTE + "0"
В движке ни намёка на то, что текстурные координаты должны быть с нулём или ещё какой-то цифрой в конце.
UPD 2024-12-15
По совету Mr F попробовал использовать MSAA и включить alpha to coverage. Это прекрасно! Офигенная, забытая технология древних. С MSAA 16x получилась почти идеальная картинка для полупрозрачных объектов буквально добавлением пары строчек. Артефакты при желании можно увидеть, но усложнять код и использовать более сложные подходы я не хочу. Сейчас у меня осовной упор именно на проработку физики, графика подойдёт почти любая.
Потом это через виртуализацию разруливается на калсайте. Да, сдеградируешь на вызовах функций, но зато выигрываешь по плотности укладки в памяти.
Если совсем с ума сойти, можно ваще сделать «value types у нас дома» через VarHandle + byte[]. Производительность должна быть на уровне, тк JIT потом это всё вклеивает нормально.
конечно, это крайне костыльно, потому что JVM:(
Кстати насчёт кодогенерации и прочих ухищрений — в скале есть макросы и inline функции, можно с их помощью что-то навертеть.
там норм валью тайпы есть
Как я сделал — скачал экспериментальную сборку JDK, распаковал. Скачал отсюда: jdk.java.net/valhalla/. Прям в папочку bin положил класс с файлом и вызвал его как /java --enable-preview --source 23 ValuePoint.java
Без слова value шаг вычисления внутри цикла завершается за 0.7 мс, с ним за 1.3мс.
чёто это не вяжется с тем что они на презентациях показывали))
Результат по итогу вот такой:
терпите, джавики.
вот тебе, блять, 10 лет люди работали. жаль джава не умерла.
изменяемый, с final полями, record и Value класс. Rysen 3700x, VM version: JDK 23.0.1, OpenJDK 64-Bit Server VM, 23.0.1+11-39
Вот мой код с уже готовым проектом если кто захочет по-быстрому попробовать.
Интересен именно момент, когда один мотоциклист пинает другого, влияя на стабильность аппарата на высокой скорости
Вообще тема мотоциклов мне интересна, но без вестибулярного аппарата и на гоночном круглом руле управлемость как у пьяного самокатчика. Это совсем не то, что я чувствовал на реальном мотоциле.
Может быть, что-то такое можно сделать, если как в игральных автоматах поставить модель мотоцикла, сделать мощный и точный большой руль с force feedback и ещё какие-нибудь приводы для наклона мотоцикла влево-вправо (тоже с force feedback, чтобы чувствовать баланс веса игрока). Потому что мотоцикл рулится контррулением и это очень требовательно к точности и скорости реакции руля.
Если нет способа размещать компоненты в памяти последовательно, то сделать быструю ECS вряд ли выйдет.
Я отсюда не вижу, как там у тебя в коде, но идея Entity-Component-System — это когда System обходит свой массив(ы) из Component без обращения к Entity или компонентам других систем. В примере из топика все данные должны быть в PhysicsBody, и обходить массив нужно по порядку, напрямую обращаясь к компоненту, а НЕ через Entity прыгать. Хеш-таблицы обычно располагают данные локально, но не последовательно, у аппаратного префетчера с этим могут быть проблемы. К тому же массивы (или пулы) можно сортировать, например, если два компонента взаимодействуют (как пружина в примере), их можно разместить подряд. Если каждый кадр по чуть-чуть перетасовывать, уже через несколько секунд всё будет разложено по порядку.
В этом случае возникает проблема дублирования данных, например, координаты объекта нужны и для графики, и для физики. Здесь уже по ситуации — если обращение на чтение системами своих компонентов происходит чаще, чем изменение их данных (графика ничего не меняет, а физика, например, на 90% из статических или спящих объектов), тогда имеет смысл поля координат сделать и в компоненте графики, и в компоненте физики, и в случае изменения проводить синхронизацию копированием этих полей. Если все объекты физики постоянно двигаются, тогда имеет смысл выделить координаты в отдельный компонент (типа Transform), разместить эти компоненты в массив, который расположить «достаточно близко» к массивам с компонентами графики и физики.
В любом случае нужно отслеживать «горячие» и «холодные» поля разных компонентов и учитывать по этому признаку тоже, а не только интуитивно делить на «графику и физику». В редакторе движка деление по типам компонентов может быть стандартным, а внутри движка данные можно распределять в памяти по-другому, если нужно.
Мне кажется, разместить два объекта для пружины подряд не получится, потому что к объекту может быть присоединено много разных пружин. Но в целом идея хорошая, может для чего-то ещё подойдёт. Я точно знаю, что так можно сделать иерархию объектов и положить всех детей после родителя, но у меня сейчас нет иерархии в принципе :)
Кстати, для итерации конкретно по пружинам я их в линейный массив складываю и по нему бегу, а не по HashMap. Так действительно быстрее.
Про дублирование данных я почему-то вообще не думал.
Но в общем и целом JVM не даёт возможности класть объекты в памяти как захочется и ECS радикальных бонусов производительности не даёт. Но мне оно просто понравилось как способ огранизации кода и масштабировая программы дописыванием новых компонент вместо выстраивания сложной иерархии.
У меня ещё сильный упор на то, что код должно быть легко писать и менять, чтобы я в одиночку быстро его писал и не сильно страдал от багов, с JVM в этом плане попроще.