💥 Обзор
Данные о зоне столкновения для жестких моделей (.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) и корпуса/упаковки всех фиксированных дочерних элементов, которые имеет связанный составной объект. Это похоже на случай, когда корпус генерируется над всеми точками в буфере точек сетки.
Однако оболочка сжатия не будет присутствовать, если сетка имеет только одну оболочку, а связанный составной объект не имеет фиксированных дочерних элементов.