Files
parkan-playground/MshLib/docs/MSH_FORMAT.md
T
2026-05-17 15:40:16 +03:00

348 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Документация формата MSH
Формат `.msh` используется игрой Parkan: Железная стратегия (1998) для хранения 3D-мешей.
MSH файлы — это NRes архивы, содержащие несколько типизированных компонентов.
## Обзор
Существует **два варианта** формата MSH:
| Вариант | Применение | Ключевые компоненты | Хранение треугольников |
|---------|------------|---------------------|------------------------|
| **Модель** | Роботы, здания, объекты | 06, 0D, 07 | Индексированные треугольники |
| **Ландшафт** | Террейн | 0B, 15 | Прямые треугольники |
### Автоопределение типа
```
Модель: Есть компонент 06 (индексы) И 0D (батчи)
Ландшафт: Есть компонент 0B (материалы) И НЕТ компонента 06
```
---
## Сводка компонентов
| Тип | Название | Размер элемента | Описание |
|:---:|----------|:---------------:|----------|
| 01 | Node table | 38 (0x26), редко 24 | Узлы модели / тайлы; старое имя: Pieces |
| 02 | Header + slots | 0x8C + n*68 | Общий заголовок и slot records; старое имя: Submeshes |
| 03 | Positions | 12 (0x0C) | Позиции вершин (Vector3); старое имя: Vertices |
| 04 | PackedNormals | 4 | `int8[4]`, normal = clamp(component / 127.0, -1..1) |
| 05 | PackedUV0 | 4 | `int16[2]`, uv = component / 1024.0 |
| 06 | Index buffer | 2 | Индексы вершин треугольников |
| 07 | Tri descriptors | 16 | Описатели треугольников для коллизии/пикинга |
| 08 | AnimKeyPool | 24 | Кейфреймы анимации меша |
| 0A | Node strings | переменный | Строки узлов; старое имя: ExternalRefs |
| 0B | неизвестно | 4 | неизвестно (только Ландшафт) |
| 0D | Batch table | 20 (0x14) | Батчи рендера; FParkan Res13 decimal |
| 0E | неизвестно | 4 | неизвестно (только Ландшафт) |
| 12 | MicrotextureMap | 4 | неизвестно |
| 13 | AnimMap | 2 | Карта кадров анимации, на нее указывает `AnimMapStart` из 0x01 |
| 15 | TerrainTriangle table | 28 (0x1C) | Terrain-гипотеза |
---
## Поток данных
### Модель (роботы, здания)
```
Компонент 01 (Pieces - части)
└─► Lod[n] ──► Компонент 02 (индекс сабмеша)
├─► TriStart:TriCount ──► Компонент 07 (данные на треугольник)
└─► BatchStart:BatchCount ──► Компонент 0D (батчи)
├─► IndexStart:IndexCount ──► Компонент 06 (индексы)
│ │
│ └─► Компонент 03 (вершины)
└─► BaseVertex (базовое смещение вершины)
```
### Ландшафт (террейн)
```
Компонент 01 (Тайлы, обычно 16×16 = 256)
└─► Lod[n] ──► Компонент 02 (индекс сабмеша)
└─► TriStart:TriCount ──► Компонент 15 (треугольники)
└─► Vertex1/2/3Index ──► Компонент 03 (вершины)
└─► TriStart:TriCount ──► Компонент 0B (материалы, параллельно 15)
```
**Важно:** В ландшафтных мешах поля `TriStart` и `TriCount` в Компоненте 02
используются для индексации в Компонент 15 (треугольники), а не в Компонент 07.
---
## Структуры компонентов
### Компонент 0x01 - Node table (0x26 = 38 байт)
Определяет узлы модели или тайлы terrain. Старое локальное имя: Pieces / SubMesh.
| Смещение | Размер | Тип | Поле | Описание |
|:--------:|:------:|:---:|------|----------|
| 0x00 | 2 | uint16 | Header0 | Заголовочное слово узла; старые имена: Type1 + Type2 |
| 0x02 | 2 | uint16 | ParentOrLink | Индекс родителя/ссылка; старый локальный тип int16 показывал 0xFFFF как -1 |
| 0x04 | 2 | uint16 | AnimMapStart | Начало блока в 0x13 или 0xFFFF; старое имя: OffsetIntoFile13 |
| 0x06 | 2 | uint16 | FallbackKey | Индекс fallback-ключа в 0x08; старое имя: IndexInFile08 |
| 0x08 | 30 | ushort[15] | SlotIndex | Индексы slot в 0x02 по формуле `lod * 5 + group`; старое имя: Lod |
**Ландшафт:** 256 тайлов в сетке 16×16. Каждый тайл имеет 2 LOD (индексы 0-255 и 256-511).
---
### Компонент 0x02 - Header + slots (Заголовок: 0x8C = 140 байт, slot: 0x44 = 68 байт)
#### Заголовок (140 байт)
| Смещение | Размер | Тип | Поле | Описание |
|:--------:|:------:|:---:|------|----------|
| 0x00 | 96 | Vector3[8] | BoundingBox | 8-точечный баундинг-бокс |
| 0x60 | 12 | Vector3 | Center | Центральная точка |
| 0x6C | 4 | float | CenterW | W-компонента |
| 0x70 | 12 | Vector3 | Bottom | Нижняя точка |
| 0x7C | 12 | Vector3 | Top | Верхняя точка |
| 0x88 | 4 | float | XYRadius | Радиус в плоскости XY |
#### Элемент (68 байт)
| Смещение | Размер | Тип | Поле | Описание |
|:--------:|:------:|:---:|------|----------|
| 0x00 | 2 | ushort | TriStart | Начальный индекс в Компоненте 07; в landscape-tooling может указывать в 15 |
| 0x02 | 2 | ushort | TriCount | Количество записей в Компоненте 07; в landscape-tooling может быть count для 15 |
| 0x04 | 2 | ushort | BatchStart | Начальное смещение в Компоненте 0D (только Модель) |
| 0x06 | 2 | ushort | BatchCount | Количество батчей в Компоненте 0D (только Модель) |
| 0x08 | 12 | Vector3 | LocalMinimum | Минимум локального баундинг-бокса |
| 0x14 | 12 | Vector3 | LocalMaximum | Максимум локального баундинг-бокса |
| 0x20 | 12 | Vector3 | Center | Центр сабмеша |
| 0x2C | 4 | float | SphereRadius | Радиус bounding sphere; старый `Vector4` был overlay-гипотезой |
| 0x30 | 20 | uint32[5] | Opaque | Непонятый tail, сохранять 1:1; старый `Vector5` был overlay-гипотезой |
---
### Компонент 03 - Vertices (0x0C = 12 байт)
| Смещение | Размер | Тип | Поле | Описание |
|:--------:|:------:|:---:|------|----------|
| 0x00 | 4 | float | X | Координата X |
| 0x04 | 4 | float | Y | Координата Y |
| 0x08 | 4 | float | Z | Координата Z |
---
### Компонент 06 - Indices (2 байта) - Только Модель
Массив `ushort` значений — индексы вершин треугольников.
Используются группами по 3 для каждого треугольника. Ссылки через батчи Компонента 0D.
---
### Компонент 0x07 - Tri descriptors (0x10 = 16 байт)
Описатели треугольников для коллизии/пикинга.
| Смещение | Размер | Тип | Поле | Описание |
|:--------:|:------:|:---:|------|----------|
| 0x00 | 2 | ushort | TriFlags | Флаги треугольника; старое имя: Flags |
| 0x02 | 2 | ushort | Link0 | Связь/opaque поле 0; старое имя: Magic02 |
| 0x04 | 2 | ushort | Link1 | Связь/opaque поле 1; старое имя: Magic04 |
| 0x06 | 2 | ushort | Link2 | Связь/opaque поле 2; старое имя: Magic06 |
| 0x08 | 2 | int16 | NormalX | Упакованная X-компонента нормали; старое имя: OffsetX |
| 0x0A | 2 | int16 | NormalY | Упакованная Y-компонента нормали; старое имя: OffsetY |
| 0x0C | 2 | int16 | NormalZ | Упакованная Z-компонента нормали; старое имя: OffsetZ |
| 0x0E | 2 | ushort | SelectorPacked | Три 2-битных селектора; `3` трактуется как `0xFFFF`; старое имя: Magic14 |
---
### Компонент 0B - Material Data (4 байта) - Только Ландшафт
Информация о материале/текстуре на каждый треугольник. Параллельный массив к Компоненту 15.
| Смещение | Размер | Тип | Поле | Описание |
|:--------:|:------:|:---:|------|----------|
| 0x00 | 2 | ushort | HighWord | Индекс материала/текстуры |
| 0x02 | 2 | ushort | LowWord | Индекс треугольника (последовательный) |
---
### Компонент 0x0D - Batch table (0x14 = 20 байт)
Определяет батчи вызовов отрисовки. В терминах FParkan это Res13 decimal.
| Смещение | Размер | Тип | Поле | Описание |
|:--------:|:------:|:---:|------|----------|
| 0x00 | 2 | ushort | BatchFlags / Flags.low | Флаги батча |
| 0x02 | 2 | ushort | MaterialIndex / Flags.high | Индекс material slot |
| 0x04 | 2 | ushort | Opaque4 | Opaque, старое имя `TriangleCount` не подтверждено |
| 0x06 | 2 | ushort | Opaque6 | Opaque |
| 0x08 | 2 | ushort | IndexCount | Количество индексов для отрисовки в 0x06 |
| 0x0A | 4 | uint32 | IndexStart | Начальный индекс в Компоненте 06 |
| 0x0E | 2 | ushort | Opaque14 | Opaque, старое имя `CountOf03` не подтверждено |
| 0x10 | 4 | uint32 | BaseVertex | Базовое смещение вершины в Компоненте 03 |
---
### Компонент 0x15 - TerrainTriangle table (0x1C = 28 байт)
Прямые определения terrain-треугольников. Это hex-компонент 0x15 проекта, не FParkan Res15 decimal.
| Смещение | Размер | Тип | Поле | Описание |
|:--------:|:------:|:---:|------|----------|
| 0x00 | 4 | uint32 | Flags | Флаги треугольника (0x20000 = коллизия) |
| 0x04 | 4 | uint32 | MaterialData | Данные материала; старое имя: Magic04 |
| 0x08 | 2 | ushort | Vertex1Index | Индекс первой вершины |
| 0x0A | 2 | ushort | Vertex2Index | Индекс второй вершины |
| 0x0C | 2 | ushort | Vertex3Index | Индекс третьей вершины |
| 0x0E | 4 | uint32 | Opaque0E | Opaque; старое имя: Magic0E |
| 0x12 | 4 | uint32 | Opaque12 | Opaque; старое имя: Magic12 |
| 0x16 | 4 | uint32 | Opaque16 | Opaque; старое имя: Magic16 |
| 0x1A | 2 | ushort | Opaque1A | Opaque; старое имя: Magic1A |
#### MaterialData (0x04) - Структура материала
```
MaterialData = 0xFFFF_SSPP
│ │└─ PP: Основной материал (byte 0)
│ └─── SS: Вторичный материал для блендинга (byte 1)
└────── Всегда 0xFFFF (байты 2-3)
```
| Значение SS | Описание |
|:-----------:|----------|
| 0xFF | Сплошной материал (без блендинга) |
| 0x01-0xFE | Индекс вторичного материала для блендинга |
Примеры:
- `0xFFFFFF01` = Сплошной материал 1
- `0xFFFF0203` = Материал 3 с блендингом в материал 2
---
### Компонент 0A - External References (переменный размер)
Таблица строк для внешних ссылок на части меша. Формат:
```
[4 байта: длина] [байты строки] [null-терминатор]
...повтор...
```
Длина 0 означает пустую запись. Строки типа `"central"` имеют особое значение (flag |= 1).
---
## Пример: Ландшафт SC_1
```
Land.msh (SC_1):
├── 01: 256 тайлов (сетка 16×16)
├── 02: 512 сабмешей (256 LOD0 + 256 LOD1)
├── 03: 10 530 вершин
├── 04: 10 530 данных на вершину
├── 05: 10 530 данных на вершину
├── 0B: 7 882 записи материалов
├── 0E: 10 530 данных на вершину
├── 12: 10 530 микротекстурный маппинг
└── 15: 7 882 треугольника
├── LOD 0: 4 993 треугольника (тайлы 0-255 → сабмеши 0-255)
└── LOD 1: 2 889 треугольников (тайлы 0-255 → сабмеши 256-511)
```
---
## Использование
```csharp
var converter = new MshConverter();
// Автоопределение типа и конвертация в OBJ
converter.Convert("Land.msh", "terrain.obj", lodLevel: 0);
converter.Convert("robot.msh", "robot.obj", lodLevel: 0);
// Ручное определение типа
var archive = NResParser.ReadFile("mesh.msh").Archive;
var type = MshConverter.DetectMeshType(archive);
// Возвращает: MshType.Model или MshType.Landscape
```
---
## Формат WEA - Файлы материалов ландшафта
Файлы `.wea` — текстовые файлы, определяющие таблицу материалов для ландшафта.
### Формат
```
{count}
{index} {material_name}
{index} {material_name}
...
```
### Связь с Land.msh
Каждая карта имеет два файла материалов:
| Файл | Используется для | Треугольники в Comp15 |
|------|------------------|----------------------|
| `Land1.wea` | LOD0 (высокая детализация) | Первые N (сумма TriCount для LOD0) |
| `Land2.wea` | LOD1 (низкая детализация) | Остальные |
### Пример (SC_1)
**Land1.wea:**
```
4
0 B_S0
1 L04
2 L02
3 L00
```
**Land2.wea:**
```
4
0 DEFAULT
1 L05
2 L03
3 L01
```
### Маппинг материалов
Индекс материала в `Comp15.MaterialData & 0xFF` → строка в `.wea` файле.
```
Треугольник с MaterialData = 0xFFFF0102
└─ Основной материал = 02 → Land1.wea[2] = "L02"
└─ Блендинг с материалом = 01 → Land1.wea[1] = "L04"
```
### Типичные имена материалов
| Префикс | Назначение |
|---------|------------|
| L00-L05 | Текстуры ландшафта (grass, dirt, etc.) |
| B_S0 | Базовая текстура |
| DEFAULT | Фолбэк для LOD1 |
| WATER | Вода (поверхность) |
| WATER_BOT | Вода (дно) |
| WATER_M | Вода LOD1 |
---
## Источники
- Реверс-инжиниринг `Terrain.dll` (класс CLandscape)
- Декомпиляция Ghidra: `CLandscape::ctor` и `IMesh2_of_CLandscape::Render`