Files
fparkan/docs/specs/materials-texm.md

16 KiB
Raw Blame History

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.pal". Каждая палитра загружается через niOpenResFileniReadData` и регистрируется как текстурный объект в графическом движке.

Максимальное количество палитр: 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 Pingpong: туда‑обратно
2 Однократное воспроизведение (clamp)
3 Случайный кадр (random)

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

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

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):

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.")
  1. Далее следует 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.")
  1. Далее следует 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 хранятся как NResentries типа 0x6D786554 ("Texm").

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

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:

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:

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

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

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

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.