@gmvjs/xray-v1 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +71 -2
  3. package/dist/db.d.ts +12 -0
  4. package/dist/db.js +134 -0
  5. package/dist/db.js.map +1 -0
  6. package/dist/details.d.ts +42 -0
  7. package/dist/details.js +210 -0
  8. package/dist/details.js.map +1 -0
  9. package/dist/export.d.ts +38 -0
  10. package/dist/export.js +241 -0
  11. package/dist/export.js.map +1 -0
  12. package/dist/games/cop.d.ts +7 -0
  13. package/dist/games/cop.js +11 -0
  14. package/dist/games/cop.js.map +1 -0
  15. package/dist/games/cs.d.ts +7 -0
  16. package/dist/games/cs.js +11 -0
  17. package/dist/games/cs.js.map +1 -0
  18. package/dist/games/sdk.d.ts +7 -0
  19. package/dist/games/sdk.js +10 -0
  20. package/dist/games/sdk.js.map +1 -0
  21. package/dist/games/soc.d.ts +9 -0
  22. package/dist/games/soc.js +13 -0
  23. package/dist/games/soc.js.map +1 -0
  24. package/dist/i18n.d.ts +2 -0
  25. package/dist/i18n.js +48 -0
  26. package/dist/i18n.js.map +1 -0
  27. package/dist/index.d.ts +5 -0
  28. package/dist/index.js +131 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/level.d.ts +39 -0
  31. package/dist/level.js +234 -0
  32. package/dist/level.js.map +1 -0
  33. package/dist/load.d.ts +5 -0
  34. package/dist/load.js +126 -0
  35. package/dist/load.js.map +1 -0
  36. package/dist/lzhuf.d.ts +2 -0
  37. package/dist/lzhuf.js +136 -0
  38. package/dist/lzhuf.js.map +1 -0
  39. package/dist/motion.d.ts +28 -0
  40. package/dist/motion.js +20 -0
  41. package/dist/motion.js.map +1 -0
  42. package/dist/object.d.ts +138 -0
  43. package/dist/object.js +361 -0
  44. package/dist/object.js.map +1 -0
  45. package/dist/ogf.d.ts +93 -0
  46. package/dist/ogf.js +418 -0
  47. package/dist/ogf.js.map +1 -0
  48. package/dist/scene.d.ts +28 -0
  49. package/dist/scene.js +141 -0
  50. package/dist/scene.js.map +1 -0
  51. package/dist/skeleton.d.ts +34 -0
  52. package/dist/skeleton.js +252 -0
  53. package/dist/skeleton.js.map +1 -0
  54. package/dist/smotion.d.ts +44 -0
  55. package/dist/smotion.js +283 -0
  56. package/dist/smotion.js.map +1 -0
  57. package/dist/soc-key.d.ts +18 -0
  58. package/dist/soc-key.js +125 -0
  59. package/dist/soc-key.js.map +1 -0
  60. package/dist/thm.d.ts +26 -0
  61. package/dist/thm.js +69 -0
  62. package/dist/thm.js.map +1 -0
  63. package/dist/write-object.d.ts +31 -0
  64. package/dist/write-object.js +228 -0
  65. package/dist/write-object.js.map +1 -0
  66. package/dist/write-omf.d.ts +47 -0
  67. package/dist/write-scene.d.ts +26 -0
  68. package/dist/write-scene.js +114 -0
  69. package/dist/write-scene.js.map +1 -0
  70. package/package.json +70 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexander Shibisty
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,72 @@
1
- # Temporary Holding Version
1
+ # @gmvjs/xray-v1 — X-Ray Engine 1.x
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ X-Ray Engine 1.x (S.T.A.L.K.E.R.: 1.0 — Тень Чернобыля, 1.5 — Чистое небо, 1.6 — Зов Припяти; версии разборщик различает сам): модели OGF v4 со скелетами и анимациями (встроенные и внешние `.omf`, части тела), архивы `.db`/`.xdb` (в том числе зашифрованные `gamedata.db*` Тени Чернобыля), скомпилированные уровни с травой из `level.details`, детали `.dm`, исходники SDK (`.object`, `.skl/.skls`, сцены `.level`) и выгрузка в X-Ray SDK 0.7.
4
+
5
+ Плагин движка для [`@gmvjs/core`](../core/README.md): форматы, архивы и пресеты игр. Тяжёлые разборщики загружаются по требованию (`import()` внутри `load`), так что подключение пакета почти ничего не стоит, пока файл этого движка не открыт.
6
+
7
+ ## Установка
8
+
9
+ ```sh
10
+ npm i @gmvjs/core @gmvjs/xray-v1 @gmvjs/codecs
11
+ ```
12
+
13
+ ## Подключение
14
+
15
+ Весь движок (все игры):
16
+
17
+ ```ts
18
+ import { createRegistry, ViewerSession } from '@gmvjs/core';
19
+ import { xrayV1Engine } from '@gmvjs/xray-v1';
20
+
21
+ const session = new ViewerSession(createRegistry([xrayV1Engine]));
22
+ ```
23
+
24
+ Одна игра — подпуть пакета (пресет игры и движок, ограниченный этой игрой):
25
+
26
+ ```ts
27
+ import { createRegistry, ViewerSession } from '@gmvjs/core';
28
+ import soc from '@gmvjs/xray-v1/soc';
29
+
30
+ const session = new ViewerSession(createRegistry([soc.engine]), soc.preset.id);
31
+ ```
32
+
33
+ ## Игры (подпути)
34
+
35
+ | Импорт | Пресет | Игра | Статус | Только для этой игры |
36
+ |---|---|---|---|---|
37
+ | `@gmvjs/xray-v1/soc` | `stalker-soc` | S.T.A.L.K.E.R.: Тень Чернобыля | 🟡 бета | `soc-key.ts`; ключ таблиц файлов `gamedata.db*` ищется в `bin/XR_3DA.exe` пользователя — ключей в пакете нет |
38
+ | `@gmvjs/xray-v1/cs` | `stalker-cs` | S.T.A.L.K.E.R.: Чистое небо | 🟡 бета | — |
39
+ | `@gmvjs/xray-v1/cop` | `stalker-cop` | S.T.A.L.K.E.R.: Зов Припяти | 🟡 бета | — |
40
+ | `@gmvjs/xray-v1/sdk` | — | выгрузка в X-Ray SDK 0.7 (`ExporterPlugin` `xray-sdk`): `.object`, `.level` + `scene_object.part`, DDS + `.thm`; используется и для карт Metro | — | `export.ts`, `write-object.ts`, `write-scene.ts` |
41
+
42
+ ## Форматы
43
+
44
+ | id | Что | Файлы | Статус |
45
+ |---|---|---|---|
46
+ | `xray-ogf` | X-Ray OGF | `.ogf` + анимации `.omf`, `.skl`, `.skls` | 🟡 бета |
47
+ | `xray-object` | X-Ray SDK .object | `.object` + анимации `.omf`, `.skl`, `.skls` | 🟡 бета |
48
+ | `xray-level` | X-Ray: скомпилированный уровень (level + level.geom) | по пути `/(^|\/)levels\/[^/]+\/level$/` | 🟡 бета |
49
+ | `xray-dm` | X-Ray: деталь (.dm — трава, кусты) | `.dm` | 🟡 бета |
50
+ | `xray-scene` | X-Ray SDK: уровень (.level + scene_object.part) | `.level` | 🟡 бета |
51
+
52
+ ## Архивы
53
+
54
+ - `xray-db` — X-Ray .db / .xdb (Чистое небо, Зов Припяти) (`.db`, `.xdb`, `.db0`, `.xdb0`, `.db1`, `.xdb1`, `.db2`, `.xdb2`…)
55
+
56
+ ## Выгрузка
57
+
58
+ - `xray-sdk` — S.T.A.L.K.E.R.: исходники X-Ray SDK 0.7 (подпуть `@gmvjs/xray-v1/sdk`)
59
+
60
+ ## Зависимости
61
+
62
+ - `@gmvjs/core` — API плагинов, типы, архивы ZIP/папка, zlib, DDS/BC, геометрия
63
+ - `@gmvjs/codecs` — LZO
64
+
65
+ ## Не сделано
66
+
67
+ - OGF старше v4 и двоичные `.level` старых SDK (нет образцов)
68
+ - SDK: проверка в LevelEditor, сопоставление материалов с шейдерами/gamemtl, запись OGF/OMF
69
+
70
+ ## Лицензия
71
+
72
+ MIT
package/dist/db.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { Archive, ArchiveEntry, BlobFile } from '@gmvjs/core/types';
2
+ interface DbEntry extends ArchiveEntry {
3
+ off: number;
4
+ real: number;
5
+ packed: number;
6
+ }
7
+ /** Разбор таблицы файлов (распакованной). */
8
+ export declare function parseDbTable(t: Uint8Array): DbEntry[];
9
+ /** Забыть найденные ключи, в том числе сохранённые (тесты, «сброс»). */
10
+ export declare const resetSocKeys: () => void;
11
+ export declare function openXrayDb(file: BlobFile, siblings?: readonly BlobFile[]): Promise<Archive>;
12
+ export {};
package/dist/db.js ADDED
@@ -0,0 +1,134 @@
1
+ import { normPath } from "@gmvjs/core/binary";
2
+ import { readRange, createArchive } from "@gmvjs/core/archives/common";
3
+ import { lzhufDecompress } from "./lzhuf.js";
4
+ import { lzo1xDecompress } from "@gmvjs/codecs";
5
+ const CHUNK_HEADER = 1, COMPRESSED = 2147483648;
6
+ async function findTable(file) {
7
+ let p = 0;
8
+ for (let n = 0; n < 64 && p + 8 <= file.size; n++) {
9
+ const h = await readRange(file, p, p + 8), dv = new DataView(h.buffer, h.byteOffset, 8);
10
+ const id = dv.getUint32(0, true), size = dv.getUint32(4, true);
11
+ if (p + 8 + size > file.size) return null;
12
+ if ((id & ~COMPRESSED) >>> 0 === CHUNK_HEADER) return { id, off: p + 8, size };
13
+ p += 8 + size;
14
+ }
15
+ return null;
16
+ }
17
+ function parseDbTable(t) {
18
+ const dv = new DataView(t.buffer, t.byteOffset, t.byteLength), out = [];
19
+ let q = 0;
20
+ while (q + 2 <= t.length) {
21
+ const bs = dv.getUint16(q, true);
22
+ if (bs < 16 || q + 2 + bs > t.length) throw new Error(`таблица файлов повреждена (смещение ${q})`);
23
+ const real = dv.getUint32(q + 2, true), packed = dv.getUint32(q + 6, true);
24
+ let name = "";
25
+ for (let k = q + 14; k < q + 2 + bs - 4; k++) name += String.fromCharCode(t[k]);
26
+ const off = dv.getUint32(q + 2 + bs - 4, true);
27
+ q += 2 + bs;
28
+ if (!real && !packed) continue;
29
+ out.push({ path: normPath(name), size: real, off, real, packed });
30
+ }
31
+ return out;
32
+ }
33
+ const socKeys = /* @__PURE__ */ new Map();
34
+ const STORE = "gmv.xray.socKeys";
35
+ function loadStored() {
36
+ try {
37
+ const list = JSON.parse(globalThis.localStorage?.getItem(STORE) ?? "[]");
38
+ for (const [id, k] of list) if (k && Number.isFinite(k.tableSeed) && Number.isFinite(k.encryptSeed) && Number.isFinite(k.iterations)) socKeys.set(id, k);
39
+ } catch {
40
+ }
41
+ }
42
+ function saveStored() {
43
+ try {
44
+ globalThis.localStorage?.setItem(STORE, JSON.stringify([...socKeys]));
45
+ } catch {
46
+ }
47
+ }
48
+ let storedLoaded = false;
49
+ const resetSocKeys = () => {
50
+ socKeys.clear();
51
+ storedLoaded = true;
52
+ try {
53
+ globalThis.localStorage?.removeItem(STORE);
54
+ } catch {
55
+ }
56
+ };
57
+ function tableEntries(table, fileSize) {
58
+ try {
59
+ const e = parseDbTable(table);
60
+ return e.length && !e.some((x) => x.off + x.packed > fileSize) ? e : null;
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+ async function openXrayDb(file, siblings = []) {
66
+ const tab = await findTable(file);
67
+ if (!tab) throw new Error(`${file.name}: не архив X-Ray (нет таблицы файлов)`);
68
+ const raw = await readRange(file, tab.off, tab.off + tab.size);
69
+ let entries = null;
70
+ try {
71
+ entries = tableEntries(tab.id & COMPRESSED ? lzhufDecompress(raw) : raw, file.size);
72
+ } catch {
73
+ entries = null;
74
+ }
75
+ if (!entries && tab.id & COMPRESSED) {
76
+ const { findSocKey, socDecode } = await import("./soc-key.js");
77
+ const check = (t) => !!tableEntries(t, file.size);
78
+ if (!storedLoaded) {
79
+ storedLoaded = true;
80
+ loadStored();
81
+ }
82
+ for (const key of socKeys.values()) {
83
+ try {
84
+ entries = tableEntries(lzhufDecompress(socDecode(key, raw)), file.size);
85
+ } catch {
86
+ entries = null;
87
+ }
88
+ if (entries) break;
89
+ }
90
+ const exes = siblings.filter((f) => /\.exe$/i.test(f.name)).sort((a, b) => +!/xr_3da/i.test(a.name) - +!/xr_3da/i.test(b.name));
91
+ const tried = [];
92
+ for (const exe of entries ? [] : exes) {
93
+ const id = `${exe.name}:${exe.size}`;
94
+ if (socKeys.has(id)) continue;
95
+ let key = null;
96
+ try {
97
+ key = findSocKey(new Uint8Array(await exe.arrayBuffer()), raw, check);
98
+ } catch (e) {
99
+ tried.push(`${exe.webkitRelativePath || exe.name}: ${e.message}`);
100
+ continue;
101
+ }
102
+ if (!key) {
103
+ tried.push(`${exe.webkitRelativePath || exe.name} (${exe.size} Б) — ключ не найден`);
104
+ continue;
105
+ }
106
+ socKeys.set(id, key);
107
+ saveStored();
108
+ entries = tableEntries(lzhufDecompress(socDecode(key, raw)), file.size);
109
+ if (entries) break;
110
+ }
111
+ if (!entries)
112
+ throw new Error(`${file.name}: таблица файлов зашифрована (архивы «Тени Чернобыля»). Откройте папку игры целиком («Папка игры…») или выберите кнопкой «Открыть .db…» сначала bin\\XR_3DA.exe, затем архивы — ключ берётся из него. ${tried.length ? `Проверены: ${tried.join("; ")}.` : `Исполняемых файлов игры среди выбранных нет (выбрано файлов: ${siblings.length}).`}`);
113
+ }
114
+ if (!entries) throw new Error(`${file.name}: таблица файлов повреждена.`);
115
+ return createArchive({
116
+ name: file.name,
117
+ kind: "xray-db",
118
+ version: 0,
119
+ entries,
120
+ async read(e) {
121
+ const u = await readRange(file, e.off, e.off + e.packed);
122
+ if (e.packed === e.real) return u;
123
+ const out = new Uint8Array(e.real);
124
+ lzo1xDecompress(u, 0, out);
125
+ return out;
126
+ }
127
+ });
128
+ }
129
+ export {
130
+ openXrayDb,
131
+ parseDbTable,
132
+ resetSocKeys
133
+ };
134
+ //# sourceMappingURL=db.js.map
package/dist/db.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sources":["../src/db.ts"],"sourcesContent":["// Архивы X-Ray (.db, .db0…, .xdb): Чистое небо, Зов Припяти и моды. Файл — цепочка чанков [id u32, размер u32]:\n// 0x29A — текстовый [header] (ЧН/ЗП), 0 — данные, 1 — таблица файлов (флаг 0x80000000 — сжата LZHUF).\n// Запись таблицы: u16 длина, затем размер, сжатый размер, CRC, имя (без нуля), смещение в архиве.\n// Сжатые файлы (размер ≠ сжатому) — LZO1X. Тень Чернобыля шифрует таблицу файлов — такие архивы не открываются.\nimport { normPath } from '@gmvjs/core/binary';\nimport { createArchive, readRange } from '@gmvjs/core/archives/common';\nimport { lzhufDecompress } from './lzhuf';\nimport { lzo1xDecompress } from '@gmvjs/codecs';\nimport type { Archive, ArchiveEntry, BlobFile } from '@gmvjs/core/types';\nimport type { SocKey } from './soc-key';\n\ninterface DbEntry extends ArchiveEntry { off: number; real: number; packed: number }\n\nconst CHUNK_HEADER = 1, COMPRESSED = 0x80000000;\n\n/** Найти чанк таблицы файлов, пробегая заголовки чанков. */\nasync function findTable(file: BlobFile): Promise<{ id: number; off: number; size: number } | null> {\n let p = 0;\n for (let n = 0; n < 64 && p + 8 <= file.size; n++) {\n const h = await readRange(file, p, p + 8), dv = new DataView(h.buffer, h.byteOffset, 8);\n const id = dv.getUint32(0, true), size = dv.getUint32(4, true);\n if (p + 8 + size > file.size) return null;\n if ((id & ~COMPRESSED) >>> 0 === CHUNK_HEADER) return { id, off: p + 8, size };\n p += 8 + size;\n }\n return null;\n}\n\n/** Разбор таблицы файлов (распакованной). */\nexport function parseDbTable(t: Uint8Array): DbEntry[] {\n const dv = new DataView(t.buffer, t.byteOffset, t.byteLength), out: DbEntry[] = [];\n let q = 0;\n while (q + 2 <= t.length) {\n const bs = dv.getUint16(q, true);\n if (bs < 16 || q + 2 + bs > t.length) throw new Error(`таблица файлов повреждена (смещение ${q})`);\n const real = dv.getUint32(q + 2, true), packed = dv.getUint32(q + 6, true);\n let name = '';\n for (let k = q + 14; k < q + 2 + bs - 4; k++) name += String.fromCharCode(t[k]!);\n const off = dv.getUint32(q + 2 + bs - 4, true);\n q += 2 + bs;\n if (!real && !packed) continue; // папка\n out.push({ path: normPath(name), size: real, off, real, packed });\n }\n return out;\n}\n\n/** Ключи «Тени Чернобыля», найденные в исполняемых файлах пользователя (по имени и размеру файла), — на время работы страницы. */\nconst socKeys = new Map<string, SocKey>();\n/**\n * Найденные ключи хранятся и в localStorage страницы (только на компьютере пользователя): после того как ключ один раз\n * найден в его XR_3DA.exe, архивы ТЧ открываются и без exe. В сборку расширения ключи не попадают.\n */\nconst STORE = 'gmv.xray.socKeys';\nfunction loadStored(): void {\n try {\n const list = JSON.parse(globalThis.localStorage?.getItem(STORE) ?? '[]') as Array<[string, SocKey]>;\n for (const [id, k] of list) if (k && Number.isFinite(k.tableSeed) && Number.isFinite(k.encryptSeed) && Number.isFinite(k.iterations)) socKeys.set(id, k);\n } catch { /* хранилище недоступно — ключи только на время работы страницы */ }\n}\nfunction saveStored(): void {\n try { globalThis.localStorage?.setItem(STORE, JSON.stringify([...socKeys])); } catch { /* недоступно */ }\n}\nlet storedLoaded = false;\n/** Забыть найденные ключи, в том числе сохранённые (тесты, «сброс»). */\nexport const resetSocKeys = (): void => { socKeys.clear(); storedLoaded = true; try { globalThis.localStorage?.removeItem(STORE); } catch { /* недоступно */ } };\n\n/** Разобрать таблицу: записи есть и лежат внутри архива. */\nfunction tableEntries(table: Uint8Array, fileSize: number): DbEntry[] | null {\n try { const e = parseDbTable(table); return e.length && !e.some((x) => x.off + x.packed > fileSize) ? e : null; } catch { return null; }\n}\n\nexport async function openXrayDb(file: BlobFile, siblings: readonly BlobFile[] = []): Promise<Archive> {\n const tab = await findTable(file);\n if (!tab) throw new Error(`${file.name}: не архив X-Ray (нет таблицы файлов)`);\n const raw = await readRange(file, tab.off, tab.off + tab.size);\n let entries: DbEntry[] | null = null;\n try { entries = tableEntries(tab.id & COMPRESSED ? lzhufDecompress(raw) : raw, file.size); } catch { entries = null; }\n if (!entries && tab.id & COMPRESSED) {\n // «Тень Чернобыля»: таблица зашифрована — ключ ищется в XR_3DA.exe, выбранном вместе с архивами (или в папке игры)\n const { findSocKey, socDecode } = await import('./soc-key');\n const check = (t: Uint8Array) => !!tableEntries(t, file.size);\n if (!storedLoaded) { storedLoaded = true; loadStored(); }\n for (const key of socKeys.values()) {\n try { entries = tableEntries(lzhufDecompress(socDecode(key, raw)), file.size); } catch { entries = null; }\n if (entries) break;\n }\n const exes = siblings.filter((f) => /\\.exe$/i.test(f.name)).sort((a, b) => +!/xr_3da/i.test(a.name) - +!/xr_3da/i.test(b.name));\n const tried: string[] = [];\n for (const exe of entries ? [] : exes) {\n const id = `${exe.name}:${exe.size}`;\n if (socKeys.has(id)) continue;\n let key: SocKey | null = null;\n try { key = findSocKey(new Uint8Array(await exe.arrayBuffer()), raw, check); }\n catch (e) { tried.push(`${exe.webkitRelativePath || exe.name}: ${(e as Error).message}`); continue; }\n if (!key) { tried.push(`${exe.webkitRelativePath || exe.name} (${exe.size} Б) — ключ не найден`); continue; }\n socKeys.set(id, key);\n saveStored();\n entries = tableEntries(lzhufDecompress(socDecode(key, raw)), file.size);\n if (entries) break;\n }\n if (!entries)\n throw new Error(`${file.name}: таблица файлов зашифрована (архивы «Тени Чернобыля»). Откройте папку игры целиком («Папка игры…») или выберите кнопкой «Открыть .db…» сначала bin\\\\XR_3DA.exe, затем архивы — ключ берётся из него. ${tried.length ? `Проверены: ${tried.join('; ')}.` : `Исполняемых файлов игры среди выбранных нет (выбрано файлов: ${siblings.length}).`}`);\n }\n if (!entries) throw new Error(`${file.name}: таблица файлов повреждена.`);\n return createArchive<DbEntry>({\n name: file.name,\n kind: 'xray-db',\n version: 0,\n entries,\n async read(e) {\n const u = await readRange(file, e.off, e.off + e.packed);\n if (e.packed === e.real) return u;\n const out = new Uint8Array(e.real);\n lzo1xDecompress(u, 0, out);\n return out;\n },\n });\n}\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import type { Mesh } from '@gmvjs/core/types';
2
+ export interface DetailModel {
3
+ shader: string;
4
+ texture: string;
5
+ flags: number;
6
+ minScale: number;
7
+ maxScale: number;
8
+ pos: Float32Array;
9
+ uv: Float32Array;
10
+ indices: Uint16Array;
11
+ }
12
+ export declare function parseDM(u: Uint8Array): DetailModel;
13
+ /** Меш модели .dm (нормали вверх — как освещается трава). */
14
+ export declare function dmMesh(m: DetailModel, name: string): Mesh;
15
+ export interface DetailsMap {
16
+ version: number;
17
+ offsX: number;
18
+ offsZ: number;
19
+ sizeX: number;
20
+ sizeZ: number;
21
+ models: DetailModel[];
22
+ slots: DataView;
23
+ }
24
+ export declare function parseDetails(u: Uint8Array): DetailsMap;
25
+ /** Коллизия уровня (level.cform): u32 версия 4, вершин, треугольников, рамка (6×f32); вершины f32×3; треугольники {u32×3, u32 материал}. */
26
+ export interface CForm {
27
+ verts: Float32Array;
28
+ tris: Uint32Array;
29
+ }
30
+ export declare function parseCForm(u: Uint8Array): CForm;
31
+ export declare const DETAIL_TRI_BUDGET = 1500000;
32
+ export interface DetailsResult {
33
+ meshes: Mesh[];
34
+ items: number;
35
+ candidates: number;
36
+ keep: number;
37
+ }
38
+ /**
39
+ * Расстановка деталей по карте и коллизии; budget — предел треугольников (лишнее прореживается равномерно).
40
+ * Координаты — как у геометрии уровня (левосторонние, Y вверх).
41
+ */
42
+ export declare function buildDetails(map: DetailsMap, cf: CForm, budget?: number): DetailsResult;
@@ -0,0 +1,210 @@
1
+ const dvOf = (u) => new DataView(u.buffer, u.byteOffset, u.byteLength);
2
+ function chunks(u) {
3
+ const d = dvOf(u), out = /* @__PURE__ */ new Map();
4
+ for (let p = 0; p + 8 <= u.length; ) {
5
+ const id = d.getUint32(p, true) & 2147483647, size = d.getUint32(p + 4, true);
6
+ if (p + 8 + size > u.length) break;
7
+ out.set(id, u.subarray(p + 8, p + 8 + size));
8
+ p += 8 + size;
9
+ }
10
+ return out;
11
+ }
12
+ function parseDM(u) {
13
+ const d = dvOf(u);
14
+ let p = 0;
15
+ const str = () => {
16
+ const e = u.indexOf(0, p);
17
+ if (e < 0) throw new Error(".dm: строка не закрыта");
18
+ const s = new TextDecoder("latin1").decode(u.subarray(p, e));
19
+ p = e + 1;
20
+ return s;
21
+ };
22
+ const shader = str(), texture = str();
23
+ if (p + 20 > u.length) throw new Error(".dm обрезан");
24
+ const flags = d.getUint32(p, true), minScale = d.getFloat32(p + 4, true), maxScale = d.getFloat32(p + 8, true), nv = d.getUint32(p + 12, true), ni = d.getUint32(p + 16, true);
25
+ p += 20;
26
+ if (nv > 65536 || ni % 3 || p + nv * 20 + ni * 2 > u.length) throw new Error(".dm: неверное число вершин или индексов");
27
+ const pos = new Float32Array(nv * 3), uv = new Float32Array(nv * 2), indices = new Uint16Array(ni);
28
+ for (let i = 0; i < nv; i++, p += 20) {
29
+ pos[i * 3] = d.getFloat32(p, true);
30
+ pos[i * 3 + 1] = d.getFloat32(p + 4, true);
31
+ pos[i * 3 + 2] = d.getFloat32(p + 8, true);
32
+ uv[i * 2] = d.getFloat32(p + 12, true);
33
+ uv[i * 2 + 1] = d.getFloat32(p + 16, true);
34
+ }
35
+ for (let i = 0; i < ni; i++, p += 2) indices[i] = d.getUint16(p, true);
36
+ return { shader, texture, flags, minScale, maxScale, pos, uv, indices };
37
+ }
38
+ function dmMesh(m, name) {
39
+ const n = m.pos.length / 3, nor = new Float32Array(n * 3);
40
+ for (let i = 0; i < n; i++) nor[i * 3 + 1] = 1;
41
+ return { name, texture: m.texture, shader: m.shader, vertexCount: n, pos: m.pos, nor, uv: m.uv, bone: new Float32Array(n * 4), weight: new Float32Array(n * 4).map((_, i) => i % 4 === 0 ? 1 : 0), indices: m.indices, skinned: false, alphaTest: true };
42
+ }
43
+ function parseDetails(u) {
44
+ const c = chunks(u), h = c.get(0), objs = c.get(1), sl = c.get(2);
45
+ if (!h || h.length < 24 || !objs || !sl) throw new Error("level.details: нет заголовка, моделей или ячеек");
46
+ const hd = dvOf(h), [version, , offsX, offsZ, sizeX, sizeZ] = [0, 4, 8, 12, 16, 20].map((o) => hd.getUint32(o, true));
47
+ if (version !== 3) throw new Error(`level.details версии ${version} не поддерживается`);
48
+ if (sizeX * sizeZ * 16 > sl.length) throw new Error("level.details: ячейки обрезаны");
49
+ const models = [...chunks(objs)].sort((a, b) => a[0] - b[0]).map(([, b]) => parseDM(b));
50
+ return { version, offsX, offsZ, sizeX, sizeZ, models, slots: dvOf(sl) };
51
+ }
52
+ function parseCForm(u) {
53
+ const d = dvOf(u), ver = d.getUint32(0, true), nv = d.getUint32(4, true), nt = d.getUint32(8, true);
54
+ if (ver !== 4 || 36 + nv * 12 + nt * 16 > u.length) throw new Error("level.cform: неизвестный формат");
55
+ const verts = new Float32Array(nv * 3), tris = new Uint32Array(nt * 3);
56
+ for (let i = 0, p = 36; i < nv * 3; i++, p += 4) verts[i] = d.getFloat32(p, true);
57
+ for (let i = 0, p = 36 + nv * 12; i < nt; i++, p += 16) {
58
+ tris[i * 3] = d.getUint32(p, true);
59
+ tris[i * 3 + 1] = d.getUint32(p + 4, true);
60
+ tris[i * 3 + 2] = d.getUint32(p + 8, true);
61
+ }
62
+ return { verts, tris };
63
+ }
64
+ class CRandom {
65
+ constructor(s) {
66
+ this.s = s;
67
+ }
68
+ s;
69
+ next() {
70
+ this.s = Math.imul(this.s, 214013) + 2531011 >>> 0;
71
+ return this.s >>> 16 & 32767;
72
+ }
73
+ randI(n) {
74
+ return this.next() % n;
75
+ }
76
+ randF(a, b) {
77
+ return a + (b - a) * this.next() / 32767;
78
+ }
79
+ randFs(r) {
80
+ return this.randF(-r, r);
81
+ }
82
+ }
83
+ const DITHER = (() => {
84
+ let m = [[0]];
85
+ for (let n = 1; n < 16; n *= 2) m = Array.from({ length: n * 2 }, (_, y) => Array.from({ length: n * 2 }, (_2, x) => 4 * m[y % n][x % n] + [[0, 2], [3, 1]][+(y >= n)][+(x >= n)]));
86
+ return Uint8Array.from(m.flat());
87
+ })();
88
+ const DETAIL_TRI_BUDGET = 15e5;
89
+ const DENSITY = 0.3, SLOT = 2;
90
+ function buildDetails(map, cf, budget = DETAIL_TRI_BUDGET) {
91
+ const { sizeX, sizeZ, offsX, offsZ, slots, models } = map, nObj = models.length;
92
+ const dSize = Math.ceil(SLOT / DENSITY), jitter = DENSITY / 1.7;
93
+ const slotOk = new Uint8Array(sizeX * sizeZ), yLo = new Float32Array(sizeX * sizeZ), yHi = new Float32Array(sizeX * sizeZ);
94
+ for (let i = 0; i < sizeX * sizeZ; i++) {
95
+ const a = slots.getUint32(i * 16, true), b = slots.getUint32(i * 16 + 4, true);
96
+ const ids2 = [a >>> 20 & 63, a >>> 26 & 63, b & 63, b >>> 6 & 63];
97
+ let any = false;
98
+ for (let k = 0; k < 4; k++) if (ids2[k] < nObj && slots.getUint16(i * 16 + 8 + k * 2, true)) any = true;
99
+ if (!any) continue;
100
+ slotOk[i] = 1;
101
+ yLo[i] = (a & 4095) * 0.2 - 200;
102
+ yHi[i] = yLo[i] + (a >>> 12 & 255) * 0.1;
103
+ }
104
+ const { verts: V, tris: T } = cf, nt = T.length / 3;
105
+ const cnt = new Uint32Array(sizeX * sizeZ + 1);
106
+ const forCells = (t, f) => {
107
+ const a = T[t * 3] * 3, b = T[t * 3 + 1] * 3, c = T[t * 3 + 2] * 3;
108
+ const x0 = Math.floor(Math.min(V[a], V[b], V[c]) / SLOT) + offsX, x1 = Math.floor(Math.max(V[a], V[b], V[c]) / SLOT) + offsX;
109
+ const z0 = Math.floor(Math.min(V[a + 2], V[b + 2], V[c + 2]) / SLOT) + offsZ, z1 = Math.floor(Math.max(V[a + 2], V[b + 2], V[c + 2]) / SLOT) + offsZ;
110
+ const ylo = Math.min(V[a + 1], V[b + 1], V[c + 1]), yhi = Math.max(V[a + 1], V[b + 1], V[c + 1]);
111
+ for (let z = Math.max(0, z0); z <= Math.min(sizeZ - 1, z1); z++) for (let x = Math.max(0, x0); x <= Math.min(sizeX - 1, x1); x++) {
112
+ const cell = z * sizeX + x;
113
+ if (slotOk[cell] && yhi >= yLo[cell] - 0.01 && ylo <= yHi[cell] + 0.01) f(cell);
114
+ }
115
+ };
116
+ for (let t = 0; t < nt; t++) forCells(t, (cell) => cnt[cell + 1]++);
117
+ for (let i = 0; i < sizeX * sizeZ; i++) cnt[i + 1] += cnt[i];
118
+ const fill = cnt.slice(0, sizeX * sizeZ), list = new Uint32Array(cnt[sizeX * sizeZ]);
119
+ for (let t = 0; t < nt; t++) forCells(t, (cell) => {
120
+ list[fill[cell]++] = t;
121
+ });
122
+ const triOf = models.map((m) => m.indices.length / 3);
123
+ const alpha = new Float32Array(16), ids = new Int32Array(4), sel = [];
124
+ const walk = (emit, needY) => {
125
+ for (let dz = 0; dz < sizeZ; dz++) for (let dx = 0; dx < sizeX; dx++) {
126
+ const cell = dz * sizeX + dx;
127
+ if (!slotOk[cell]) continue;
128
+ const o = cell * 16, a = slots.getUint32(o, true), b = slots.getUint32(o + 4, true);
129
+ ids[0] = a >>> 20 & 63;
130
+ ids[1] = a >>> 26 & 63;
131
+ ids[2] = b & 63;
132
+ ids[3] = b >>> 6 & 63;
133
+ for (let k = 0; k < 4; k++) {
134
+ const pal = slots.getUint16(o + 8 + k * 2, true);
135
+ for (let j = 0; j < 4; j++) alpha[k * 4 + j] = 255 * (pal >> j * 4 & 15) / 15;
136
+ }
137
+ const sx = dx - offsX, sz = dz - offsZ, seed = (302455168 ^ Math.imul(sx, sz)) >>> 0;
138
+ const rSel = new CRandom(seed), rJit = new CRandom(seed), rYaw = new CRandom(seed), rScl = new CRandom(seed);
139
+ const minX = sx * SLOT, minZ = sz * SLOT, top = yHi[cell], bottom = yLo[cell];
140
+ for (let z = 0; z <= dSize; z++) for (let x = 0; x <= dSize; x++) {
141
+ const shx = rJit.randI(16), shz = rJit.randI(16);
142
+ sel.length = 0;
143
+ const cx = Math.min(x, dSize - 1), cz = Math.min(z, dSize - 1), fx = cx / dSize, fz = cz / dSize;
144
+ for (let k = 0; k < 4; k++) {
145
+ if (ids[k] >= nObj) continue;
146
+ const c01 = alpha[k * 4] * (1 - fx) + alpha[k * 4 + 1] * fx, c02 = alpha[k * 4 + 2] * (1 - fx) + alpha[k * 4 + 3] * fx;
147
+ const c = Math.min(255, Math.max(0, Math.floor(c01 * (1 - fz) + c02 * fz + 0.5)));
148
+ if (c > DITHER[(cx + shx) % 16 * 16 + (cz + shz) % 16]) sel.push(k);
149
+ }
150
+ if (!sel.length) continue;
151
+ const obj = ids[sel.length === 1 ? sel[0] : sel[rSel.randI(sel.length)]];
152
+ const px = x / dSize * SLOT + minX + rJit.randFs(jitter), pz = z / dSize * SLOT + minZ + rJit.randFs(jitter);
153
+ let y = bottom - 5;
154
+ if (needY) {
155
+ for (let i = cnt[cell]; i < cnt[cell + 1]; i++) {
156
+ const t = list[i], A = T[t * 3] * 3, B = T[t * 3 + 1] * 3, C = T[t * 3 + 2] * 3;
157
+ const ax = V[A], az = V[A + 2], e1x = V[B] - ax, e1z = V[B + 2] - az, e2x = V[C] - ax, e2z = V[C + 2] - az;
158
+ const det = e1x * e2z - e2x * e1z;
159
+ if (Math.abs(det) < 1e-9) continue;
160
+ const qx = px - ax, qz = pz - az, u = (qx * e2z - e2x * qz) / det, v = (e1x * qz - qx * e1z) / det;
161
+ if (u < 0 || v < 0 || u + v > 1) continue;
162
+ const yt = V[A + 1] + u * (V[B + 1] - V[A + 1]) + v * (V[C + 1] - V[A + 1]);
163
+ if (yt <= top + 1e-3 && yt > y) y = yt;
164
+ }
165
+ if (y < bottom) continue;
166
+ }
167
+ const m = models[obj], scale = rScl.randF(m.minScale * 0.5, m.maxScale * 0.9), yaw = rYaw.randF(0, Math.PI * 2);
168
+ emit(obj, px, y, pz, scale, yaw, cell);
169
+ }
170
+ }
171
+ };
172
+ let candTris = 0, candidates = 0;
173
+ walk((obj) => {
174
+ candTris += triOf[obj];
175
+ candidates++;
176
+ }, false);
177
+ const keep = candTris > budget ? budget / candTris : 1;
178
+ const thin = new CRandom(24301);
179
+ const P = models.map(() => []), U = models.map(() => []), I = models.map(() => []);
180
+ let items = 0;
181
+ walk((obj, x, y, z, s, yaw) => {
182
+ if (keep < 1 && thin.next() / 32768 >= keep) return;
183
+ const m = models[obj], c = Math.cos(yaw), sn = Math.sin(yaw), p = P[obj], base = p.length / 3;
184
+ if (base + m.pos.length / 3 > 4e6) return;
185
+ for (let i = 0; i < m.pos.length; i += 3) {
186
+ const vx = m.pos[i] * s, vy = m.pos[i + 1] * s, vz = m.pos[i + 2] * s;
187
+ p.push(x + vx * c + vz * sn, y + vy, z - vx * sn + vz * c);
188
+ }
189
+ for (let i = 0; i < m.uv.length; i++) U[obj].push(m.uv[i]);
190
+ for (let i = 0; i < m.indices.length; i++) I[obj].push(base + m.indices[i]);
191
+ items++;
192
+ }, true);
193
+ const meshes = [];
194
+ models.forEach((m, k) => {
195
+ if (!I[k].length) return;
196
+ const n = P[k].length / 3, nor = new Float32Array(n * 3);
197
+ for (let i = 0; i < n; i++) nor[i * 3 + 1] = 1;
198
+ meshes.push({ name: `Трава · ${m.texture}`, texture: m.texture, shader: m.shader, vertexCount: n, pos: Float32Array.from(P[k]), nor, uv: Float32Array.from(U[k]), bone: new Float32Array(n * 4), weight: new Float32Array(n * 4).map((_, i) => i % 4 === 0 ? 1 : 0), indices: Uint32Array.from(I[k]), skinned: false, alphaTest: true });
199
+ });
200
+ return { meshes, items, candidates, keep };
201
+ }
202
+ export {
203
+ DETAIL_TRI_BUDGET,
204
+ buildDetails,
205
+ dmMesh,
206
+ parseCForm,
207
+ parseDM,
208
+ parseDetails
209
+ };
210
+ //# sourceMappingURL=details.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"details.js","sources":["../src/details.ts"],"sourcesContent":["// Трава и мелкие детали X-Ray: модели .dm и карта level.details (ТЧ, ЧН, ЗП).\n// .dm: шейдер, текстура (строки), u32 флаги, f32 мин./макс. масштаб, u32 вершин, u32 индексов, вершины {f32 x,y,z,u,v}, индексы u16.\n// level.details — чанки: 0 заголовок (u32 версия 3, объектов, offs_x, offs_z, size_x, size_z), 1 модели (вложенные чанки .dm),\n// 2 ячейки 2×2 м по 16 байт: u32 y_base:12 (×0.2 − 200 м), y_height:8 (×0.1 м), id0:6, id1:6; u32 id2:6, id3:6, свет…;\n// 4×u16 плотности (по 4 бита на угол ячейки). Ячейка (x, z) лежит в мире с ((x − offs_x)·2, (z − offs_z)·2).\n// Расстановку игра считает сама при показе (CDetailManager::cache_Decompress): сетка точек с дрожанием, выбор модели\n// по интерполированной плотности с упорядоченным дизерингом, высота — луч вниз по коллизии уровня (level.cform),\n// поворот вокруг Y и масштаб — случайные. Здесь тот же порядок; при большом числе треугольников трава прореживается.\nimport type { Mesh } from '@gmvjs/core/types';\n\nexport interface DetailModel { shader: string; texture: string; flags: number; minScale: number; maxScale: number; pos: Float32Array; uv: Float32Array; indices: Uint16Array }\n\nconst dvOf = (u: Uint8Array) => new DataView(u.buffer, u.byteOffset, u.byteLength);\n\nfunction chunks(u: Uint8Array): Map<number, Uint8Array> {\n const d = dvOf(u), out = new Map<number, Uint8Array>();\n for (let p = 0; p + 8 <= u.length; ) {\n const id = d.getUint32(p, true) & 0x7fffffff, size = d.getUint32(p + 4, true);\n if (p + 8 + size > u.length) break;\n out.set(id, u.subarray(p + 8, p + 8 + size));\n p += 8 + size;\n }\n return out;\n}\n\nexport function parseDM(u: Uint8Array): DetailModel {\n const d = dvOf(u);\n let p = 0;\n const str = () => { const e = u.indexOf(0, p); if (e < 0) throw new Error('.dm: строка не закрыта'); const s = new TextDecoder('latin1').decode(u.subarray(p, e)); p = e + 1; return s; };\n const shader = str(), texture = str();\n if (p + 20 > u.length) throw new Error('.dm обрезан');\n const flags = d.getUint32(p, true), minScale = d.getFloat32(p + 4, true), maxScale = d.getFloat32(p + 8, true), nv = d.getUint32(p + 12, true), ni = d.getUint32(p + 16, true);\n p += 20;\n if (nv > 65536 || ni % 3 || p + nv * 20 + ni * 2 > u.length) throw new Error('.dm: неверное число вершин или индексов');\n const pos = new Float32Array(nv * 3), uv = new Float32Array(nv * 2), indices = new Uint16Array(ni);\n for (let i = 0; i < nv; i++, p += 20) { pos[i * 3] = d.getFloat32(p, true); pos[i * 3 + 1] = d.getFloat32(p + 4, true); pos[i * 3 + 2] = d.getFloat32(p + 8, true); uv[i * 2] = d.getFloat32(p + 12, true); uv[i * 2 + 1] = d.getFloat32(p + 16, true); }\n for (let i = 0; i < ni; i++, p += 2) indices[i] = d.getUint16(p, true);\n return { shader, texture, flags, minScale, maxScale, pos, uv, indices };\n}\n\n/** Меш модели .dm (нормали вверх — как освещается трава). */\nexport function dmMesh(m: DetailModel, name: string): Mesh {\n const n = m.pos.length / 3, nor = new Float32Array(n * 3);\n for (let i = 0; i < n; i++) nor[i * 3 + 1] = 1;\n return { name, texture: m.texture, shader: m.shader, vertexCount: n, pos: m.pos, nor, uv: m.uv, bone: new Float32Array(n * 4), weight: new Float32Array(n * 4).map((_, i) => (i % 4 === 0 ? 1 : 0)), indices: m.indices, skinned: false, alphaTest: true };\n}\n\nexport interface DetailsMap { version: number; offsX: number; offsZ: number; sizeX: number; sizeZ: number; models: DetailModel[]; slots: DataView }\n\nexport function parseDetails(u: Uint8Array): DetailsMap {\n const c = chunks(u), h = c.get(0), objs = c.get(1), sl = c.get(2);\n if (!h || h.length < 24 || !objs || !sl) throw new Error('level.details: нет заголовка, моделей или ячеек');\n const hd = dvOf(h), [version, , offsX, offsZ, sizeX, sizeZ] = [0, 4, 8, 12, 16, 20].map((o) => hd.getUint32(o, true)) as [number, number, number, number, number, number];\n if (version !== 3) throw new Error(`level.details версии ${version} не поддерживается`);\n if (sizeX * sizeZ * 16 > sl.length) throw new Error('level.details: ячейки обрезаны');\n const models = [...chunks(objs)].sort((a, b) => a[0] - b[0]).map(([, b]) => parseDM(b));\n return { version, offsX, offsZ, sizeX, sizeZ, models, slots: dvOf(sl) };\n}\n\n/** Коллизия уровня (level.cform): u32 версия 4, вершин, треугольников, рамка (6×f32); вершины f32×3; треугольники {u32×3, u32 материал}. */\nexport interface CForm { verts: Float32Array; tris: Uint32Array }\nexport function parseCForm(u: Uint8Array): CForm {\n const d = dvOf(u), ver = d.getUint32(0, true), nv = d.getUint32(4, true), nt = d.getUint32(8, true);\n if (ver !== 4 || 36 + nv * 12 + nt * 16 > u.length) throw new Error('level.cform: неизвестный формат');\n const verts = new Float32Array(nv * 3), tris = new Uint32Array(nt * 3);\n for (let i = 0, p = 36; i < nv * 3; i++, p += 4) verts[i] = d.getFloat32(p, true);\n for (let i = 0, p = 36 + nv * 12; i < nt; i++, p += 16) { tris[i * 3] = d.getUint32(p, true); tris[i * 3 + 1] = d.getUint32(p + 4, true); tris[i * 3 + 2] = d.getUint32(p + 8, true); }\n return { verts, tris };\n}\n\n/** CRandom движка (как rand() MSVC). */\nclass CRandom {\n constructor(private s: number) {}\n next() { this.s = (Math.imul(this.s, 214013) + 2531011) >>> 0; return (this.s >>> 16) & 0x7fff; }\n randI(n: number) { return this.next() % n; }\n randF(a: number, b: number) { return a + ((b - a) * this.next()) / 32767; }\n randFs(r: number) { return this.randF(-r, r); }\n}\n\n/** Упорядоченный дизеринг 16×16 (матрица Байера, пороги 0..255). */\nconst DITHER = (() => {\n let m = [[0]];\n for (let n = 1; n < 16; n *= 2) m = Array.from({ length: n * 2 }, (_, y) => Array.from({ length: n * 2 }, (_, x) => 4 * m[y % n]![x % n]! + [[0, 2], [3, 1]][+(y >= n)]![+(x >= n)]!));\n return Uint8Array.from(m.flat());\n})();\nexport const DETAIL_TRI_BUDGET = 1_500_000;\n/** Плотность травы по умолчанию (r__detail_density): шаг сетки точек в метрах. */\nconst DENSITY = 0.3, SLOT = 2;\n\nexport interface DetailsResult { meshes: Mesh[]; items: number; candidates: number; keep: number }\n\n/**\n * Расстановка деталей по карте и коллизии; budget — предел треугольников (лишнее прореживается равномерно).\n * Координаты — как у геометрии уровня (левосторонние, Y вверх).\n */\nexport function buildDetails(map: DetailsMap, cf: CForm, budget = DETAIL_TRI_BUDGET): DetailsResult {\n const { sizeX, sizeZ, offsX, offsZ, slots, models } = map, nObj = models.length;\n const dSize = Math.ceil(SLOT / DENSITY), jitter = DENSITY / 1.7;\n // непустые ячейки и их высоты\n const slotOk = new Uint8Array(sizeX * sizeZ), yLo = new Float32Array(sizeX * sizeZ), yHi = new Float32Array(sizeX * sizeZ);\n for (let i = 0; i < sizeX * sizeZ; i++) {\n const a = slots.getUint32(i * 16, true), b = slots.getUint32(i * 16 + 4, true);\n const ids = [(a >>> 20) & 63, (a >>> 26) & 63, b & 63, (b >>> 6) & 63];\n let any = false;\n for (let k = 0; k < 4; k++) if (ids[k]! < nObj && slots.getUint16(i * 16 + 8 + k * 2, true)) any = true;\n if (!any) continue;\n slotOk[i] = 1; yLo[i] = (a & 0xfff) * 0.2 - 200; yHi[i] = yLo[i]! + ((a >>> 12) & 0xff) * 0.1;\n }\n // треугольники коллизии по ячейкам (только непустые)\n const { verts: V, tris: T } = cf, nt = T.length / 3;\n const cnt = new Uint32Array(sizeX * sizeZ + 1);\n const forCells = (t: number, f: (cell: number) => void) => {\n const a = T[t * 3]! * 3, b = T[t * 3 + 1]! * 3, c = T[t * 3 + 2]! * 3;\n const x0 = Math.floor(Math.min(V[a]!, V[b]!, V[c]!) / SLOT) + offsX, x1 = Math.floor(Math.max(V[a]!, V[b]!, V[c]!) / SLOT) + offsX;\n const z0 = Math.floor(Math.min(V[a + 2]!, V[b + 2]!, V[c + 2]!) / SLOT) + offsZ, z1 = Math.floor(Math.max(V[a + 2]!, V[b + 2]!, V[c + 2]!) / SLOT) + offsZ;\n const ylo = Math.min(V[a + 1]!, V[b + 1]!, V[c + 1]!), yhi = Math.max(V[a + 1]!, V[b + 1]!, V[c + 1]!);\n for (let z = Math.max(0, z0); z <= Math.min(sizeZ - 1, z1); z++) for (let x = Math.max(0, x0); x <= Math.min(sizeX - 1, x1); x++) {\n const cell = z * sizeX + x;\n if (slotOk[cell] && yhi >= yLo[cell]! - 0.01 && ylo <= yHi[cell]! + 0.01) f(cell);\n }\n };\n for (let t = 0; t < nt; t++) forCells(t, (cell) => cnt[cell + 1]!++);\n for (let i = 0; i < sizeX * sizeZ; i++) cnt[i + 1]! += cnt[i]!;\n const fill = cnt.slice(0, sizeX * sizeZ), list = new Uint32Array(cnt[sizeX * sizeZ]!);\n for (let t = 0; t < nt; t++) forCells(t, (cell) => { list[fill[cell]!++] = t; });\n\n // проход по ячейкам: kind 0 — подсчёт треугольников, 1 — постановка\n const triOf = models.map((m) => m.indices.length / 3);\n const alpha = new Float32Array(16), ids = new Int32Array(4), sel: number[] = [];\n const walk = (emit: (obj: number, x: number, y: number, z: number, scale: number, yaw: number, cell: number) => void, needY: boolean) => {\n for (let dz = 0; dz < sizeZ; dz++) for (let dx = 0; dx < sizeX; dx++) {\n const cell = dz * sizeX + dx;\n if (!slotOk[cell]) continue;\n const o = cell * 16, a = slots.getUint32(o, true), b = slots.getUint32(o + 4, true);\n ids[0] = (a >>> 20) & 63; ids[1] = (a >>> 26) & 63; ids[2] = b & 63; ids[3] = (b >>> 6) & 63;\n for (let k = 0; k < 4; k++) { const pal = slots.getUint16(o + 8 + k * 2, true); for (let j = 0; j < 4; j++) alpha[k * 4 + j] = (255 * ((pal >> (j * 4)) & 15)) / 15; }\n const sx = dx - offsX, sz = dz - offsZ, seed = (0x12071980 ^ Math.imul(sx, sz)) >>> 0;\n const rSel = new CRandom(seed), rJit = new CRandom(seed), rYaw = new CRandom(seed), rScl = new CRandom(seed);\n const minX = sx * SLOT, minZ = sz * SLOT, top = yHi[cell]!, bottom = yLo[cell]!;\n for (let z = 0; z <= dSize; z++) for (let x = 0; x <= dSize; x++) {\n const shx = rJit.randI(16), shz = rJit.randI(16);\n sel.length = 0;\n const cx = Math.min(x, dSize - 1), cz = Math.min(z, dSize - 1), fx = cx / dSize, fz = cz / dSize;\n for (let k = 0; k < 4; k++) {\n if (ids[k]! >= nObj) continue;\n const c01 = alpha[k * 4]! * (1 - fx) + alpha[k * 4 + 1]! * fx, c02 = alpha[k * 4 + 2]! * (1 - fx) + alpha[k * 4 + 3]! * fx;\n const c = Math.min(255, Math.max(0, Math.floor(c01 * (1 - fz) + c02 * fz + 0.5)));\n if (c > DITHER[((cx + shx) % 16) * 16 + ((cz + shz) % 16)]!) sel.push(k);\n }\n if (!sel.length) continue;\n const obj = ids[sel.length === 1 ? sel[0]! : sel[rSel.randI(sel.length)]!]!;\n const px = (x / dSize) * SLOT + minX + rJit.randFs(jitter), pz = (z / dSize) * SLOT + minZ + rJit.randFs(jitter);\n let y = bottom - 5;\n if (needY) {\n for (let i = cnt[cell]!; i < cnt[cell + 1]!; i++) {\n const t = list[i]!, A = T[t * 3]! * 3, B = T[t * 3 + 1]! * 3, C = T[t * 3 + 2]! * 3;\n // вертикальный луч: барицентрические координаты в плоскости XZ\n const ax = V[A]!, az = V[A + 2]!, e1x = V[B]! - ax, e1z = V[B + 2]! - az, e2x = V[C]! - ax, e2z = V[C + 2]! - az;\n const det = e1x * e2z - e2x * e1z;\n if (Math.abs(det) < 1e-9) continue;\n const qx = px - ax, qz = pz - az, u = (qx * e2z - e2x * qz) / det, v = (e1x * qz - qx * e1z) / det;\n if (u < 0 || v < 0 || u + v > 1) continue;\n const yt = V[A + 1]! + u * (V[B + 1]! - V[A + 1]!) + v * (V[C + 1]! - V[A + 1]!);\n if (yt <= top + 1e-3 && yt > y) y = yt;\n }\n if (y < bottom) continue;\n }\n const m = models[obj]!, scale = rScl.randF(m.minScale * 0.5, m.maxScale * 0.9), yaw = rYaw.randF(0, Math.PI * 2);\n emit(obj, px, y, pz, scale, yaw, cell);\n }\n }\n };\n let candTris = 0, candidates = 0;\n walk((obj) => { candTris += triOf[obj]!; candidates++; }, false);\n const keep = candTris > budget ? budget / candTris : 1;\n const thin = new CRandom(0x5eed);\n const P: number[][] = models.map(() => []), U: number[][] = models.map(() => []), I: number[][] = models.map(() => []);\n let items = 0;\n walk((obj, x, y, z, s, yaw) => {\n if (keep < 1 && thin.next() / 32768 >= keep) return;\n const m = models[obj]!, c = Math.cos(yaw), sn = Math.sin(yaw), p = P[obj]!, base = p.length / 3;\n if (base + m.pos.length / 3 > 4_000_000) return;\n for (let i = 0; i < m.pos.length; i += 3) {\n const vx = m.pos[i]! * s, vy = m.pos[i + 1]! * s, vz = m.pos[i + 2]! * s;\n p.push(x + vx * c + vz * sn, y + vy, z - vx * sn + vz * c); // поворот вокруг Y (как Fmatrix::rotateY)\n }\n for (let i = 0; i < m.uv.length; i++) U[obj]!.push(m.uv[i]!);\n for (let i = 0; i < m.indices.length; i++) I[obj]!.push(base + m.indices[i]!);\n items++;\n }, true);\n const meshes: Mesh[] = [];\n models.forEach((m, k) => {\n if (!I[k]!.length) return;\n const n = P[k]!.length / 3, nor = new Float32Array(n * 3);\n for (let i = 0; i < n; i++) nor[i * 3 + 1] = 1;\n meshes.push({ name: `Трава · ${m.texture}`, texture: m.texture, shader: m.shader, vertexCount: n, pos: Float32Array.from(P[k]!), nor, uv: Float32Array.from(U[k]!), bone: new Float32Array(n * 4), weight: new Float32Array(n * 4).map((_, i) => (i % 4 === 0 ? 1 : 0)), indices: Uint32Array.from(I[k]!), skinned: false, alphaTest: true });\n });\n return { meshes, items, candidates, keep };\n}\n"],"names":["_","ids"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { DecodedImage, Model } from '@gmvjs/core/types';
2
+ export interface TextureForExport {
3
+ /** Имя исходной текстуры (для имени файла). */
4
+ name: string;
5
+ /** Исходный DDS — копируется без перекодирования. */
6
+ dds?: Uint8Array;
7
+ /** Иначе — декодированная картинка. */
8
+ image?: DecodedImage | null;
9
+ }
10
+ export interface XrayExportOptions {
11
+ /** Имя уровня/объекта: латиница, цифры, «_». */
12
+ name: string;
13
+ /** Папка внутри rawdata/objects и gamedata/textures. По умолчанию «gmv\\<имя>». */
14
+ prefix?: string;
15
+ /** Множитель единиц в метры (X-Ray: 1 = 1 м). */
16
+ scale?: number;
17
+ /** Размер ячейки разбиения, м; 0 — один объект. По умолчанию: 0 для моделей, 32 м для сцен больше 100 м или 60 тыс. треугольников, 64 м — больше 400 м. */
18
+ cell?: number;
19
+ /** Текстура меша по номеру (из сессии просмотра). */
20
+ texture?: (meshIndex: number) => Promise<TextureForExport | null>;
21
+ /** Скрытые меши не выгружаются. */
22
+ hidden?: ReadonlySet<number>;
23
+ /**
24
+ * Объекты уровня (model.instances): true (по умолчанию) — каждая модель одним .object в «<префикс>\\props», в сцене — ссылки с позами;
25
+ * false — объекты вливаются в геометрию ячеек.
26
+ */
27
+ instances?: boolean;
28
+ /**
29
+ * Источник — сам X-Ray (скомпилированный уровень, OGF, .object): имена текстур, шейдеры и материалы остаются игровыми,
30
+ * текстуры не копируются (они уже есть в gamedata).
31
+ */
32
+ xraySource?: boolean;
33
+ }
34
+ export interface XrayExportResult {
35
+ files: Map<string, Uint8Array>;
36
+ report: string[];
37
+ }
38
+ export declare function exportXraySdk(model: Model, o: XrayExportOptions): Promise<XrayExportResult>;