aboutsummaryrefslogtreecommitdiff
path: root/docs/specs/materials-texm.md
blob: 4c8c8f4c3a6c33136fae56165fdded1bf46623f3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
# Materials + Texm

Документ описывает материалы, текстуры, палитры, блоки `WEAR` / `LIGHTMAPS` и формат `Texm`.

---

## 2.1. Архитектура материальной системы

Материальная подсистема реализована в `World3D.dll` и включает:

- **Менеджер материалов** (`LoadMatManager`) — объект размером 0x470 байт (1136), хранящий до 140 таблиц материалов (поле `+572`, `this[143]`).
- **Библиотека палитр** (`SetPalettesLib`) — NRes‑архив с палитрами.
- **Библиотека текстур** (`SetTexturesLib`) — путь к файлу/каталогу текстур.
- **Библиотека материалов** (`SetMaterialLib`) — NRes‑архив с данными материалов.
- **Библиотека lightmap'ов** (`SetLightMapLib`) — опциональная.

### Загрузка палитр (sub_10002B40)

Палитры загружаются из NRes‑архива по именам. Система перебирает буквы `'A'`..'Z'` (26 категорий) × 11 суффиксов, формируя имена вида `"A<suffix>.pal"`. Каждая палитра загружается через `niOpenResFile``niReadData` и регистрируется как текстурный объект в графическом движке.

Максимальное количество палитр: 26 × 11 = **286**.

## 2.2. Запись материала (76 байт)

Материал представлен записью размером **76 байт** (19 DWORD). Поля восстановлены из функции интерполяции `sub_10003030` и функций `sub_100031F0` / `sub_10003680`.

| Смещение | Размер | Тип    | Интерполяция | Описание                             |
|----------|--------|--------|--------------|--------------------------------------|
| 0        | 4      | uint32 | Нет          | `flags` — тип/режим материала        |
| 4        | 4      | float  | Бит 1 (0x02) | Цветовой компонент A — R             |
| 8        | 4      | float  | Бит 1 (0x02) | Цветовой компонент A — G             |
| 12       | 4      | float  | Бит 1 (0x02) | Цветовой компонент A — B             |
| 16       | 4      | —      | Нет          | Зарезервировано / паддинг            |
| 20       | 4      | float  | Бит 0 (0x01) | Цветовой компонент B — R             |
| 24       | 4      | float  | Бит 0 (0x01) | Цветовой компонент B — G             |
| 28       | 4      | float  | Бит 0 (0x01) | Цветовой компонент B — B             |
| 32       | 4      | float  | Бит 4 (0x10) | Скалярный параметр (power / opacity) |
| 36       | 4      | float  | Бит 2 (0x04) | Цветовой компонент C — R             |
| 40       | 4      | float  | Бит 2 (0x04) | Цветовой компонент C — G             |
| 44       | 4      | float  | Бит 2 (0x04) | Цветовой компонент C — B             |
| 48       | 4      | —      | Нет          | Зарезервировано / паддинг            |
| 52       | 4      | float  | Бит 3 (0x08) | Цветовой компонент D — R             |
| 56       | 4      | float  | Бит 3 (0x08) | Цветовой компонент D — G             |
| 60       | 4      | float  | Бит 3 (0x08) | Цветовой компонент D — B             |
| 64       | 4      | —      | Нет          | Зарезервировано / паддинг            |
| 68       | 4      | int32  | Нет          | `textureIndex` — индекс текстуры     |
| 72       | 4      | int32  | Нет          | Дополнительный параметр              |

### Маппинг компонентов на D3D Material (предположительный)

По аналогии со стандартной структурой `D3DMATERIAL7`:

| Компонент    | Вероятное назначение | Биты интерполяции |
|--------------|----------------------|-------------------|
| A (+4..+12)  | Diffuse (RGB)        | 0x02              |
| B (+20..+28) | Ambient (RGB)        | 0x01              |
| C (+36..+44) | Specular (RGB)       | 0x04              |
| D (+52..+60) | Emissive (RGB)       | 0x08              |
| (+32)        | Specular power       | 0x10              |

### Поле textureIndex (+68)

- Значение `< 0` означает «нет текстуры» → `texture_ptr = NULL`.
- Значение `≥ 0` используется как индекс в глобальном массиве текстурных объектов: `texture = texture_array[5 * textureIndex]`.

## 2.3. Алгоритм интерполяции материалов

Движок поддерживает **анимацию материалов** между ключевыми кадрами. Функция `sub_10003030`:

```
Вход: mat_a (исходный), mat_b (целевой), t (фактор 0..1), mask (битовая маска)

Выход: mat_result

