Поверхности SUR
🛰️
Поверхности SUR

💥 Обзор

Данные о зоне столкновения для жестких моделей (.cmp и .3db).

Важно понимать, что данные, содержащиеся в .sur, не имеют смысла без иерархии составных моделей из .cmp, поскольку для правильного расположения корпусов зоны столкновения необходимы преобразования отдельных частей из Cmpnd.

Обратите внимание, что некоторые структуры содержат необычные длины битов для чисел.

📎 Заголовок

Имя Тип Описание
signature uint32 Должно быть 0x73726576 ("vers").
version float Должно быть 2.0.
parts varying
  • Счетчика деталей нет, поэтому чтение выполняется в цикле до конца файла.

📌 Part

Имя Тип Описание
partId uint32 FLCRC32 хэш имени объекта.
sectionCount uint32 Количество секций в детали.
section varying
  • PartId — это хэш FLCRC32 Object name из parts в Cmpnd для многокомпонентной модели (.cmp) или 0x0 для однокомпонентной модели (.3db).
  • Точный порядок разделов, похоже, не имеет значения, но все файлы Freelancer имеют одинаковый порядок: нефиксированный, экстент, сетка, точки крепления.

💢 Нефиксированная секция

Имя Тип Описание
name uint32 Должно быть 0x64786621 ("!fxd").
  • При наличии в детали указывает, что эта деталь подвижна.
  • Корневая деталь для .cmp или пустая деталь для .3db всегда должна иметь флаг раздела «нефиксированный».
  • Для других деталей он должен присутствовать, если связанный с ним составной объект не имеет фиксированного типа соединения (Rev, Pris и т. д.).
  • В этой секции нет дополнительных данных, только собственное имя.

🔥 Раздел «Объем»

Имя Тип Описание
name uint32 Должно быть 0x73747865 («exts»).
minimum float[3] Координаты минимальной точки.
maximum float[3] Координаты максимальной точки.
  • Ограничительная рамка для детали, вероятно, используется для сравнения AABB. Минимальное/максимальное значение из точек сетки.

💢 Раздел «Точки крепления»

Имя Тип Описание
name uint32 Должно быть 0x64697068 («hpid»).
count uint32 Количество точек крепления.
hardpointIds uint32[] Хеш FLCRC32 имени точки крепления.
  • В списке указано, какие точки крепления будут иметь собственные хитбоксы, переопределенные предоставленным корпусом с тем же ID. В противном случае прикрепленные объекты сохранят свои собственные хитбоксы.
  • В стандартных моделях для точек крепления HpWeapon использовались коробки, для точек крепления HpTurret — полусферы, а для точек крепления оборудования, таких как HpCM, HpThruster, — своего рода цилиндры.
  • Предоставление корпуса для HpMount и указание его в качестве точки крепления переопределит защитный пузырь корабля.

💨 Раздел «Поверхности»

Содержит заголовок, выпуклые оболочки, массив точек и дерево BSP.

Имя Тип Описание
name uint32 Должно быть 0x66727573 («surf»).
size uint32 Длина байта поверхностного сечения от этого смещения.
center float[3] Смещение центра ограничивающей сферы.
drag float[3] Вектор линейного сопротивления. Должен быть больше 0. Он работает вместе с параметром mass, заданным для детали в файлах ini, например [CollisionGroup].
radius float Радиус ограничивающей сферы.
scale uint8 Мультипликатор ограничивающей сферы только для очков корпуса.
treeEnd uint24 Смещение до конца дерева BSP.
treeStart uint32 Смещение до начала дерева BSP.
unknown2 float[3] Неизвестный вектор.
hulls varying Выпуклые оболочки.
points varying Буфер вершин.
nodes varying Узлы BSP.
  • Размер поверхностного сечения не включает длину байта имени.
  • Ограничивающая сфера охватывает все точки корпуса.
  • Ограничивающая сфера с радиусом * масштабом охватывает только точки корпуса, не являющиеся точками крепления.
  • Считывайте масштаб как байт без знака и делите на 0xFA.
  • Смещения начала и конца дерева BSP относительны по отношению к смещению сечения.

Сразу после заголовка раздела перечислены все выпуклые оболочки.

Имя Тип Описание
offsetToPoints uint32 Смещение блока точек относительно самого себя.
partId uint32 Хеш имени объекта или смещение к узлу в дереве BSP в зависимости от типа, указанного ниже.
type uint8 Тип корпуса.
refCount uint24 Количество ссылок в DWORD (шаг 4 байта).
faceCount uint16 Количество поверхностей.
unknown uint16 Отступы?
faces varying
  • Считывайте корпуса, пока смещение не достигнет offsetToPoints.
  • Тип 4 — обычный корпус (но является жесткой точкой, если partID указан в разделе hpid).
  • Тип 5 — обертка. Используется для группировки нескольких корпусов, по одному на сетку. Отсутствует, если сетка содержит только один корпус.
  • Если корпус является оберткой, его partID будет смещен к узлу в дереве BSP.
  • Счетчик ссылок — это количество ссылок на точки треугольника + заголовок оболочки в DWORD: (12 + triangle.count * 6) / 4.
  • Выпуклая оболочка всегда будет иметь четное количество треугольников, поскольку симплекс состоит из четырех треугольников, деление ребер разделяет соседние грани пополам, а подразделение граней заменяет одну на три.

