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