aboutsummaryrefslogtreecommitdiff
path: root/docs/reference/msh.md
diff options
context:
space:
mode:
Diffstat (limited to 'docs/reference/msh.md')
-rw-r--r--docs/reference/msh.md109
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`, сравнивает камеру с