Для каждого бита mask:
  если бит установлен:
    mat_result.component = mat_a.component + (mat_b.component - mat_a.component) × t
  иначе:
    mat_result.component = mat_a.component  (без интерполяции)

mat_result.textureIndex = mat_a.textureIndex  (всегда копируется без интерполяции)
```

### Режимы анимации материалов

Материал может иметь несколько фаз (phase) с разными режимами цикличности:

| Режим (flags & 7) | Описание                            |
|-------------------|-------------------------------------|
| 0                 | Цикл: повтор с начала               |
| 1                 | Ping‑pong: туда‑обратно             |
| 2                 | Однократное воспроизведение (clamp) |
| 3                 | Случайный кадр (random)             |

## 2.4. Глобальный массив текстур

Текстуры хранятся в глобальном массиве записей по **20 байт** (5 DWORD):

```c
struct TextureSlot {           // 20 байт
    int32_t  name_hash;       // +0:  Хэш/ID имени текстуры (-1 = свободен)
    void*    texture_object;   // +4:  Указатель на объект текстуры D3D
    int32_t  ref_count;        // +8:  Счётчик ссылок
    uint32_t last_release;     // +12: Время последнего Release
    uint32_t extra;            // +16: Дополнительный флаг
};
```

Функция `UnloadAllTextures` обнуляет все слоты, вызывая деструктор для каждого ненулевого `texture_object`.

## 2.5. Глобальный массив определений материалов

Определения материалов хранятся в глобальном массиве записей по **368 байт** (92 DWORD):

```c
struct MaterialDef {            // 368 байт (92 DWORD)
    int32_t  name_hash;        // dword_100669F0[92*i]:  -1 = свободен
    int32_t  ref_count;        // dword_100669F4[92*i]:  Счётчик ссылок
    int32_t  phase_count;      // dword_100669F8[92*i]:  Число текстурных фаз
    void*    record_ptr;       // dword_100669FC[92*i]:  Указатель на массив записей по 76 байт
    int32_t  anim_phase_count; // dword_10066A00[92*i]:  Число фаз анимации
    // +20..+367: данные фаз анимации (до 22 фаз × 16 байт)
};
```

## 2.6. Переключатели рендера (из Ngi32.dll)

Движок читает настройки из реестра Windows (`HKCU\Software\Nikita\NgiTool`). Подтверждённые ключи:

| Ключ реестра             | Глобальная переменная | Описание                        |
|--------------------------|-----------------------|---------------------------------|
| `Disable MultiTexturing` | `dword_1003A184`      | Отключить мультитекстурирование |
| `DisableMipmap`          | `dword_1003A174`      | Отключить мипмап‑фильтрацию     |
| `Force 16-bit textures`  | `dword_1003A180`      | Принудительно 16‑бит текстуры   |
| `UseFirstCard`           | `dword_100340EC`      | Использовать первую видеокарту  |
| `DisableD3DCalls`        | `dword_1003A178`      | Отключить вызовы D3D (отладка)  |
| `DisableDSound`          | `dword_1003A17C`      | Отключить DirectSound           |
| `ForceCpu`               | (комбинированный)     | Режим рендера: SW/HW TnL/Mixed  |

### Значения ForceCpu и их влияние на рендер

| ForceCpu | Force SSE | Force 3DNow | Force FXCH | Force MMX |
|----------|-----------|-------------|------------|-----------|
| 2        | Да        | Нет         | Нет        | Нет       |
| 3        | Нет       | Да          | Нет        | Нет       |
| 4        | Да        | Да          | Нет        | Нет       |
| 5        | Да        | Да          | Да         | Да        |
| 6        | Да        | Да          | Да         | Нет       |
| 7        | Нет       | Нет         | Нет        | Да        |

### Практические выводы для порта

Движок спроектирован для работы **без** следующих функций (graceful degradation):

- Мипмапы.
- Bilinear/trilinear фильтрация.
- Мультитекстурирование (2‑й текстурный слой).
- 32‑битные текстуры (fallback на 16‑бит).
- Аппаратный T&L (software fallback).

---

## 2.7. Текстовый файл WEAR + LIGHTMAPS (World3D.dll)

`World3D.dll` содержит парсер текстового файла (режим `rt`), который задаёт:

- список **материалов (wear)**, используемых в сцене/объекте;
- список **лайтмап (lightmaps)**.

Формат читается через `fgets`/`sscanf`/`fscanf`, поэтому он чувствителен к структуре строк и ключевому слову `LIGHTMAPS`.

### 2.7.1. Блок WEAR (материалы)

1) **Первая строка файла** — целое число:

- `wearCount` (обязательно `> 0`, иначе ошибка `"Illegal wear length."`)

2) Далее следует `wearCount` строк. Каждая строка имеет вид:

- `<int> <пробелы> <materialName>`

Где:

- `<int>` парсится, но фактически не используется как ключ (движок обрабатывает записи последовательно).
- `<materialName>` — имя материала, которое движок ищет в менеджере материалов.
  - Если материал не найден, пишется `"Material %s not found."` и используется fallback `"DEFAULT"`.

> Практическая рекомендация для инструментов: считайте `<int>` как необязательный “legacy-id”, а истинным идентификатором материала делайте строку `<materialName>`.

### 2.7.2. Блок LIGHTMAPS

После чтения wear-списка движок последовательно читает токены (`fscanf("%s")`) до тех пор, пока не встретит слово **`LIGHTMAPS`**.

Затем:

1) Читается `lightmapCount`:

- `lightmapCount` (обязательно `> 0`, иначе ошибка `"Illegal lightmaps length."`)

2) Далее следует `lightmapCount` строк вида:

- `<int> <пробелы> <lightmapName>`

Где:

- `<int>` парсится, но фактически не используется как ключ (аналогично wear).
- `<lightmapName>` — имя лайтмапы; если ресурс не найден, пишется `"LightMap %s not found."`.

### 2.7.3. Валидация имени лайтмапы (деталь движка)

Перед загрузкой лайтмапы выполняется проверка имени:

- в имени должна встречаться точка `.` **в пределах первых 16 символов**, иначе ошибка `"Bad texture name."`;
- далее движок использует подстроку после точки в вычислениях внутренних индексов/кэша (на практике полезно придерживаться шаблона вида `NAME.A1`, `NAME.B2` и т.п.).

---
## 2.8. Формат текстурного ассета `Texm` (Ngi32.dll)

Текстуры из `Textures.lib` хранятся как NRes‑entries типа `0x6D786554` (`"Texm"`).

### 2.8.1. Заголовок `Texm` (32 байта)

```c
struct TexmHeader32 {
    uint32_t magic;      // 0x6D786554 ('Texm')
    uint32_t width;      // base width
    uint32_t height;     // base height
    uint32_t mipCount;   // количество уровней
    uint32_t flags4;     // наблюдаются 0 или 32
    uint32_t flags5;     // наблюдаются 0 или 0x04000000
    uint32_t unk6;       // служебное поле (часто 0, иногда ненулевое)
    uint32_t format;     // код пиксельного формата
};
```

Подтверждённые `format`:

- `0` — paletted 8-bit (индекс + palette);
- `565`, `556`, `4444` — 16-bit семейство;
- `888`, `8888` — 32-bit семейство.

### 2.8.2. Layout payload

После заголовка:

1) если `format == 0`: palette блок 1024 байта (`256 × 4`);
2) далее mip-chain пикселей;
3) опционально chunk атласа `Page`.

Размер mip-chain:

```c
bytesPerPixel = (format == 0 ? 1 : format in {565,556,4444} ? 2 : 4);
pixelBytes = bytesPerPixel * sum_{i=0..mipCount-1}(max(1,width>>i) * max(1,height>>i));
```

Итого «чистый» размер без `Page`:

```c
sizeCore = 32 + (format == 0 ? 1024 : 0) + pixelBytes;
```

### 2.8.3. Опциональный `Page` chunk

Если после `sizeCore` остаются байты и в этой позиции стоит magic `"Page"` (`0x65676150`), парсер `sub_1000FF60` читает таблицу subrect:

```c
struct PageChunk {
    uint32_t magic;      // 'Page'
    uint32_t count;
    struct Rect16 {
        int16_t x;
        int16_t w;
        int16_t y;
        int16_t h;
    } rects[count];
};
```

Для каждого rect рантайм строит:

- пиксельные границы (`x0,y0,x1,y1`);
- нормализованные UV (`u0,v0,u1,v1`) с делителем `1/(width<<mipSkip)` и `1/(height<<mipSkip)`.

`mipSkip` вычисляется `sub_1000F580` (уровень, с которого реально начинается загрузка в GPU в зависимости от формата/ограничений).

### 2.8.4. Palette в формате `format==0`

В `sub_1000FB30` palette конвертируется в локальную 32-bit таблицу; байты источника читаются как BGR-порядок (четвёртый байт входной записи не используется напрямую в базовом пути), итоговая alpha зависит от флагов runtime-конфига.

### 2.8.5. Проверка на реальных данных

Для всех 393 entries в `Textures.lib`:

- `magic == 'Texm'`;
- размеры совпадают с `sizeCore` либо `sizeCore + PageChunk (+pad до 8 байт NRes)`;
- при наличии хвоста в `sizeCore` всегда обнаруживается валидный `Page` chunk.

---