diff options
Diffstat (limited to 'docs/reference/msh.md')
| -rw-r--r-- | docs/reference/msh.md | 109 |
1 files changed, 108 insertions, 1 deletions
diff --git a/docs/reference/msh.md b/docs/reference/msh.md index a28514d..0a8a2cb 100644 --- a/docs/reference/msh.md +++ b/docs/reference/msh.md @@ -18,6 +18,7 @@ type 9 service stream type 10 strings and node names type 13 Batch20 records type 15 auxiliary stream +type 16 optional vertex-colour data type 17 auxiliary data type 18 packed UV1 (optional secondary coordinates) type 19 animation frame map @@ -25,6 +26,9 @@ type 20 rare auxiliary table ``` Reader ищет entries по type, но сохраняет исходный порядок для roundtrip. +Поле `ModelAsset.type16_present` отмечает только наличие entry типа 16. Данные +payload сохраняются, но этот путь рендера пока не преобразует их в цвета +вершин. ## Node and slot selection @@ -149,11 +153,114 @@ struct Batch20 { ``` Byte `material_index` выбирает строку WEAR; high word at `+0x02` сохраняется -отдельно для native overrides. Loader проверяет, что +отдельно для native overrides. Слово `local_batch_index` остаётся raw-полем +`+0x06`: в пути CBuilding helper retry оно вместе с базой модели задаёт +связанный корень Node38, а у отдельной `ModelAsset` база равна нулю. Вне этого +пути слово сохраняется без вывода о его значении. Loader проверяет, что `index_start + index_count` остаётся внутри type 6, а каждый использованный индекс через `base_vertex` попадает в position stream. `vertex_count` и `local_batch_index` доступны renderer для исходного draw contract. +## Type 7 и сегментный запрос + +Type 7 необязателен для обычного MSH preview. Если stream есть, каждые 16 bytes +описывают одну грань: `tri_flags` находится по `+0`, четыре неинтерпретируемых +слова — по `+2`, `+4`, `+6` и `+14`, а signed `normal[3]` — по `+8`, `+0xA` и +`+0xC`. `ModelAsset.triangle_descriptors` сохраняет декодированную таблицу как +`Option`; отсутствие Type 7 не делает статическую модель недействительной. + +`query_slot_segment` использует Node38 header mask, Batch20 leading DWORD +`batch_flags | (material_index_hi << 16)` и `tri_flags`. Для каждого поля +include mask требует все установленные биты, exclude mask пропускает запись при +любом совпавшем бите. Descriptor cursor начинается с `Slot68.tri_start` и +сдвигается на `Batch20.index_count / 3` после каждой записи, даже если batch +или грань отфильтрованы. Индексы грани читаются из type 6 с учётом +`base_vertex`. + +Правило восстановлено для GOG-бинарей `AniMesh.dll` +(`65659bf67f20e5be77d67a5b2f59968f61b94293143807fa59ca451c560c4f7e`) и +`Ngi32.dll` (`bab9840d94f4e4e74ffc26677724fa896cf4823845504d09a9e025f80016edf5`). +Callback `AniMesh.dll+0x11090` переводит signed type-7 normal множителем +`1 / 32767`; функция `AniMesh.dll+0x11EB0` записывает этот scale в глобальное +поле. Callback вызывает FastProc slot `+0xB4`: в этой сборке он указывает на +`Ngi32.dll+0x1D560`. После обычной загрузки DLL порог `Ngi32.dll+0x37224` +равен нулю, поэтому plane test принимает только переход от положительного +расстояния к неположительному (`dot(normal, end - start) < 0`, расстояние в +начале `> 0`, в конце `<= 0`). `Batch20.batch_flags & 0x0002` разрешает повторный +plane test в обратном направлении, только если прямой plane test не пересёк +плоскость. Пересечение проверяется export ordinal 202 +(`Ngi32.dll+0x1E60`, `mrnPointInPoly`): проекция выбирает ось с наибольшим по +модулю normal component, а точка проходит грань, пока ни одно edge expression +не меньше нуля. Равенство нулю принимается, поэтому попадание на ребро или +вершину считается попаданием. Поздний кандидат с тем же squared distance +заменяет предыдущий; расстояние считается от исходного первого endpoint. + +Native helper получает точку пересечения через `RCPSS` и один шаг уточнения; +Rust использует обычное деление `f32`, поэтому последние биты координат могут +отличаться. Игнорируемый тест с ресурсами проверяет исходную стену, дверной +проём и обратное направление на `fortif.rlb/fr_l_plant.msh`. + +### Мировой отрезок по выбранным узлам + +Локальный запрос выше проверяет один узел и slot модели. Runtime-функция +`query_building_segment_world` принимает мировой отрезок и исходный список +выбранных узлов со slot-ами и их преобразованиями `world_from_node`. Первый +проход сохраняет порядок этого списка, переводит концы отрезка обратной +матрицей и выбирает ближайшее попадание в мире. + +```text +segment_node = inverse(world_from_node) * segment_world +hit_world = world_from_node * hit_node +distance² = dot(hit_world - original_start, hit_world - original_start) +``` + +`original_start` остаётся началом первоначального мирового отрезка, даже когда +запрос перезапускается. Если ближайший batch имеет bit `0x0008`, его ключ +`local_batch_index` выбирает новый корень Node38, а отрезок начинается в +мировой точке helper-попадания и идёт к исходному концу. Runtime обходит +метаданные этого поддерева даже у узлов без переданной геометрии; сами slot- +запросы выполняются только для узлов, присутствующих во входном списке. Обход +проверяет сначала допустимых потомков, затем сам узел. Header bit `0x0010` +прекращает обход его потомков; bit `0x0004` у дочернего узла исключает всю эту ветвь, но bit +`0x0004` у текущего корня не мешает запросу его собственной геометрии. Слово +родителя `0xFFFF` не связывает узел с дочерним корнем, а helper-ключ `0xFFFF` +считается ошибкой данных. + +В каждом проходе batch `0x0200` и type-7 triangle flags `0x0024` исключаются. +Ближайшее попадание и разрешение равных расстояний сохраняют правила локального +запроса; расстояние измеряется от первоначального начала. Счётчик ограничивает +все пересечения: при посещении 200 запрос завершается ошибкой до проверки bit +`0x0008`. + +Эта схема сверена с GOG `Terrain.dll` SHA-256 +`af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead` (RVA +`0x35430`, `0x35961`) и `AniMesh.dll` SHA-256 +`65659bf67f20e5be77d67a5b2f59968f61b94293143807fa59ca451c560c4f7e` (RVA +`0x134D0`, `0x10A50`). + +Игнорируемый тест загружает настоящий объект PLANT 4 миссии +AutoDemo и использует его исходную CTLD-привязку Node3: `frame_a = 0`, +`frame_b = 1`, начальное смешивание `0`. В контроллере корневого PLANT нет +записей tag-21. В +закрытой начальной позе запрос, вызванный через обновление камеры приложения, +останавливает движение. Затем тест вручную задаёт время `1` только для +Node3, пересчитывает pose и преобразование присоединения и повторяет тот же +путь обновления камеры; отрезок проходит через проём. Это проверяет подачу +двух заданных в MSH поз в коллизию приложения, а не открытие двери событием +миссии и не её видимое обновление в игровой сцене. + +Интерактивный preview создаёт отдельную запись для каждого отрисовываемого +корневого строения класса `FORT` и его отрисовываемых компонентов. На каждом +движении камера получает текущую Node38-иерархию компонента и его актуальное +преобразование присоединения, переводит выбранные узлы и slot-ы в мировой +запрос и выполняет его после ограничения цели рельефом. Попадание сохраняет +прежнюю позицию камеры, но не отменяет новый поворот; повтор ожидающего кадра +не применяет шаг движения второй раз. Так приложение связывает мировой +MSH-запрос с движением свободной preview-камеры. Выбор первого корня и полный +состав мирового списка ещё не сверены с оригиналом; игровые источники поз и +событий, а также поведение стратегической и следящей камеры остаются +открытыми. + ## Сторона грани в цветовом проходе В цветном проходе `Terrain.dll`, функция `0x100296F0`, сравнивает камеру с |