Грани корпуса представляют собой треугольники с тремя ребрами.

Имя Тип Описание
faceIndex uint12 Этот индекс поверхности.
oppositeFaceIndex uint12 Индекс противоположной поверхности.
unknown bit[7]
flag bit Флаг поверхности.
edges int32[3]
  • Прочитать столько же строк, сколько указано в заголовке корпуса.

Каждая грань корпуса содержит три ребра:

Имя Тип Описание
pointIndex uint16 Индекс точки.
adjacentOffset int15 DWORD смещение до соседнего ребра от этого ребра.
flag bit Флаг ребра.
  • Заголовок грани содержит индекс грани (12 бит), индекс противоположной грани (12 бит), неизвестный (7 бит) и флаг (1 бит).
  • Бит флага грани/ребра установлен для обернутых корпусов и снят для всех других корпусов.
  • Согласно Adoxa, индекс противоположной грани, по-видимому, не используется в игре. Obj2sur по умолчанию устанавливает его равным 1 для всех граней, кроме первой.
  • Каждая грань является общей для двух граней, так как корпуса не могут иметь отверстий.
  • Каждая грань состоит из 4 DWORD, смещение соседней грани равно количеству DWORD от смещения текущей грани DWORD.

После корпусов перечисляется ряд пунктов:

Имя Тип Описание
position float[3] Координаты точки.
partId uint32 FLCRC32 хэш объекта.
  • Точки считываются до начала смещения узлов.
  • Наличие PartID в каждой точке кажется излишним, учитывая, что каждый корпус и так имеет PartID, но так уж сложилось.

Наконец, поверхностный раздел содержит дерево BSP, где каждый узел представляет собой:

Имя Тип Описание
childOffset int32 Смещение к правому дочернему элементу.
hullOffset int32 Смещение относительно корпуса.
center float[3] Центр ограничивающей рамки.
size float Ограничивающая рамка однородного базового размера.
scale uint8[3] Масштаб осей размеров ограничивающей рамки.
padding uint8 Неиспользованное значение заполнения.
  • Узлы дерева считываются до смещения конца узлов в заголовке.
  • Смещение дочернего узла относительно себя и равно 0 для конечных точек.
  • Смещение корпуса относительно себя, отрицательное число и равно 0 для узлов ветвей.
  • Считывайте компоненты множителя оси как байт без знака, разделите 0xFA и масштабируйте размер базовой ограничительной рамки по ним.
  • Сетки с одним корпусом не будут иметь усадки и будут иметь только один узел.

Каждый корпус должен быть выпуклым, в противном случае при столкновениях может возникнуть непредсказуемое поведение, начиная от нерегистрации ударов и заканчивая полным сбоем игры. Похоже, что корпуса в файлах Freelancer генерируются с помощью алгоритма quickhull. Некоторые программы для моделирования предоставляют инструменты для генерации выпуклых корпусов из массива точек с помощью этого алгоритма: MassFX/PhysX в Autodesk 3Ds Max, Convex Tool в Blender и т. д. Рекомендуется, чтобы количество вершин было как можно меньше, а их равномерное распределение по сетке, по-видимому, повышает стабильность. Оптимальное количество вершин для выпуклой сетки составляет 30-40. Как правило, следует избегать применения инструмента convex tool к фактической видимой сетке, вместо этого создайте гораздо более упрощенную геометрию над видимой сеткой и используйте ее. Даже выпуклые сетки могут не работать в Freelancer, если они слишком детализированы или их слишком много.

Обычно секция сетки содержит идентификаторы корпуса, соответствующие идентификаторам детали, содержащей секцию сетки, однако могут присутствовать дополнительные корпуса для точек крепления, а иногда и для фиксированных составных дочерних элементов, что приводит к дублированию корпусов: родительская деталь имеет корпуса для дочерней детали (и эти идентификаторы корпусов соответствуют идентификатору дочерней детали), в то время как дочерняя деталь также имеет свои собственные корпуса. Однако это не всегда так, не каждая хитбокс в модели Freelancer, по-видимому, создана с таким поведением, некоторые да, а другие нет. Это может быть связано с разными версиями экспортера, производящими эти дубликаты, или, возможно, с чем-то, связанным с разрушаемыми частями. Пример дубликатов корпусов родитель-потомок можно найти в li_elite.sur, корневой части, содержащей корпуса для крыльев, несмотря на то, что части крыльев имеют свои собственные корпуса.

В части, которая имеет более одного корпуса, один специальный корпус (тип 5) будет охватывать все обычные корпуса (тип 4) и корпуса/упаковки всех фиксированных дочерних элементов, которые имеет связанный составной объект. Это похоже на случай, когда корпус генерируется над всеми точками в буфере точек сетки.

Однако оболочка сжатия не будет присутствовать, если сетка имеет только одну оболочку, а связанный составной объект не имеет фиксированных дочерних элементов.