@gmvjs/xray-v1 0.1.0 → 0.1.1
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.
- package/README.md +35 -29
- package/dist/db.d.ts +2 -2
- package/dist/db.js.map +1 -1
- package/dist/details.d.ts +4 -4
- package/dist/details.js.map +1 -1
- package/dist/export.d.ts +13 -13
- package/dist/export.js.map +1 -1
- package/dist/games/cop.js.map +1 -1
- package/dist/games/cs.js.map +1 -1
- package/dist/games/sdk.d.ts +1 -1
- package/dist/games/sdk.js.map +1 -1
- package/dist/games/soc.d.ts +1 -1
- package/dist/games/soc.js.map +1 -1
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/level.d.ts +5 -5
- package/dist/level.js.map +1 -1
- package/dist/load.d.ts +1 -1
- package/dist/load.js.map +1 -1
- package/dist/lzhuf.d.ts +1 -1
- package/dist/lzhuf.js.map +1 -1
- package/dist/motion.d.ts +6 -6
- package/dist/motion.js.map +1 -1
- package/dist/object.d.ts +12 -12
- package/dist/object.js.map +1 -1
- package/dist/ogf.d.ts +6 -6
- package/dist/ogf.js.map +1 -1
- package/dist/scene.d.ts +6 -6
- package/dist/scene.js.map +1 -1
- package/dist/skeleton.d.ts +4 -4
- package/dist/skeleton.js +4 -4
- package/dist/skeleton.js.map +1 -1
- package/dist/smotion.d.ts +5 -5
- package/dist/smotion.js.map +1 -1
- package/dist/soc-key.d.ts +6 -6
- package/dist/soc-key.js.map +1 -1
- package/dist/thm.d.ts +1 -1
- package/dist/thm.js.map +1 -1
- package/dist/write-object.d.ts +7 -7
- package/dist/write-object.js +1 -1
- package/dist/write-object.js.map +1 -1
- package/dist/write-omf.d.ts +9 -9
- package/dist/write-scene.d.ts +6 -6
- package/dist/write-scene.js.map +1 -1
- package/package.json +3 -3
package/dist/i18n.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.js","sources":["../src/i18n.ts"],"sourcesContent":["//
|
|
1
|
+
{"version":3,"file":"i18n.js","sources":["../src/i18n.ts"],"sourcesContent":["// English texts of the xray-v1 engine (Russian ones live in the plugins themselves).\nimport type { EngineTexts } from '@gmvjs/core/plugin';\n\nexport const EN: EngineTexts = {\n \"presets\": {\n \"stalker-soc\": {\n \"title\": \"S.T.A.L.K.E.R.: Shadow of Chernobyl\",\n \"hint\": \"OGF v4. Animations are inside the model or in an external .omf. gamedata.db* archives open through “Game folder” (the file-table key is taken from your game’s bin/XR_3DA.exe) or with “Open .db…” together with XR_3DA.exe; mod .xdb archives and an unpacked gamedata folder open too. External .omf files referenced by a model are attached automatically when the gamedata folder or archives are open; torso animations play together with legs (choose in “Other body parts”). Levels include grass from level.details; SDK sources: .object with skeleton and animations, .skl/.skls.\",\n \"archiveLabel\": \"Open .db…\"\n },\n \"stalker-cs\": {\n \"title\": \"S.T.A.L.K.E.R.: Clear Sky\",\n \"hint\": \"OGF v4. Open resources.db0–db2 (models and textures) and patches/xpatch_*.db, or a gamedata folder. External .omf files referenced by a model are attached automatically when the gamedata folder or archives are open; torso animations play together with legs (choose in “Other body parts”). Levels include grass from level.details; SDK sources: .object with skeleton and animations, .skl/.skls.\",\n \"archiveLabel\": \"Open .db…\"\n },\n \"stalker-cop\": {\n \"title\": \"S.T.A.L.K.E.R.: Call of Pripyat\",\n \"hint\": \"OGF v4, including 3- and 4-weight vertices. .db archives as in Clear Sky. External .omf files referenced by a model are attached automatically when the gamedata folder or archives are open; torso animations play together with legs (choose in “Other body parts”). Levels include grass from level.details; SDK sources: .object with skeleton and animations, .skl/.skls.\",\n \"archiveLabel\": \"Open .db…\"\n }\n },\n \"exporters\": {\n \"xray-sdk\": {\n \"title\": \"S.T.A.L.K.E.R.: X-Ray SDK 0.7 sources\",\n \"description\": \"For LevelEditor and ActorEditor (Call of Pripyat, SDK 0.7): .object geometry (large maps are split into cells), a .level + scene_object.part scene, DDS + .thm textures — one ZIP. Lights, sectors, the AI grid and the xrLC build are done in the SDK.\",\n \"fields\": {\n \"name\": {\n \"label\": \"Level name\",\n \"hint\": \"Latin letters, digits, \\\"_\\\": rawdata/objects/gmv/<name>, rawdata/levels/<name>.level\"\n },\n \"cell\": {\n \"label\": \"Cell, m\",\n \"placeholder\": \"auto\",\n \"hint\": \"Split the geometry into objects (0 — a single object; 32–64 suits maps)\"\n },\n \"scale\": {\n \"label\": \"Scale\",\n \"hint\": \"Metres per model unit (X-Ray: 1 unit = 1 m)\"\n },\n \"instances\": {\n \"label\": \"Level objects as references\",\n \"hint\": \"Each object model becomes one .object in props, the scene holds references with poses. Uncheck to merge objects into the cell geometry\"\n }\n }\n }\n }\n};\n"],"names":[],"mappings":"AAGO,MAAM,KAAkB;AAAA,EAC7B,WAAW;AAAA,IACT,eAAe;AAAA,MACb,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,gBAAgB;AAAA,IAAA;AAAA,IAElB,cAAc;AAAA,MACZ,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,gBAAgB;AAAA,IAAA;AAAA,IAElB,eAAe;AAAA,MACb,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,gBAAgB;AAAA,IAAA;AAAA,EAClB;AAAA,EAEF,aAAa;AAAA,IACX,YAAY;AAAA,MACV,SAAS;AAAA,MACT,eAAe;AAAA,MACf,UAAU;AAAA,QACR,QAAQ;AAAA,UACN,SAAS;AAAA,UACT,QAAQ;AAAA,QAAA;AAAA,QAEV,QAAQ;AAAA,UACN,SAAS;AAAA,UACT,eAAe;AAAA,UACf,QAAQ;AAAA,QAAA;AAAA,QAEV,SAAS;AAAA,UACP,SAAS;AAAA,UACT,QAAQ;AAAA,QAAA;AAAA,QAEV,aAAa;AAAA,UACX,SAAS;AAAA,UACT,QAAQ;AAAA,QAAA;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAEJ;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export declare const xrayV1Engine: import("@gmvjs/core/plugin").EnginePlugin;
|
|
2
|
-
/**
|
|
2
|
+
/** Export of a model/level into X-Ray SDK sources (.object, .level, textures); the module is loaded on demand. */
|
|
3
3
|
export declare function exportXraySdk(...args: Parameters<typeof import('./export').exportXraySdk>): ReturnType<typeof import('./export').exportXraySdk>;
|
|
4
4
|
export type { XrayExportOptions, XrayExportResult, TextureForExport } from './export';
|
|
5
5
|
export { autoLayers, XRAY_FX_PART, type XrayMotion, type XrayTrack } from './motion';
|
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ const xrayV1Engine = defineEngine({
|
|
|
17
17
|
extensions: ["ogf"],
|
|
18
18
|
motionExtensions: ["omf", "skl", "skls"],
|
|
19
19
|
status: "beta",
|
|
20
|
-
//
|
|
20
|
+
// the first chunk is OGF_HEADER (id 1), then a version byte; otherwise not certain, but not excluded either
|
|
21
21
|
sniff: (h) => u32le(h, 0) === 1 ? h[8] === 4 ? 1 : 0.6 : 0.2,
|
|
22
22
|
load: async (main, ctx) => (await import("./load.js")).loadOgf(main, ctx)
|
|
23
23
|
},
|
|
@@ -38,9 +38,9 @@ const xrayV1Engine = defineEngine({
|
|
|
38
38
|
extensions: [],
|
|
39
39
|
pathPattern: /(^|\/)levels\/[^/]+\/level$/,
|
|
40
40
|
status: "beta",
|
|
41
|
-
//
|
|
41
|
+
// the first chunk is the level header (id 1, 4 bytes: version and quality)
|
|
42
42
|
sniff: (h) => u32le(h, 0) === 1 && u32le(h, 4) === 4 && u32le(h, 8) !== 16 ? 0.9 : 0,
|
|
43
|
-
// 16
|
|
43
|
+
// 16 is the Metro 2033 (4A) level version
|
|
44
44
|
load: async (main, ctx) => (await import("./level.js")).loadLevel(main, ctx)
|
|
45
45
|
},
|
|
46
46
|
{
|
|
@@ -49,7 +49,7 @@ const xrayV1Engine = defineEngine({
|
|
|
49
49
|
engine: "xray-v1",
|
|
50
50
|
extensions: ["dm"],
|
|
51
51
|
status: "beta",
|
|
52
|
-
//
|
|
52
|
+
// starts with a shader name (Latin letters, "\\", "_")
|
|
53
53
|
sniff: (h) => /^[\w\\.-]{2,}\0/.test(ascii(h, 0, Math.min(h.length, 64))) ? 0.8 : 0,
|
|
54
54
|
load: async (main) => {
|
|
55
55
|
const { parseDM, dmMesh } = await import("./details.js");
|
|
@@ -64,7 +64,7 @@ const xrayV1Engine = defineEngine({
|
|
|
64
64
|
engine: "xray-v1",
|
|
65
65
|
extensions: ["level"],
|
|
66
66
|
status: "beta",
|
|
67
|
-
// SDK 0.7 (
|
|
67
|
+
// SDK 0.7 (CoP) saves the scene as ini; binary .level files of older SDKs are not read yet
|
|
68
68
|
sniff: (h) => /^\s*\[(build_params|camera|guid|level_options|level_tag|version)\]/.test(ascii(h, 0, Math.min(h.length, 64))) ? 1 : 0,
|
|
69
69
|
load: async (main, ctx) => (await import("./scene.js")).loadSceneLevel(main, ctx)
|
|
70
70
|
}
|
|
@@ -75,11 +75,11 @@ const xrayV1Engine = defineEngine({
|
|
|
75
75
|
title: "X-Ray .db / .xdb (Чистое небо, Зов Припяти)",
|
|
76
76
|
engine: "xray-v1",
|
|
77
77
|
extensions: DB_EXTS,
|
|
78
|
-
//
|
|
78
|
+
// SoC: the game executable is an archive companion (the file table key is taken from it)
|
|
79
79
|
companionExtensions: ["exe"],
|
|
80
|
-
//
|
|
80
|
+
// in the "Game folder" archives open by themselves (only the file table is read): CS/CoP with a header, SoC gamedata.db*
|
|
81
81
|
openFromFolder: (h, name) => u32le(h, 0) === 666 || u32le(h, 0) === 0 && /^gamedata\.db[0-9a-z]$/i.test(name),
|
|
82
|
-
//
|
|
82
|
+
// CS/CoP start with the text [header] chunk (id 0x29A), SoC and mods with the data chunk (id 0)
|
|
83
83
|
sniff: (h) => u32le(h, 0) === 666 ? 1 : u32le(h, 0) === 0 ? 0.5 : 0,
|
|
84
84
|
open: async (file, siblings) => (await import("./db.js")).openXrayDb(file, siblings)
|
|
85
85
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["// X-Ray Engine (S.T.A.L.K.E.R.): OGF v4 + анимации OMF, архивы .db/.xdb.\nimport { ascii, u32le } from '@gmvjs/core/binary';\nimport { defineEngine, type GamePreset } from '@gmvjs/core/plugin';\nimport { EN } from './i18n';\n\nconst DB_EXTS = ['db', 'xdb', ...'0123456789'.split('').flatMap((d) => ['db' + d, 'xdb' + d]), 'dba', 'dbb', 'dbc', 'dbd'];\nconst ui: GamePreset['ui'] = { motionLabel: '+ .omf', invertQuat: true, archives: true, archiveExts: [...DB_EXTS, 'exe'], archiveLabel: 'Открыть .db…' };\nconst game = (id: string, title: string, hint: string): GamePreset => ({ id, group: 'Игры', title, engine: 'xray-v1', formats: ['xray-ogf', 'xray-object', 'xray-level', 'xray-dm', 'xray-scene'], hint, ui, status: 'beta', markers: ['gamedata', 'fsgame.ltx'] });\n\nexport const xrayV1Engine = defineEngine({\n id: 'xray-v1',\n title: 'X-Ray Engine 1.x',\n i18n: { en: EN },\n formats: [\n {\n id: 'xray-ogf',\n title: 'X-Ray OGF',\n engine: 'xray-v1',\n extensions: ['ogf'],\n motionExtensions: ['omf', 'skl', 'skls'],\n status: 'beta',\n // первый чанк — OGF_HEADER (id 1), затем байт версии; иначе не уверены, но и не исключаем\n sniff: (h) => (u32le(h, 0) === 1 ? (h[8] === 4 ? 1 : 0.6) : 0.2),\n load: async (main, ctx) => (await import('./load')).loadOgf(main, ctx),\n },\n {\n id: 'xray-object',\n title: 'X-Ray SDK .object',\n engine: 'xray-v1',\n extensions: ['object'],\n motionExtensions: ['omf', 'skl', 'skls'],\n status: 'beta',\n sniff: (h) => (u32le(h, 0) === 0x7777 ? 1 : 0),\n load: async (main, ctx) => (await import('./load')).loadObject(main, ctx),\n },\n {\n id: 'xray-level',\n title: 'X-Ray: скомпилированный уровень (level + level.geom)',\n engine: 'xray-v1',\n extensions: [],\n pathPattern: /(^|\\/)levels\\/[^/]+\\/level$/,\n status: 'beta',\n // первый чанк — заголовок уровня (id 1, 4 байта: версия и качество)\n sniff: (h) => (u32le(h, 0) === 1 && u32le(h, 4) === 4 && u32le(h, 8) !== 16 ? 0.9 : 0), // 16 — версия уровня Metro 2033 (4A)\n load: async (main, ctx) => (await import('./level')).loadLevel(main, ctx),\n },\n {\n id: 'xray-dm',\n title: 'X-Ray: деталь (.dm — трава, кусты)',\n engine: 'xray-v1',\n extensions: ['dm'],\n status: 'beta',\n // начинается с имени шейдера (латиница, «\\\\», «_»)\n sniff: (h) => (/^[\\w\\\\.-]{2,}\\0/.test(ascii(h, 0, Math.min(h.length, 64))) ? 0.8 : 0),\n load: async (main) => {\n const { parseDM, dmMesh } = await import('./details');\n const { boundsOf } = await import('@gmvjs/core/geometry');\n const m = parseDM(new Uint8Array(await main.arrayBuffer())), mesh = dmMesh(m, m.texture);\n return { meshes: [mesh], skeleton: null, motions: [], bounds: boundsOf([mesh]), flipZ: true, info: [`Деталь X-Ray: ${m.shader} · ${m.texture}`, `Вершин ${mesh.vertexCount}, треугольников ${m.indices.length / 3}, масштаб ${m.minScale}…${m.maxScale}`] };\n },\n },\n {\n id: 'xray-scene',\n title: 'X-Ray SDK: уровень (.level + scene_object.part)',\n engine: 'xray-v1',\n extensions: ['level'],\n status: 'beta',\n // SDK 0.7 (ЗП) сохраняет сцену как ini; бинарные .level старых SDK пока не читаются\n sniff: (h) => (/^\\s*\\[(build_params|camera|guid|level_options|level_tag|version)\\]/.test(ascii(h, 0, Math.min(h.length, 64))) ? 1 : 0),\n load: async (main, ctx) => (await import('./scene')).loadSceneLevel(main, ctx),\n },\n ],\n archives: [\n {\n id: 'xray-db',\n title: 'X-Ray .db / .xdb (Чистое небо, Зов Припяти)',\n engine: 'xray-v1',\n extensions: DB_EXTS,\n // ТЧ: исполняемый файл игры — спутник архивов (из него берётся ключ таблиц файлов)\n companionExtensions: ['exe'],\n // в «Папке игры» архивы открываются сами (читается только таблица файлов): ЧН/ЗП — с заголовком, ТЧ — gamedata.db*\n openFromFolder: (h, name) => u32le(h, 0) === 0x29a || (u32le(h, 0) === 0 && /^gamedata\\.db[0-9a-z]$/i.test(name)),\n // ЧН/ЗП начинаются с текстового чанка [header] (id 0x29A), ТЧ и моды — с чанка данных (id 0)\n sniff: (h) => (u32le(h, 0) === 0x29a ? 1 : u32le(h, 0) === 0 ? 0.5 : 0),\n open: async (file, siblings) => (await import('./db')).openXrayDb(file, siblings),\n },\n ],\n exporters: [\n {\n id: 'xray-sdk',\n title: 'S.T.A.L.K.E.R.: исходники X-Ray SDK 0.7',\n target: 'xray',\n description: 'Для LevelEditor и ActorEditor (Зов Припяти, SDK 0.7): геометрия .object (большие карты режутся на ячейки), сцена .level + scene_object.part, текстуры DDS + .thm — одним ZIP. Свет, секторы, AI-сетку и сборку xrLC делаете в SDK.',\n from: ['4a', 'xray-v1'],\n fields: [\n { id: 'name', label: 'Имя уровня', type: 'text', hint: 'Латиница, цифры, «_»: rawdata/objects/gmv/<имя>, rawdata/levels/<имя>.level', default: (_m, base) => base.toLowerCase().replace(/[^a-z0-9_]+/g, '_').replace(/^_+|_+$/g, '') || 'level' },\n { id: 'cell', label: 'Ячейка, м', type: 'number', min: 0, step: 8, placeholder: 'авто', hint: 'Разбиение геометрии на объекты (0 — одним объектом; для карт удобно 32–64)' },\n { id: 'scale', label: 'Масштаб', type: 'number', min: 0, step: 0.01, default: 1, hint: 'Сколько метров в одной единице модели (X-Ray: 1 единица = 1 м)' },\n { id: 'instances', label: 'Объекты уровня — ссылками', type: 'checkbox', default: true, hint: 'Каждая модель объекта одним .object в props, в сцене — ссылки с позами. Снимите, чтобы влить объекты в геометрию ячеек', when: (m) => !!m.instances },\n ],\n run: async (model, v, ctx) => {\n const { exportXraySdk } = await import('./export');\n const name = String(v.name || ctx.baseName);\n const r = await exportXraySdk(model, {\n name, cell: v.cell === '' || v.cell === undefined ? undefined : +v.cell, scale: +v.scale || 1, hidden: ctx.hidden,\n texture: (i) => ctx.texture(i), xraySource: ctx.sourceEngine === 'xray-v1', instances: v.instances !== false,\n });\n return { ...r, name: `${name}_xray_sdk` };\n },\n },\n ],\n games: [\n game('stalker-soc', 'S.T.A.L.K.E.R.: Тень Чернобыля', 'OGF v4. Анимации — внутри модели или во внешнем .omf. Архивы gamedata.db* открываются через «Папку игры» (ключ таблиц файлов берётся из bin/XR_3DA.exe вашей игры) или кнопкой «Открыть .db…» вместе с XR_3DA.exe; .xdb модов и распакованная gamedata тоже открываются. Внешние .omf из ссылок модели подключаются сами, если открыта папка gamedata или архивы; анимации торса играют вместе с ногами (выбор — «Другие части тела»). Уровни — с травой из level.details; исходники SDK: .object со скелетом и анимациями, .skl/.skls.'),\n game('stalker-cs', 'S.T.A.L.K.E.R.: Чистое небо', 'OGF v4. Откройте resources.db0–db2 (модели и текстуры) и patches/xpatch_*.db или папку gamedata. Внешние .omf из ссылок модели подключаются сами, если открыта папка gamedata или архивы; анимации торса играют вместе с ногами (выбор — «Другие части тела»). Уровни — с травой из level.details; исходники SDK: .object со скелетом и анимациями, .skl/.skls.'),\n game('stalker-cop', 'S.T.A.L.K.E.R.: Зов Припяти', 'OGF v4, в том числе 3/4-весовые вершины. Архивы .db — как у Чистого неба. Внешние .omf из ссылок модели подключаются сами, если открыта папка gamedata или архивы; анимации торса играют вместе с ногами (выбор — «Другие части тела»). Уровни — с травой из level.details; исходники SDK: .object со скелетом и анимациями, .skl/.skls.'),\n ],\n});\n\n/** Выгрузка модели/уровня в исходники X-Ray SDK (.object, .level, текстуры); модуль подгружается по требованию. */\nexport async function exportXraySdk(...args: Parameters<typeof import('./export').exportXraySdk>): ReturnType<typeof import('./export').exportXraySdk> {\n return (await import('./export')).exportXraySdk(...args);\n}\nexport type { XrayExportOptions, XrayExportResult, TextureForExport } from './export';\nexport { autoLayers, XRAY_FX_PART, type XrayMotion, type XrayTrack } from './motion';\n"],"names":["exportXraySdk"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["// X-Ray Engine 1.x (S.T.A.L.K.E.R.): OGF v4 + OMF animations, .db/.xdb archives.\nimport { ascii, u32le } from '@gmvjs/core/binary';\nimport { defineEngine, type GamePreset } from '@gmvjs/core/plugin';\nimport { EN } from './i18n';\n\nconst DB_EXTS = ['db', 'xdb', ...'0123456789'.split('').flatMap((d) => ['db' + d, 'xdb' + d]), 'dba', 'dbb', 'dbc', 'dbd'];\nconst ui: GamePreset['ui'] = { motionLabel: '+ .omf', invertQuat: true, archives: true, archiveExts: [...DB_EXTS, 'exe'], archiveLabel: 'Открыть .db…' };\nconst game = (id: string, title: string, hint: string): GamePreset => ({ id, group: 'Игры', title, engine: 'xray-v1', formats: ['xray-ogf', 'xray-object', 'xray-level', 'xray-dm', 'xray-scene'], hint, ui, status: 'beta', markers: ['gamedata', 'fsgame.ltx'] });\n\nexport const xrayV1Engine = defineEngine({\n id: 'xray-v1',\n title: 'X-Ray Engine 1.x',\n i18n: { en: EN },\n formats: [\n {\n id: 'xray-ogf',\n title: 'X-Ray OGF',\n engine: 'xray-v1',\n extensions: ['ogf'],\n motionExtensions: ['omf', 'skl', 'skls'],\n status: 'beta',\n // the first chunk is OGF_HEADER (id 1), then a version byte; otherwise not certain, but not excluded either\n sniff: (h) => (u32le(h, 0) === 1 ? (h[8] === 4 ? 1 : 0.6) : 0.2),\n load: async (main, ctx) => (await import('./load')).loadOgf(main, ctx),\n },\n {\n id: 'xray-object',\n title: 'X-Ray SDK .object',\n engine: 'xray-v1',\n extensions: ['object'],\n motionExtensions: ['omf', 'skl', 'skls'],\n status: 'beta',\n sniff: (h) => (u32le(h, 0) === 0x7777 ? 1 : 0),\n load: async (main, ctx) => (await import('./load')).loadObject(main, ctx),\n },\n {\n id: 'xray-level',\n title: 'X-Ray: скомпилированный уровень (level + level.geom)',\n engine: 'xray-v1',\n extensions: [],\n pathPattern: /(^|\\/)levels\\/[^/]+\\/level$/,\n status: 'beta',\n // the first chunk is the level header (id 1, 4 bytes: version and quality)\n sniff: (h) => (u32le(h, 0) === 1 && u32le(h, 4) === 4 && u32le(h, 8) !== 16 ? 0.9 : 0), // 16 is the Metro 2033 (4A) level version\n load: async (main, ctx) => (await import('./level')).loadLevel(main, ctx),\n },\n {\n id: 'xray-dm',\n title: 'X-Ray: деталь (.dm — трава, кусты)',\n engine: 'xray-v1',\n extensions: ['dm'],\n status: 'beta',\n // starts with a shader name (Latin letters, \"\\\\\", \"_\")\n sniff: (h) => (/^[\\w\\\\.-]{2,}\\0/.test(ascii(h, 0, Math.min(h.length, 64))) ? 0.8 : 0),\n load: async (main) => {\n const { parseDM, dmMesh } = await import('./details');\n const { boundsOf } = await import('@gmvjs/core/geometry');\n const m = parseDM(new Uint8Array(await main.arrayBuffer())), mesh = dmMesh(m, m.texture);\n return { meshes: [mesh], skeleton: null, motions: [], bounds: boundsOf([mesh]), flipZ: true, info: [`Деталь X-Ray: ${m.shader} · ${m.texture}`, `Вершин ${mesh.vertexCount}, треугольников ${m.indices.length / 3}, масштаб ${m.minScale}…${m.maxScale}`] };\n },\n },\n {\n id: 'xray-scene',\n title: 'X-Ray SDK: уровень (.level + scene_object.part)',\n engine: 'xray-v1',\n extensions: ['level'],\n status: 'beta',\n // SDK 0.7 (CoP) saves the scene as ini; binary .level files of older SDKs are not read yet\n sniff: (h) => (/^\\s*\\[(build_params|camera|guid|level_options|level_tag|version)\\]/.test(ascii(h, 0, Math.min(h.length, 64))) ? 1 : 0),\n load: async (main, ctx) => (await import('./scene')).loadSceneLevel(main, ctx),\n },\n ],\n archives: [\n {\n id: 'xray-db',\n title: 'X-Ray .db / .xdb (Чистое небо, Зов Припяти)',\n engine: 'xray-v1',\n extensions: DB_EXTS,\n // SoC: the game executable is an archive companion (the file table key is taken from it)\n companionExtensions: ['exe'],\n // in the \"Game folder\" archives open by themselves (only the file table is read): CS/CoP with a header, SoC gamedata.db*\n openFromFolder: (h, name) => u32le(h, 0) === 0x29a || (u32le(h, 0) === 0 && /^gamedata\\.db[0-9a-z]$/i.test(name)),\n // CS/CoP start with the text [header] chunk (id 0x29A), SoC and mods with the data chunk (id 0)\n sniff: (h) => (u32le(h, 0) === 0x29a ? 1 : u32le(h, 0) === 0 ? 0.5 : 0),\n open: async (file, siblings) => (await import('./db')).openXrayDb(file, siblings),\n },\n ],\n exporters: [\n {\n id: 'xray-sdk',\n title: 'S.T.A.L.K.E.R.: исходники X-Ray SDK 0.7',\n target: 'xray',\n description: 'Для LevelEditor и ActorEditor (Зов Припяти, SDK 0.7): геометрия .object (большие карты режутся на ячейки), сцена .level + scene_object.part, текстуры DDS + .thm — одним ZIP. Свет, секторы, AI-сетку и сборку xrLC делаете в SDK.',\n from: ['4a', 'xray-v1'],\n fields: [\n { id: 'name', label: 'Имя уровня', type: 'text', hint: 'Латиница, цифры, «_»: rawdata/objects/gmv/<имя>, rawdata/levels/<имя>.level', default: (_m, base) => base.toLowerCase().replace(/[^a-z0-9_]+/g, '_').replace(/^_+|_+$/g, '') || 'level' },\n { id: 'cell', label: 'Ячейка, м', type: 'number', min: 0, step: 8, placeholder: 'авто', hint: 'Разбиение геометрии на объекты (0 — одним объектом; для карт удобно 32–64)' },\n { id: 'scale', label: 'Масштаб', type: 'number', min: 0, step: 0.01, default: 1, hint: 'Сколько метров в одной единице модели (X-Ray: 1 единица = 1 м)' },\n { id: 'instances', label: 'Объекты уровня — ссылками', type: 'checkbox', default: true, hint: 'Каждая модель объекта одним .object в props, в сцене — ссылки с позами. Снимите, чтобы влить объекты в геометрию ячеек', when: (m) => !!m.instances },\n ],\n run: async (model, v, ctx) => {\n const { exportXraySdk } = await import('./export');\n const name = String(v.name || ctx.baseName);\n const r = await exportXraySdk(model, {\n name, cell: v.cell === '' || v.cell === undefined ? undefined : +v.cell, scale: +v.scale || 1, hidden: ctx.hidden,\n texture: (i) => ctx.texture(i), xraySource: ctx.sourceEngine === 'xray-v1', instances: v.instances !== false,\n });\n return { ...r, name: `${name}_xray_sdk` };\n },\n },\n ],\n games: [\n game('stalker-soc', 'S.T.A.L.K.E.R.: Тень Чернобыля', 'OGF v4. Анимации — внутри модели или во внешнем .omf. Архивы gamedata.db* открываются через «Папку игры» (ключ таблиц файлов берётся из bin/XR_3DA.exe вашей игры) или кнопкой «Открыть .db…» вместе с XR_3DA.exe; .xdb модов и распакованная gamedata тоже открываются. Внешние .omf из ссылок модели подключаются сами, если открыта папка gamedata или архивы; анимации торса играют вместе с ногами (выбор — «Другие части тела»). Уровни — с травой из level.details; исходники SDK: .object со скелетом и анимациями, .skl/.skls.'),\n game('stalker-cs', 'S.T.A.L.K.E.R.: Чистое небо', 'OGF v4. Откройте resources.db0–db2 (модели и текстуры) и patches/xpatch_*.db или папку gamedata. Внешние .omf из ссылок модели подключаются сами, если открыта папка gamedata или архивы; анимации торса играют вместе с ногами (выбор — «Другие части тела»). Уровни — с травой из level.details; исходники SDK: .object со скелетом и анимациями, .skl/.skls.'),\n game('stalker-cop', 'S.T.A.L.K.E.R.: Зов Припяти', 'OGF v4, в том числе 3/4-весовые вершины. Архивы .db — как у Чистого неба. Внешние .omf из ссылок модели подключаются сами, если открыта папка gamedata или архивы; анимации торса играют вместе с ногами (выбор — «Другие части тела»). Уровни — с травой из level.details; исходники SDK: .object со скелетом и анимациями, .skl/.skls.'),\n ],\n});\n\n/** Export of a model/level into X-Ray SDK sources (.object, .level, textures); the module is loaded on demand. */\nexport async function exportXraySdk(...args: Parameters<typeof import('./export').exportXraySdk>): ReturnType<typeof import('./export').exportXraySdk> {\n return (await import('./export')).exportXraySdk(...args);\n}\nexport type { XrayExportOptions, XrayExportResult, TextureForExport } from './export';\nexport { autoLayers, XRAY_FX_PART, type XrayMotion, type XrayTrack } from './motion';\n"],"names":["exportXraySdk"],"mappings":"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"}
|
package/dist/level.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ interface VB {
|
|
|
12
12
|
tan: number;
|
|
13
13
|
bin: number;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
15
|
+
/** Vertex buffers: the declaration up to D3DDECL_END (stream 0xFF), the vertex count, the data. */
|
|
16
16
|
export declare function parseVBs(u: Uint8Array): VB[];
|
|
17
17
|
export declare function parseIBs(u: Uint8Array): Uint16Array[];
|
|
18
18
|
export interface LevelVisual {
|
|
@@ -26,14 +26,14 @@ export interface LevelVisual {
|
|
|
26
26
|
ibCount: number;
|
|
27
27
|
xform: Float32Array | null;
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
29
|
+
/** Index window of a progressive mesh (SWI): offset and triangle count of the most detailed level. */
|
|
30
30
|
type Slide = [offset: number, tris: number];
|
|
31
|
-
/**
|
|
31
|
+
/** The level's shared SWIs (level.geom, chunk 11; CS/CoP): TREE_PM trees refer to them by number (visual chunk 20). */
|
|
32
32
|
export declare function parseSwis(u: Uint8Array): Array<Slide | null>;
|
|
33
33
|
export declare function parseVisuals(u: Uint8Array, swis?: Array<Slide | null>): LevelVisual[];
|
|
34
|
-
/**
|
|
34
|
+
/** Level shader string "shader/texture,lightmap…" -> [shader, texture]. */
|
|
35
35
|
export declare function splitShader(s: string): [string, string];
|
|
36
|
-
/**
|
|
36
|
+
/** Level: visuals with geometry, merged by shader. */
|
|
37
37
|
export declare function levelModel(level: Uint8Array, geom: Uint8Array, name: string): Model;
|
|
38
38
|
export declare function loadLevel(main: VFile, ctx: LoadContext): Promise<Model>;
|
|
39
39
|
export {};
|
package/dist/level.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"level.js","sources":["../src/level.ts"],"sourcesContent":["// Скомпилированный уровень X-Ray (levels/<уровень>/level + level.geom): статическая геометрия для просмотра и сверки.\n// level: 1 заголовок, 2 шейдеры («шейдер/текстура,лайтмап»), 3 визуалы (OGF-подобные чанки), 8 секторы.\n// level.geom: 9 вершинные буферы (объявление D3DVERTEXELEMENT9 + данные), 10 индексные буферы.\n// Визуал ссылается на буферы чанком GCONTAINER (ЧН/ЗП, 21) или VCONTAINER/ICONTAINER (ТЧ, 7/8); индексы — от начала своего куска VB.\nimport { boundsOf } from '@gmvjs/core/geometry';\nimport { lzhufDecompress } from './lzhuf';\nimport type { LoadContext } from '@gmvjs/core/plugin';\nimport type { Mesh, Model, VFile } from '@gmvjs/core/types';\n\nconst FSL = { HEADER: 1, SHADERS: 2, VISUALS: 3, VB: 9, IB: 10, SWIS: 11 } as const;\nconst OGFL = { HEADER: 1, SWIDATA: 6, VCONTAINER: 7, ICONTAINER: 8, CHILDREN_L: 10, TREEDEF2: 12, SWICONTAINER: 20, GCONTAINER: 21 } as const;\n/** Типы визуалов с собственной геометрией: обычный, прогрессивный, деревья. */\nconst GEOM_TYPES = new Set([0, 2, 7, 11]);\n\ninterface Chunk { id: number; data: Uint8Array }\nfunction chunks(u8: Uint8Array): Chunk[] {\n const dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength), out: Chunk[] = [];\n let p = 0;\n while (p + 8 <= u8.length) {\n const raw = dv.getUint32(p, true), size = dv.getUint32(p + 4, true);\n if (p + 8 + size > u8.length) break;\n let data = u8.subarray(p + 8, p + 8 + size);\n if (raw & 0x80000000) data = lzhufDecompress(data);\n out.push({ id: raw & 0x7fffffff, data });\n p += 8 + size;\n }\n return out;\n}\nconst dvOf = (u: Uint8Array) => new DataView(u.buffer, u.byteOffset, u.byteLength);\n\ninterface VB { stride: number; count: number; data: DataView; pos: number; posType: number; nor: number; tc: number; tcType: number; tan: number; bin: number }\n\n/** Вершинные буферы: объявление до D3DDECL_END (поток 0xFF), число вершин, данные. */\nexport function parseVBs(u: Uint8Array): VB[] {\n const dv = dvOf(u), n = dv.getUint32(0, true), out: VB[] = [];\n const SIZE: Record<number, number> = { 0: 4, 1: 8, 2: 12, 3: 16, 4: 4, 5: 4, 6: 4, 7: 8, 8: 4, 9: 8, 10: 4, 11: 8, 12: 4, 13: 8, 15: 4, 16: 8 };\n let p = 4;\n for (let k = 0; k < n; k++) {\n let stride = 0, pos = -1, posType = 2, nor = -1, tc = -1, tcType = 1, tan = -1, bin = -1;\n for (;;) {\n const stream = dv.getUint16(p, true), off = dv.getUint16(p + 2, true), type = dv.getUint8(p + 4), usage = dv.getUint8(p + 6), ui = dv.getUint8(p + 7);\n p += 8;\n if (stream === 0xff) break;\n stride = Math.max(stride, off + (SIZE[type] ?? 4));\n if (usage === 0 && pos < 0) { pos = off; posType = type; }\n else if (usage === 3) nor = off;\n else if (usage === 6) tan = off;\n else if (usage === 7) bin = off;\n else if (usage === 5 && ui === 0) { tc = off; tcType = type; }\n }\n const count = dv.getUint32(p, true);\n p += 4;\n out.push({ stride, count, data: new DataView(u.buffer, u.byteOffset + p, stride * count), pos, posType, nor, tc, tcType, tan, bin });\n p += stride * count;\n }\n return out;\n}\n\nexport function parseIBs(u: Uint8Array): Uint16Array[] {\n const dv = dvOf(u), n = dv.getUint32(0, true), out: Uint16Array[] = [];\n let p = 4;\n for (let k = 0; k < n; k++) {\n const cnt = dv.getUint32(p, true);\n p += 4;\n const a = new Uint16Array(cnt);\n for (let i = 0; i < cnt; i++) a[i] = dv.getUint16(p + i * 2, true);\n out.push(a);\n p += cnt * 2;\n }\n return out;\n}\n\nexport interface LevelVisual { type: number; shader: number; vb: number; vbOff: number; vbCount: number; ib: number; ibOff: number; ibCount: number; xform: Float32Array | null }\n\n/** Окно индексов прогрессивного меша (SWI): смещение и число треугольников самого подробного уровня. */\ntype Slide = [offset: number, tris: number];\n/** Первое окно SWI: 4×u32 резерв, u32 число окон, окна {u32 смещение, u16 треугольников, u16 вершин}. */\nconst firstSlide = (d: DataView, p: number): Slide | null => (p + 28 <= d.byteLength && d.getUint32(p + 16, true) > 0 ? [d.getUint32(p + 20, true), d.getUint16(p + 24, true)] : null);\n\n/** Общие SWI уровня (level.geom, чанк 11; ЧН/ЗП): деревья TREE_PM ссылаются на них номером (чанк 20 визуала). */\nexport function parseSwis(u: Uint8Array): Array<Slide | null> {\n const d = dvOf(u), n = d.getUint32(0, true), out: Array<Slide | null> = [];\n let p = 4;\n for (let k = 0; k < n && p + 20 <= u.length; k++) { out.push(firstSlide(d, p)); p += 20 + d.getUint32(p + 16, true) * 8; }\n return out;\n}\n\nexport function parseVisuals(u: Uint8Array, swis: Array<Slide | null> = []): LevelVisual[] {\n const out: LevelVisual[] = [];\n for (const v of chunks(u)) {\n const sub = chunks(v.data), hdr = sub.find((c) => c.id === OGFL.HEADER);\n if (!hdr) continue;\n const h = dvOf(hdr.data), type = h.getUint8(1), shader = h.getUint16(2, true);\n const g = sub.find((c) => c.id === OGFL.GCONTAINER), vc = sub.find((c) => c.id === OGFL.VCONTAINER), ic = sub.find((c) => c.id === OGFL.ICONTAINER);\n let vb = -1, vbOff = 0, vbCount = 0, ib = -1, ibOff = 0, ibCount = 0;\n if (g) { const d = dvOf(g.data); [vb, vbOff, vbCount, ib, ibOff, ibCount] = [0, 4, 8, 12, 16, 20].map((o) => d.getUint32(o, true)) as [number, number, number, number, number, number]; }\n else if (vc && ic) { const a = dvOf(vc.data), b = dvOf(ic.data); [vb, vbOff, vbCount] = [a.getUint32(0, true), a.getUint32(4, true), a.getUint32(8, true)]; [ib, ibOff, ibCount] = [b.getUint32(0, true), b.getUint32(4, true), b.getUint32(8, true)]; }\n // прогрессивный меш: в буфере индексов лежат все уровни детализации; берём первое «окно» SWI — самое подробное\n const swi = sub.find((c) => c.id === OGFL.SWIDATA), swc = sub.find((c) => c.id === OGFL.SWICONTAINER);\n const slide = swi ? firstSlide(dvOf(swi.data), 0) : swc && swc.data.length >= 4 ? swis[dvOf(swc.data).getUint32(0, true)] ?? null : null;\n if (slide) { const [off, tris] = slide; if (off + tris * 3 <= ibCount) { ibOff += off; ibCount = tris * 3; } }\n let xform: Float32Array | null = null;\n const td = sub.find((c) => c.id === OGFL.TREEDEF2);\n if (td) { const d = dvOf(td.data); xform = new Float32Array(16); for (let k = 0; k < 16; k++) xform[k] = d.getFloat32(k * 4, true); }\n out.push({ type, shader, vb, vbOff, vbCount, ib, ibOff, ibCount, xform });\n }\n return out;\n}\n\n/** Строка шейдера уровня «шейдер/текстура,лайтмап…» -> [шейдер, текстура]. */\nexport function splitShader(s: string): [string, string] {\n const i = s.indexOf('/');\n return i < 0 ? [s, ''] : [s.slice(0, i), s.slice(i + 1).split(',')[0]!];\n}\n\n/** Уровень: визуалы с геометрией, объединённые по шейдеру. */\nexport function levelModel(level: Uint8Array, geom: Uint8Array, name: string): Model {\n const top = chunks(level), g = chunks(geom);\n const sh = top.find((c) => c.id === FSL.SHADERS), vis = top.find((c) => c.id === FSL.VISUALS), hd = top.find((c) => c.id === FSL.HEADER);\n const vbc = g.find((c) => c.id === FSL.VB), ibc = g.find((c) => c.id === FSL.IB);\n if (!sh || !vis || !vbc || !ibc) throw new Error('Не уровень X-Ray: нет шейдеров, визуалов или буферов level.geom');\n const shaders: string[] = [];\n { const d = dvOf(sh.data), n = d.getUint32(0, true); let p = 4; for (let k = 0; k < n; k++) { let s = ''; while (p < sh.data.length && sh.data[p]) s += String.fromCharCode(sh.data[p++]!); p++; shaders.push(s); } }\n const swc = g.find((c) => c.id === FSL.SWIS);\n const vbs = parseVBs(vbc.data), ibs = parseIBs(ibc.data), visuals = parseVisuals(vis.data, swc ? parseSwis(swc.data) : []);\n const groups = new Map<number, { pos: number[]; nor: number[]; uv: number[]; idx: number[] }>();\n let used = 0, skipped = 0;\n for (const v of visuals) {\n if (!GEOM_TYPES.has(v.type) || v.vb < 0) continue;\n const vb = vbs[v.vb], ib = ibs[v.ib];\n if (!vb || !ib || vb.pos < 0 || v.vbOff + v.vbCount > vb.count || v.ibOff + v.ibCount > ib.length) { skipped++; continue; }\n if (vb.posType !== 2 && vb.posType !== 3) { skipped++; continue; } // сжатые позиции (деревья ЗП) — позже\n let gr = groups.get(v.shader);\n if (!gr) groups.set(v.shader, (gr = { pos: [], nor: [], uv: [], idx: [] }));\n const base = gr.pos.length / 3, d = vb.data, X = v.xform;\n for (let i = 0; i < v.vbCount; i++) {\n const o = (v.vbOff + i) * vb.stride;\n let x = d.getFloat32(o + vb.pos, true), y = d.getFloat32(o + vb.pos + 4, true), z = d.getFloat32(o + vb.pos + 8, true);\n let nx = 0, ny = 1, nz = 0;\n if (vb.nor >= 0) { nx = (d.getUint8(o + vb.nor + 2) / 255) * 2 - 1; ny = (d.getUint8(o + vb.nor + 1) / 255) * 2 - 1; nz = (d.getUint8(o + vb.nor) / 255) * 2 - 1; }\n if (X) { // строки-векторы: v·M\n [x, y, z] = [x * X[0]! + y * X[4]! + z * X[8]! + X[12]!, x * X[1]! + y * X[5]! + z * X[9]! + X[13]!, x * X[2]! + y * X[6]! + z * X[10]! + X[14]!];\n [nx, ny, nz] = [nx * X[0]! + ny * X[4]! + nz * X[8]!, nx * X[1]! + ny * X[5]! + nz * X[9]!, nx * X[2]! + ny * X[6]! + nz * X[10]!];\n }\n const l = Math.hypot(nx, ny, nz) || 1;\n gr.pos.push(x, y, z); gr.nor.push(nx / l, ny / l, nz / l);\n let u = 0, w = 0;\n if (vb.tc >= 0) {\n if (vb.tcType === 1) { u = d.getFloat32(o + vb.tc, true); w = d.getFloat32(o + vb.tc + 4, true); }\n // SHORT4 — вершины деревьев и MU-моделей (машины, бочки, камни — визуалы TREE_ST/PM с матрицей): UV × 2048,\n // z, w — коэффициенты ветра (FTreeVisual); сверено по атласам техники во всех трёх играх (диапазон 0..2048)\n else if (vb.tcType === 7) { u = d.getInt16(o + vb.tc, true) / 2048; w = d.getInt16(o + vb.tc + 2, true) / 2048; }\n else { // SHORT2: координата × 1024, дробная поправка — в альфе касательной и бинормали\n const du = vb.tan >= 0 ? d.getUint8(o + vb.tan + 3) / 255 : 0, dv = vb.bin >= 0 ? d.getUint8(o + vb.bin + 3) / 255 : 0;\n u = (d.getInt16(o + vb.tc, true) + du) / 1024; w = (d.getInt16(o + vb.tc + 2, true) + dv) / 1024;\n }\n }\n gr.uv.push(u, w);\n }\n for (let i = 0; i < v.ibCount; i++) gr.idx.push(base + ib[v.ibOff + i]!);\n used++;\n }\n const meshes: Mesh[] = [...groups].map(([si, gr]) => {\n const [shader, texture] = splitShader(shaders[si] ?? ''), n = gr.pos.length / 3;\n return {\n name: `${texture || '?'} · ${shader}`, texture, shader, vertexCount: n, pos: Float32Array.from(gr.pos), nor: Float32Array.from(gr.nor), uv: Float32Array.from(gr.uv),\n bone: new Float32Array(n * 4), weight: new Float32Array(n * 4).map((_, i) => (i % 4 === 0 ? 1 : 0)), indices: Uint32Array.from(gr.idx), skinned: false,\n };\n }).filter((m) => m.indices.length);\n const ver = hd ? dvOf(hd.data).getUint16(0, true) : 0;\n const info = [`Уровень X-Ray (скомпилированный, версия ${ver}): ${name}`, `Визуалов ${visuals.length}, показано ${used}${skipped ? `, пропущено ${skipped}` : ''}; буферов VB ${vbs.length}, IB ${ibs.length}; шейдеров ${shaders.length}`, `Мешей (по шейдерам) ${meshes.length}, вершин ${meshes.reduce((a, m) => a + m.vertexCount, 0)}, треугольников ${meshes.reduce((a, m) => a + m.indices.length / 3, 0)}`];\n return { meshes, skeleton: null, motions: [], bounds: boundsOf(meshes), flipZ: true, info };\n}\n\nexport async function loadLevel(main: VFile, ctx: LoadContext): Promise<Model> {\n const geom = await ctx.sibling('level.geom');\n if (!geom) throw new Error('Рядом с level нет level.geom');\n const dir = (main.path ?? '').split('/').slice(-2, -1)[0] ?? '';\n const model = levelModel(new Uint8Array(await main.arrayBuffer()), new Uint8Array(geom), dir || main.name);\n // трава: level.details + высоты по level.cform\n const det = await ctx.sibling('level.details');\n if (det) {\n try {\n const { parseDetails, parseCForm, buildDetails } = await import('./details');\n const map = parseDetails(new Uint8Array(det)), cf = await ctx.sibling('level.cform');\n if (!cf) model.info.push('Трава: рядом нет level.cform — высоты не найти, трава не показана');\n else {\n const r = buildDetails(map, parseCForm(new Uint8Array(cf)));\n model.meshes.push(...r.meshes);\n model.bounds = boundsOf(model.meshes);\n const tris = r.meshes.reduce((a, m) => a + m.indices.length / 3, 0);\n model.info.push(`Трава (level.details): моделей ${map.models.length}, поставлено ${r.items} из ${r.candidates}${r.keep < 1 ? ` (прорежено до ${Math.round(r.keep * 100)} %)` : ''}, треугольников ${tris}`);\n }\n } catch (e) { model.info.push(`Трава: ${(e as Error).message}`); }\n }\n return model;\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"level.js","sources":["../src/level.ts"],"sourcesContent":["// A compiled X-Ray level (levels/<level>/level + level.geom): static geometry for viewing and comparison.\n// level: 1 header, 2 shaders (\"shader/texture,lightmap\"), 3 visuals (OGF-like chunks), 8 sectors.\n// level.geom: 9 vertex buffers (a D3DVERTEXELEMENT9 declaration + data), 10 index buffers.\n// A visual refers to buffers with a GCONTAINER chunk (CS/CoP, 21) or VCONTAINER/ICONTAINER (SoC, 7/8); indices are relative to its own VB range.\nimport { boundsOf } from '@gmvjs/core/geometry';\nimport { lzhufDecompress } from './lzhuf';\nimport type { LoadContext } from '@gmvjs/core/plugin';\nimport type { Mesh, Model, VFile } from '@gmvjs/core/types';\n\nconst FSL = { HEADER: 1, SHADERS: 2, VISUALS: 3, VB: 9, IB: 10, SWIS: 11 } as const;\nconst OGFL = { HEADER: 1, SWIDATA: 6, VCONTAINER: 7, ICONTAINER: 8, CHILDREN_L: 10, TREEDEF2: 12, SWICONTAINER: 20, GCONTAINER: 21 } as const;\n/** Visual types with their own geometry: regular, progressive, trees. */\nconst GEOM_TYPES = new Set([0, 2, 7, 11]);\n\ninterface Chunk { id: number; data: Uint8Array }\nfunction chunks(u8: Uint8Array): Chunk[] {\n const dv = new DataView(u8.buffer, u8.byteOffset, u8.byteLength), out: Chunk[] = [];\n let p = 0;\n while (p + 8 <= u8.length) {\n const raw = dv.getUint32(p, true), size = dv.getUint32(p + 4, true);\n if (p + 8 + size > u8.length) break;\n let data = u8.subarray(p + 8, p + 8 + size);\n if (raw & 0x80000000) data = lzhufDecompress(data);\n out.push({ id: raw & 0x7fffffff, data });\n p += 8 + size;\n }\n return out;\n}\nconst dvOf = (u: Uint8Array) => new DataView(u.buffer, u.byteOffset, u.byteLength);\n\ninterface VB { stride: number; count: number; data: DataView; pos: number; posType: number; nor: number; tc: number; tcType: number; tan: number; bin: number }\n\n/** Vertex buffers: the declaration up to D3DDECL_END (stream 0xFF), the vertex count, the data. */\nexport function parseVBs(u: Uint8Array): VB[] {\n const dv = dvOf(u), n = dv.getUint32(0, true), out: VB[] = [];\n const SIZE: Record<number, number> = { 0: 4, 1: 8, 2: 12, 3: 16, 4: 4, 5: 4, 6: 4, 7: 8, 8: 4, 9: 8, 10: 4, 11: 8, 12: 4, 13: 8, 15: 4, 16: 8 };\n let p = 4;\n for (let k = 0; k < n; k++) {\n let stride = 0, pos = -1, posType = 2, nor = -1, tc = -1, tcType = 1, tan = -1, bin = -1;\n for (;;) {\n const stream = dv.getUint16(p, true), off = dv.getUint16(p + 2, true), type = dv.getUint8(p + 4), usage = dv.getUint8(p + 6), ui = dv.getUint8(p + 7);\n p += 8;\n if (stream === 0xff) break;\n stride = Math.max(stride, off + (SIZE[type] ?? 4));\n if (usage === 0 && pos < 0) { pos = off; posType = type; }\n else if (usage === 3) nor = off;\n else if (usage === 6) tan = off;\n else if (usage === 7) bin = off;\n else if (usage === 5 && ui === 0) { tc = off; tcType = type; }\n }\n const count = dv.getUint32(p, true);\n p += 4;\n out.push({ stride, count, data: new DataView(u.buffer, u.byteOffset + p, stride * count), pos, posType, nor, tc, tcType, tan, bin });\n p += stride * count;\n }\n return out;\n}\n\nexport function parseIBs(u: Uint8Array): Uint16Array[] {\n const dv = dvOf(u), n = dv.getUint32(0, true), out: Uint16Array[] = [];\n let p = 4;\n for (let k = 0; k < n; k++) {\n const cnt = dv.getUint32(p, true);\n p += 4;\n const a = new Uint16Array(cnt);\n for (let i = 0; i < cnt; i++) a[i] = dv.getUint16(p + i * 2, true);\n out.push(a);\n p += cnt * 2;\n }\n return out;\n}\n\nexport interface LevelVisual { type: number; shader: number; vb: number; vbOff: number; vbCount: number; ib: number; ibOff: number; ibCount: number; xform: Float32Array | null }\n\n/** Index window of a progressive mesh (SWI): offset and triangle count of the most detailed level. */\ntype Slide = [offset: number, tris: number];\n/** The first SWI window: 4×u32 reserved, u32 window count, windows {u32 offset, u16 triangles, u16 vertices}. */\nconst firstSlide = (d: DataView, p: number): Slide | null => (p + 28 <= d.byteLength && d.getUint32(p + 16, true) > 0 ? [d.getUint32(p + 20, true), d.getUint16(p + 24, true)] : null);\n\n/** The level's shared SWIs (level.geom, chunk 11; CS/CoP): TREE_PM trees refer to them by number (visual chunk 20). */\nexport function parseSwis(u: Uint8Array): Array<Slide | null> {\n const d = dvOf(u), n = d.getUint32(0, true), out: Array<Slide | null> = [];\n let p = 4;\n for (let k = 0; k < n && p + 20 <= u.length; k++) { out.push(firstSlide(d, p)); p += 20 + d.getUint32(p + 16, true) * 8; }\n return out;\n}\n\nexport function parseVisuals(u: Uint8Array, swis: Array<Slide | null> = []): LevelVisual[] {\n const out: LevelVisual[] = [];\n for (const v of chunks(u)) {\n const sub = chunks(v.data), hdr = sub.find((c) => c.id === OGFL.HEADER);\n if (!hdr) continue;\n const h = dvOf(hdr.data), type = h.getUint8(1), shader = h.getUint16(2, true);\n const g = sub.find((c) => c.id === OGFL.GCONTAINER), vc = sub.find((c) => c.id === OGFL.VCONTAINER), ic = sub.find((c) => c.id === OGFL.ICONTAINER);\n let vb = -1, vbOff = 0, vbCount = 0, ib = -1, ibOff = 0, ibCount = 0;\n if (g) { const d = dvOf(g.data); [vb, vbOff, vbCount, ib, ibOff, ibCount] = [0, 4, 8, 12, 16, 20].map((o) => d.getUint32(o, true)) as [number, number, number, number, number, number]; }\n else if (vc && ic) { const a = dvOf(vc.data), b = dvOf(ic.data); [vb, vbOff, vbCount] = [a.getUint32(0, true), a.getUint32(4, true), a.getUint32(8, true)]; [ib, ibOff, ibCount] = [b.getUint32(0, true), b.getUint32(4, true), b.getUint32(8, true)]; }\n // progressive mesh: the index buffer holds every level of detail; take the first SWI \"window\", the most detailed one\n const swi = sub.find((c) => c.id === OGFL.SWIDATA), swc = sub.find((c) => c.id === OGFL.SWICONTAINER);\n const slide = swi ? firstSlide(dvOf(swi.data), 0) : swc && swc.data.length >= 4 ? swis[dvOf(swc.data).getUint32(0, true)] ?? null : null;\n if (slide) { const [off, tris] = slide; if (off + tris * 3 <= ibCount) { ibOff += off; ibCount = tris * 3; } }\n let xform: Float32Array | null = null;\n const td = sub.find((c) => c.id === OGFL.TREEDEF2);\n if (td) { const d = dvOf(td.data); xform = new Float32Array(16); for (let k = 0; k < 16; k++) xform[k] = d.getFloat32(k * 4, true); }\n out.push({ type, shader, vb, vbOff, vbCount, ib, ibOff, ibCount, xform });\n }\n return out;\n}\n\n/** Level shader string \"shader/texture,lightmap…\" -> [shader, texture]. */\nexport function splitShader(s: string): [string, string] {\n const i = s.indexOf('/');\n return i < 0 ? [s, ''] : [s.slice(0, i), s.slice(i + 1).split(',')[0]!];\n}\n\n/** Level: visuals with geometry, merged by shader. */\nexport function levelModel(level: Uint8Array, geom: Uint8Array, name: string): Model {\n const top = chunks(level), g = chunks(geom);\n const sh = top.find((c) => c.id === FSL.SHADERS), vis = top.find((c) => c.id === FSL.VISUALS), hd = top.find((c) => c.id === FSL.HEADER);\n const vbc = g.find((c) => c.id === FSL.VB), ibc = g.find((c) => c.id === FSL.IB);\n if (!sh || !vis || !vbc || !ibc) throw new Error('Не уровень X-Ray: нет шейдеров, визуалов или буферов level.geom');\n const shaders: string[] = [];\n { const d = dvOf(sh.data), n = d.getUint32(0, true); let p = 4; for (let k = 0; k < n; k++) { let s = ''; while (p < sh.data.length && sh.data[p]) s += String.fromCharCode(sh.data[p++]!); p++; shaders.push(s); } }\n const swc = g.find((c) => c.id === FSL.SWIS);\n const vbs = parseVBs(vbc.data), ibs = parseIBs(ibc.data), visuals = parseVisuals(vis.data, swc ? parseSwis(swc.data) : []);\n const groups = new Map<number, { pos: number[]; nor: number[]; uv: number[]; idx: number[] }>();\n let used = 0, skipped = 0;\n for (const v of visuals) {\n if (!GEOM_TYPES.has(v.type) || v.vb < 0) continue;\n const vb = vbs[v.vb], ib = ibs[v.ib];\n if (!vb || !ib || vb.pos < 0 || v.vbOff + v.vbCount > vb.count || v.ibOff + v.ibCount > ib.length) { skipped++; continue; }\n if (vb.posType !== 2 && vb.posType !== 3) { skipped++; continue; } // compressed positions (CoP trees): later\n let gr = groups.get(v.shader);\n if (!gr) groups.set(v.shader, (gr = { pos: [], nor: [], uv: [], idx: [] }));\n const base = gr.pos.length / 3, d = vb.data, X = v.xform;\n for (let i = 0; i < v.vbCount; i++) {\n const o = (v.vbOff + i) * vb.stride;\n let x = d.getFloat32(o + vb.pos, true), y = d.getFloat32(o + vb.pos + 4, true), z = d.getFloat32(o + vb.pos + 8, true);\n let nx = 0, ny = 1, nz = 0;\n if (vb.nor >= 0) { nx = (d.getUint8(o + vb.nor + 2) / 255) * 2 - 1; ny = (d.getUint8(o + vb.nor + 1) / 255) * 2 - 1; nz = (d.getUint8(o + vb.nor) / 255) * 2 - 1; }\n if (X) { // row vectors: v·M\n [x, y, z] = [x * X[0]! + y * X[4]! + z * X[8]! + X[12]!, x * X[1]! + y * X[5]! + z * X[9]! + X[13]!, x * X[2]! + y * X[6]! + z * X[10]! + X[14]!];\n [nx, ny, nz] = [nx * X[0]! + ny * X[4]! + nz * X[8]!, nx * X[1]! + ny * X[5]! + nz * X[9]!, nx * X[2]! + ny * X[6]! + nz * X[10]!];\n }\n const l = Math.hypot(nx, ny, nz) || 1;\n gr.pos.push(x, y, z); gr.nor.push(nx / l, ny / l, nz / l);\n let u = 0, w = 0;\n if (vb.tc >= 0) {\n if (vb.tcType === 1) { u = d.getFloat32(o + vb.tc, true); w = d.getFloat32(o + vb.tc + 4, true); }\n // SHORT4 is used by tree and MU-model vertices (vehicles, barrels, rocks: TREE_ST/PM visuals with a matrix): UV × 2048,\n // z, w are wind coefficients (FTreeVisual); checked against vehicle atlases in all three games (range 0..2048)\n else if (vb.tcType === 7) { u = d.getInt16(o + vb.tc, true) / 2048; w = d.getInt16(o + vb.tc + 2, true) / 2048; }\n else { // SHORT2: coordinate × 1024, the fractional correction is in the alpha of the tangent and binormal\n const du = vb.tan >= 0 ? d.getUint8(o + vb.tan + 3) / 255 : 0, dv = vb.bin >= 0 ? d.getUint8(o + vb.bin + 3) / 255 : 0;\n u = (d.getInt16(o + vb.tc, true) + du) / 1024; w = (d.getInt16(o + vb.tc + 2, true) + dv) / 1024;\n }\n }\n gr.uv.push(u, w);\n }\n for (let i = 0; i < v.ibCount; i++) gr.idx.push(base + ib[v.ibOff + i]!);\n used++;\n }\n const meshes: Mesh[] = [...groups].map(([si, gr]) => {\n const [shader, texture] = splitShader(shaders[si] ?? ''), n = gr.pos.length / 3;\n return {\n name: `${texture || '?'} · ${shader}`, texture, shader, vertexCount: n, pos: Float32Array.from(gr.pos), nor: Float32Array.from(gr.nor), uv: Float32Array.from(gr.uv),\n bone: new Float32Array(n * 4), weight: new Float32Array(n * 4).map((_, i) => (i % 4 === 0 ? 1 : 0)), indices: Uint32Array.from(gr.idx), skinned: false,\n };\n }).filter((m) => m.indices.length);\n const ver = hd ? dvOf(hd.data).getUint16(0, true) : 0;\n const info = [`Уровень X-Ray (скомпилированный, версия ${ver}): ${name}`, `Визуалов ${visuals.length}, показано ${used}${skipped ? `, пропущено ${skipped}` : ''}; буферов VB ${vbs.length}, IB ${ibs.length}; шейдеров ${shaders.length}`, `Мешей (по шейдерам) ${meshes.length}, вершин ${meshes.reduce((a, m) => a + m.vertexCount, 0)}, треугольников ${meshes.reduce((a, m) => a + m.indices.length / 3, 0)}`];\n return { meshes, skeleton: null, motions: [], bounds: boundsOf(meshes), flipZ: true, info };\n}\n\nexport async function loadLevel(main: VFile, ctx: LoadContext): Promise<Model> {\n const geom = await ctx.sibling('level.geom');\n if (!geom) throw new Error('Рядом с level нет level.geom');\n const dir = (main.path ?? '').split('/').slice(-2, -1)[0] ?? '';\n const model = levelModel(new Uint8Array(await main.arrayBuffer()), new Uint8Array(geom), dir || main.name);\n // grass: level.details + heights from level.cform\n const det = await ctx.sibling('level.details');\n if (det) {\n try {\n const { parseDetails, parseCForm, buildDetails } = await import('./details');\n const map = parseDetails(new Uint8Array(det)), cf = await ctx.sibling('level.cform');\n if (!cf) model.info.push('Трава: рядом нет level.cform — высоты не найти, трава не показана');\n else {\n const r = buildDetails(map, parseCForm(new Uint8Array(cf)));\n model.meshes.push(...r.meshes);\n model.bounds = boundsOf(model.meshes);\n const tris = r.meshes.reduce((a, m) => a + m.indices.length / 3, 0);\n model.info.push(`Трава (level.details): моделей ${map.models.length}, поставлено ${r.items} из ${r.candidates}${r.keep < 1 ? ` (прорежено до ${Math.round(r.keep * 100)} %)` : ''}, треугольников ${tris}`);\n }\n } catch (e) { model.info.push(`Трава: ${(e as Error).message}`); }\n }\n return model;\n}\n"],"names":[],"mappings":"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"}
|
package/dist/load.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LoadContext } from '@gmvjs/core/plugin';
|
|
2
2
|
import type { Model, VFile } from '@gmvjs/core/types';
|
|
3
3
|
export declare function loadOgf(main: VFile, ctx?: LoadContext): Promise<Model>;
|
|
4
|
-
/**
|
|
4
|
+
/** A source SDK object (.object): meshes by surface, textures from the surfaces. */
|
|
5
5
|
export declare function loadObject(main: VFile, ctx?: LoadContext): Promise<Model>;
|
package/dist/load.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"load.js","sources":["../src/load.ts"],"sourcesContent":["import { boundsOf, makeIndices } from '@gmvjs/core/geometry';\nimport type { LoadContext } from '@gmvjs/core/plugin';\nimport type { Mesh, Model, VFile } from '@gmvjs/core/types';\nimport type { XrayMotion } from './motion';\nimport { parseOGF, parseOMF, type OgfBone } from './ogf';\nimport { XraySkeleton } from './skeleton';\n\n/** Внешние анимации по расширению: .omf (по именам костей из S_SMPARAMS), .skl/.skls (ActorEditor). */\nasync function motionsFromFile(file: VFile, bones: OgfBone[], skel: XraySkeleton, parts?: Array<{ name: string; bones: string[] }>): Promise<XrayMotion[]> {\n const buf = await file.arrayBuffer(), ext = (file.name.split('.').pop() ?? '').toLowerCase();\n if (ext === 'skl' || ext === 'skls') {\n const { parseSkl, parseSMotions, smotionToXray } = await import('./smotion');\n const u = new Uint8Array(buf), list = ext === 'skl' ? [parseSkl(u)] : parseSMotions(u);\n return list.map((sm) => smotionToXray(sm, bones.map((b) => b.name), (r, p, o) => skel.local(r, p, o), parts));\n }\n return parseOMF(buf, bones);\n}\n\n/** Анимации из S_MOTION_REFS / ссылок объекта: meshes/<ссылка>.omf в открытых архивах и папках. */\nasync function referencedMotions(refs: readonly string[], bones: OgfBone[], ctx?: LoadContext): Promise<{ list: XrayMotion[]; missing: string[] }> {\n const list: XrayMotion[] = [], missing: string[] = [];\n if (!ctx || !bones.length) return { list, missing };\n for (const ref of refs) {\n const rel = ref.replace(/\\\\/g, '/').toLowerCase().replace(/\\.omf$/, '') + '.omf';\n let u: Uint8Array | null = null;\n for (const p of [`meshes/${rel}`, `gamedata/meshes/${rel}`, rel]) { u = await ctx.readPath(p).catch(() => null); if (u) break; }\n if (!u) { missing.push(ref); continue; }\n try {\n const src = rel.split('/').pop()!;\n for (const x of parseOMF(u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength) as ArrayBuffer, bones)) list.push({ ...x, source: src });\n } catch { missing.push(ref); }\n }\n return { list, missing };\n}\nconst refsInfo = (refs: readonly string[], n: number, missing: readonly string[]) =>\n `Внешние анимации (.omf): ${refs.join(', ')}${n ? ` — подключено ${n}` : ''}${missing.length ? ` (не найдены: ${missing.join(', ')} — откройте папку gamedata или архивы игры)` : ''}`;\n\nexport async function loadOgf(main: VFile, ctx?: LoadContext): Promise<Model> {\n const m = parseOGF(await main.arrayBuffer());\n const { list: ext, missing } = await referencedMotions(m.info.motionRefs, m.bones, ctx);\n m.motions.push(...ext);\n m.meshes.forEach((x, i) => { x.name = `#${i}`; });\n const meshes = m.meshes.map((x) => ({ ...x, name: x.name ?? '' }));\n const skel = XraySkeleton.auto(m.bones, meshes);\n const verts = meshes.reduce((a, x) => a + x.vertexCount, 0), i = m.info, info: string[] = [];\n info.push(`Тип: ${i.type}`, `Мешей: ${meshes.length}, вершин: ${verts}, треугольников: ${i.tris}`,\n `Костей: ${m.bones.length}${m.bones.length && !i.ikOk ? ' (IK-данные не разобраны — bind-поза неточна)' : ''}`);\n if (skel.bindVariant) info.push(`Углы костей: вариант ${skel.bindVariant}`);\n info.push(`Анимаций внутри: ${m.motions.length - ext.length}`);\n if (i.motionRefs.length) info.push(refsInfo(i.motionRefs, ext.length, missing));\n if (i.desc) info.push(`Источник: ${i.desc.source}`, `Автор: ${i.desc.owner ?? ''}`);\n return {\n meshes, skeleton: skel.n ? skel : null, motions: m.motions, bounds: m.bounds, flipZ: true, info,\n detectInvert: (motion) => (motion.kind === 'xray' ? skel.detectInvert(meshes, motion) : false),\n loadMotions: async (file) => motionsFromFile(file, m.bones, skel),\n };\n}\n\n/** Исходный объект SDK (.object): меши по поверхностям, текстуры из поверхностей. */\nexport async function loadObject(main: VFile, ctx?: LoadContext): Promise<Model> {\n const { parseObject, objectMeshes, objectSkeleton } = await import('./object');\n const o = parseObject(await main.arrayBuffer()), sk = objectSkeleton(o);\n const boneOfMap = sk ? new Map(sk.bones.map((b, i) => [b.wmap.toLowerCase(), i])) : undefined;\n if (sk) sk.bones.forEach((b, i) => { if (!boneOfMap!.has(b.name.toLowerCase())) boneOfMap!.set(b.name.toLowerCase(), i); });\n const list = objectMeshes(o, 'auto', boneOfMap);\n if (!list.length) throw new Error('В объекте нет граней');\n const meshes: Mesh[] = list.map((x) => {\n const n = x.pos.length / 3;\n return {\n name: x.name, texture: x.surface?.texture ?? '', shader: x.surface?.eshader ?? '', material: x.surface?.gamemtl, vertexCount: n,\n pos: x.pos, nor: x.nor, uv: x.uv, bone: x.bone ?? new Float32Array(n * 4), weight: x.weight ?? new Float32Array(n * 4).map((_, i) => (i % 4 === 0 ? 1 : 0)),\n indices: makeIndices(n, x.indices), skinned: !!x.bone,\n };\n });\n const verts = meshes.reduce((a, m) => a + m.vertexCount, 0), tris = meshes.reduce((a, m) => a + m.indices.length / 3, 0);\n const info = [`X-Ray SDK .object: мешей ${o.meshes.length}, поверхностей ${o.surfaces.length}, вершин ${verts}, треугольников ${tris}`];\n for (const s of o.surfaces.slice(0, 12)) info.push(` • ${s.name}: ${s.texture} | ${s.eshader} | ${s.cshader} | ${s.gamemtl}`);\n if (o.revision) info.push(`Автор: ${o.revision.owner}`);\n const model: Model = { meshes, skeleton: null, motions: [], bounds: boundsOf(meshes), flipZ: true, info };\n if (!sk) return model;\n // скелет: bind-поза из костей объекта (те же углы setXYZi, что в IK-данных OGF)\n const bones: OgfBone[] = sk.bones.map((b) => ({ name: b.name, parent: b.parent, rot: b.rot, pos: b.pos }));\n const skel = XraySkeleton.auto(bones, meshes);\n const parts = sk.parts.length ? sk.parts : undefined;\n if (sk.motions) {\n try {\n const { parseSMotions, smotionToXray } = await import('./smotion');\n for (const sm of parseSMotions(sk.motions)) model.motions.push(smotionToXray(sm, bones.map((b) => b.name), (r, p, x) => skel.local(r, p, x), parts));\n } catch (e) { info.push(`Анимации объекта не прочитаны: ${(e as Error).message}`); }\n }\n const own = model.motions.length;\n const { list: ext, missing } = await referencedMotions(sk.motionRefs, bones, ctx);\n model.motions.push(...ext);\n info.push(`Костей: ${bones.length}${skel.bindVariant ? ` (углы: вариант ${skel.bindVariant})` : ''}, частей тела: ${sk.parts.length}, анимаций внутри: ${own}`);\n if (sk.motionRefs.length) info.push(refsInfo(sk.motionRefs, ext.length, missing));\n return {\n ...model, skeleton: skel,\n detectInvert: (motion) => (motion.kind === 'xray' ? skel.detectInvert(meshes, motion) : false),\n loadMotions: async (file) => motionsFromFile(file, bones, skel, parts),\n };\n}\n"],"names":["i"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"load.js","sources":["../src/load.ts"],"sourcesContent":["import { boundsOf, makeIndices } from '@gmvjs/core/geometry';\nimport type { LoadContext } from '@gmvjs/core/plugin';\nimport type { Mesh, Model, VFile } from '@gmvjs/core/types';\nimport type { XrayMotion } from './motion';\nimport { parseOGF, parseOMF, type OgfBone } from './ogf';\nimport { XraySkeleton } from './skeleton';\n\n/** External animations by extension: .omf (by bone names from S_SMPARAMS), .skl/.skls (ActorEditor). */\nasync function motionsFromFile(file: VFile, bones: OgfBone[], skel: XraySkeleton, parts?: Array<{ name: string; bones: string[] }>): Promise<XrayMotion[]> {\n const buf = await file.arrayBuffer(), ext = (file.name.split('.').pop() ?? '').toLowerCase();\n if (ext === 'skl' || ext === 'skls') {\n const { parseSkl, parseSMotions, smotionToXray } = await import('./smotion');\n const u = new Uint8Array(buf), list = ext === 'skl' ? [parseSkl(u)] : parseSMotions(u);\n return list.map((sm) => smotionToXray(sm, bones.map((b) => b.name), (r, p, o) => skel.local(r, p, o), parts));\n }\n return parseOMF(buf, bones);\n}\n\n/** Animations from S_MOTION_REFS / object references: meshes/<reference>.omf in the open archives and folders. */\nasync function referencedMotions(refs: readonly string[], bones: OgfBone[], ctx?: LoadContext): Promise<{ list: XrayMotion[]; missing: string[] }> {\n const list: XrayMotion[] = [], missing: string[] = [];\n if (!ctx || !bones.length) return { list, missing };\n for (const ref of refs) {\n const rel = ref.replace(/\\\\/g, '/').toLowerCase().replace(/\\.omf$/, '') + '.omf';\n let u: Uint8Array | null = null;\n for (const p of [`meshes/${rel}`, `gamedata/meshes/${rel}`, rel]) { u = await ctx.readPath(p).catch(() => null); if (u) break; }\n if (!u) { missing.push(ref); continue; }\n try {\n const src = rel.split('/').pop()!;\n for (const x of parseOMF(u.buffer.slice(u.byteOffset, u.byteOffset + u.byteLength) as ArrayBuffer, bones)) list.push({ ...x, source: src });\n } catch { missing.push(ref); }\n }\n return { list, missing };\n}\nconst refsInfo = (refs: readonly string[], n: number, missing: readonly string[]) =>\n `Внешние анимации (.omf): ${refs.join(', ')}${n ? ` — подключено ${n}` : ''}${missing.length ? ` (не найдены: ${missing.join(', ')} — откройте папку gamedata или архивы игры)` : ''}`;\n\nexport async function loadOgf(main: VFile, ctx?: LoadContext): Promise<Model> {\n const m = parseOGF(await main.arrayBuffer());\n const { list: ext, missing } = await referencedMotions(m.info.motionRefs, m.bones, ctx);\n m.motions.push(...ext);\n m.meshes.forEach((x, i) => { x.name = `#${i}`; });\n const meshes = m.meshes.map((x) => ({ ...x, name: x.name ?? '' }));\n const skel = XraySkeleton.auto(m.bones, meshes);\n const verts = meshes.reduce((a, x) => a + x.vertexCount, 0), i = m.info, info: string[] = [];\n info.push(`Тип: ${i.type}`, `Мешей: ${meshes.length}, вершин: ${verts}, треугольников: ${i.tris}`,\n `Костей: ${m.bones.length}${m.bones.length && !i.ikOk ? ' (IK-данные не разобраны — bind-поза неточна)' : ''}`);\n if (skel.bindVariant) info.push(`Углы костей: вариант ${skel.bindVariant}`);\n info.push(`Анимаций внутри: ${m.motions.length - ext.length}`);\n if (i.motionRefs.length) info.push(refsInfo(i.motionRefs, ext.length, missing));\n if (i.desc) info.push(`Источник: ${i.desc.source}`, `Автор: ${i.desc.owner ?? ''}`);\n return {\n meshes, skeleton: skel.n ? skel : null, motions: m.motions, bounds: m.bounds, flipZ: true, info,\n detectInvert: (motion) => (motion.kind === 'xray' ? skel.detectInvert(meshes, motion) : false),\n loadMotions: async (file) => motionsFromFile(file, m.bones, skel),\n };\n}\n\n/** A source SDK object (.object): meshes by surface, textures from the surfaces. */\nexport async function loadObject(main: VFile, ctx?: LoadContext): Promise<Model> {\n const { parseObject, objectMeshes, objectSkeleton } = await import('./object');\n const o = parseObject(await main.arrayBuffer()), sk = objectSkeleton(o);\n const boneOfMap = sk ? new Map(sk.bones.map((b, i) => [b.wmap.toLowerCase(), i])) : undefined;\n if (sk) sk.bones.forEach((b, i) => { if (!boneOfMap!.has(b.name.toLowerCase())) boneOfMap!.set(b.name.toLowerCase(), i); });\n const list = objectMeshes(o, 'auto', boneOfMap);\n if (!list.length) throw new Error('В объекте нет граней');\n const meshes: Mesh[] = list.map((x) => {\n const n = x.pos.length / 3;\n return {\n name: x.name, texture: x.surface?.texture ?? '', shader: x.surface?.eshader ?? '', material: x.surface?.gamemtl, vertexCount: n,\n pos: x.pos, nor: x.nor, uv: x.uv, bone: x.bone ?? new Float32Array(n * 4), weight: x.weight ?? new Float32Array(n * 4).map((_, i) => (i % 4 === 0 ? 1 : 0)),\n indices: makeIndices(n, x.indices), skinned: !!x.bone,\n };\n });\n const verts = meshes.reduce((a, m) => a + m.vertexCount, 0), tris = meshes.reduce((a, m) => a + m.indices.length / 3, 0);\n const info = [`X-Ray SDK .object: мешей ${o.meshes.length}, поверхностей ${o.surfaces.length}, вершин ${verts}, треугольников ${tris}`];\n for (const s of o.surfaces.slice(0, 12)) info.push(` • ${s.name}: ${s.texture} | ${s.eshader} | ${s.cshader} | ${s.gamemtl}`);\n if (o.revision) info.push(`Автор: ${o.revision.owner}`);\n const model: Model = { meshes, skeleton: null, motions: [], bounds: boundsOf(meshes), flipZ: true, info };\n if (!sk) return model;\n // skeleton: the bind pose from the object's bones (the same setXYZi angles as in OGF IK data)\n const bones: OgfBone[] = sk.bones.map((b) => ({ name: b.name, parent: b.parent, rot: b.rot, pos: b.pos }));\n const skel = XraySkeleton.auto(bones, meshes);\n const parts = sk.parts.length ? sk.parts : undefined;\n if (sk.motions) {\n try {\n const { parseSMotions, smotionToXray } = await import('./smotion');\n for (const sm of parseSMotions(sk.motions)) model.motions.push(smotionToXray(sm, bones.map((b) => b.name), (r, p, x) => skel.local(r, p, x), parts));\n } catch (e) { info.push(`Анимации объекта не прочитаны: ${(e as Error).message}`); }\n }\n const own = model.motions.length;\n const { list: ext, missing } = await referencedMotions(sk.motionRefs, bones, ctx);\n model.motions.push(...ext);\n info.push(`Костей: ${bones.length}${skel.bindVariant ? ` (углы: вариант ${skel.bindVariant})` : ''}, частей тела: ${sk.parts.length}, анимаций внутри: ${own}`);\n if (sk.motionRefs.length) info.push(refsInfo(sk.motionRefs, ext.length, missing));\n return {\n ...model, skeleton: skel,\n detectInvert: (motion) => (motion.kind === 'xray' ? skel.detectInvert(meshes, motion) : false),\n loadMotions: async (file) => motionsFromFile(file, bones, skel, parts),\n };\n}\n"],"names":["i"],"mappings":"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"}
|
package/dist/lzhuf.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Decompress LZHUF. outSize: if the size is known in advance (otherwise taken from the first 4 bytes). */
|
|
2
2
|
export declare function lzhufDecompress(src: Uint8Array, outSize?: number): Uint8Array;
|
package/dist/lzhuf.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lzhuf.js","sources":["../src/lzhuf.ts"],"sourcesContent":["// LZHUF (LZSS +
|
|
1
|
+
{"version":3,"file":"lzhuf.js","sources":["../src/lzhuf.ts"],"sourcesContent":["// LZHUF (LZSS + adaptive Huffman, Okumura's 1988 scheme): N = 4096, F = 60, threshold 2.\n// In xrCore the tree is rebuilt at a root frequency of 0x4000 (not 0x8000 as in the original LZHUF.C); verified on Clear Sky archives.\n// X-Ray compresses chunks with the 0x80000000 flag this way (.db archive headers, parts of level files). Input: u32 size + a bit stream.\nconst N = 4096, F = 60, THRESHOLD = 2, N_CHAR = 256 - THRESHOLD + F, T = N_CHAR * 2 - 1, R = T - 1, MAX_FREQ = 0x4000;\n\n// decoding tables for the top 6 bits of the position: codes of 3..8 bits (1, 3, 8, 12, 24, 16 codes)\nconst D_CODE = new Uint8Array(256), D_LEN = new Uint8Array(256);\n{\n let i = 0, code = 0;\n [[3, 1], [4, 3], [5, 8], [6, 12], [7, 24], [8, 16]].forEach(([len, n]) => {\n for (let k = 0; k < n!; k++, code++) for (let r = 0; r < 1 << (8 - len!); r++) { D_CODE[i] = code; D_LEN[i] = len!; i++; }\n });\n}\n\n/** Decompress LZHUF. outSize: if the size is known in advance (otherwise taken from the first 4 bytes). */\nexport function lzhufDecompress(src: Uint8Array, outSize?: number): Uint8Array {\n let ip = 0;\n const size = outSize ?? (src[0]! | (src[1]! << 8) | (src[2]! << 16) | (src[3]! << 24)) >>> 0;\n if (outSize === undefined) ip = 4;\n // an encrypted or foreign stream gives an absurd size: more than 64× the compressed size is treated as a sign of encryption\n if (size > Math.max(1 << 16, src.length * 64)) throw new Error(`LZHUF: неправдоподобный размер ${size} (данные зашифрованы или повреждены)`);\n const out = new Uint8Array(size);\n const freq = new Uint32Array(T + 1), prnt = new Int32Array(T + N_CHAR), son = new Int32Array(T);\n for (let i = 0; i < N_CHAR; i++) { freq[i] = 1; son[i] = i + T; prnt[i + T] = i; }\n for (let i = 0, j = N_CHAR; j <= R; i += 2, j++) { freq[j] = freq[i]! + freq[i + 1]!; son[j] = i; prnt[i] = prnt[i + 1] = j; }\n freq[T] = 0xffff; prnt[R] = 0;\n\n let getbuf = 0, getlen = 0;\n const fill = () => { while (getlen <= 8) { getbuf = (getbuf | ((ip < src.length ? src[ip++]! : 0) << (8 - getlen))) & 0xffff; getlen += 8; } };\n const bit = () => { fill(); const i = getbuf; getbuf = (getbuf << 1) & 0xffff; getlen--; return (i >> 15) & 1; };\n const byte = () => { fill(); const i = getbuf; getbuf = (getbuf << 8) & 0xffff; getlen -= 8; return (i >> 8) & 0xff; };\n\n const reconst = () => {\n let j = 0;\n for (let i = 0; i < T; i++) if (son[i]! >= T) { freq[j] = (freq[i]! + 1) >> 1; son[j] = son[i]!; j++; }\n for (let i = 0, jj = N_CHAR; jj < T; i += 2, jj++) {\n const f = (freq[jj] = freq[i]! + freq[i + 1]!);\n let k = jj - 1;\n while (f < freq[k]!) k--;\n k++;\n freq.copyWithin(k + 1, k, jj); freq[k] = f;\n son.copyWithin(k + 1, k, jj); son[k] = i;\n }\n for (let i = 0; i < T; i++) { const k = son[i]!; if (k >= T) prnt[k] = i; else prnt[k] = prnt[k + 1] = i; }\n };\n const update = (ch: number) => {\n if (freq[R] === MAX_FREQ) reconst();\n let c = prnt[ch + T]!;\n do {\n const k = ++freq[c]!;\n let l = c + 1;\n if (k > freq[l]!) {\n while (k > freq[++l]!);\n l--;\n freq[c] = freq[l]!; freq[l] = k;\n const i = son[c]!;\n prnt[i] = l; if (i < T) prnt[i + 1] = l;\n const j = son[l]!;\n son[l] = i;\n prnt[j] = c; if (j < T) prnt[j + 1] = c;\n son[c] = j;\n c = l;\n }\n } while ((c = prnt[c]!) !== 0);\n };\n const decodeChar = () => { let c = son[R]!; while (c < T) c = son[c + bit()]!; c -= T; update(c); return c; };\n const decodePosition = () => {\n let i = byte();\n const c = D_CODE[i]! << 6;\n let j = D_LEN[i]! - 2;\n while (j--) i = (i << 1) + bit();\n return c | (i & 0x3f);\n };\n\n const text = new Uint8Array(N).fill(0x20);\n let r = N - F, o = 0;\n while (o < size) {\n const c = decodeChar();\n if (c < 256) { out[o++] = c; text[r] = c; r = (r + 1) & (N - 1); }\n else {\n const i = (r - decodePosition() - 1) & (N - 1), len = c - 255 + THRESHOLD;\n for (let k = 0; k < len && o < size; k++) { const b = text[(i + k) & (N - 1)]!; out[o++] = b; text[r] = b; r = (r + 1) & (N - 1); }\n }\n }\n return out;\n}\n"],"names":[],"mappings":"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"}
|
package/dist/motion.d.ts
CHANGED
|
@@ -7,11 +7,11 @@ export interface XrayTrack {
|
|
|
7
7
|
export interface XrayMotion extends MotionBase {
|
|
8
8
|
kind: 'xray';
|
|
9
9
|
frames: number;
|
|
10
|
-
/**
|
|
10
|
+
/** Tracks by model bone number; a gap means the bone is not animated (it stays in the bind pose). */
|
|
11
11
|
tracks: XrayTrack[];
|
|
12
|
-
/**
|
|
12
|
+
/** Body part (legs, torso, head) or "effect", if the animation does not drive the whole skeleton. */
|
|
13
13
|
part?: string;
|
|
14
|
-
/**
|
|
14
|
+
/** Animations of other body parts playing at the same time (legs + torso): bones without their own track take it from them. */
|
|
15
15
|
layers?: XrayMotion[];
|
|
16
16
|
}
|
|
17
17
|
declare module '@gmvjs/core/types' {
|
|
@@ -19,10 +19,10 @@ declare module '@gmvjs/core/types' {
|
|
|
19
19
|
xray: XrayMotion;
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
-
/**
|
|
22
|
+
/** The "effect" part: an animation of one bone on top of the rest (esmFX). */
|
|
23
23
|
export declare const XRAY_FX_PART = "\u044D\u0444\u0444\u0435\u043A\u0442";
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
25
|
+
* Layers for an animation of one body part (as in the game, legs and torso play different animations): for every other part,
|
|
26
|
+
* its animation sharing the most words in the name ("norm_torso_2_walk_0" → "norm_walk_fwd_0"), on a tie "idle"/"stand".
|
|
27
27
|
*/
|
|
28
28
|
export declare function autoLayers(main: XrayMotion, list: readonly Motion[]): XrayMotion[];
|
package/dist/motion.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"motion.js","sources":["../src/motion.ts"],"sourcesContent":["//
|
|
1
|
+
{"version":3,"file":"motion.js","sources":["../src/motion.ts"],"sourcesContent":["// X-Ray animations (OGF/OMF/SDK): quantized per-frame quaternions and positions, body parts (S_SMPARAMS).\nimport type { Motion, MotionBase } from '@gmvjs/core/types';\n\nexport interface XrayTrack {\n rot: Int16Array;\n trans: Float32Array;\n constT?: boolean;\n}\nexport interface XrayMotion extends MotionBase {\n kind: 'xray';\n frames: number;\n /** Tracks by model bone number; a gap means the bone is not animated (it stays in the bind pose). */\n tracks: XrayTrack[];\n /** Body part (legs, torso, head) or \"effect\", if the animation does not drive the whole skeleton. */\n part?: string;\n /** Animations of other body parts playing at the same time (legs + torso): bones without their own track take it from them. */\n layers?: XrayMotion[];\n}\n\ndeclare module '@gmvjs/core/types' {\n interface MotionKinds { xray: XrayMotion }\n}\n\n/** The \"effect\" part: an animation of one bone on top of the rest (esmFX). */\nexport const XRAY_FX_PART = 'эффект';\n\n/**\n * Layers for an animation of one body part (as in the game, legs and torso play different animations): for every other part,\n * its animation sharing the most words in the name (\"norm_torso_2_walk_0\" → \"norm_walk_fwd_0\"), on a tie \"idle\"/\"stand\".\n */\nexport function autoLayers(main: XrayMotion, list: readonly Motion[]): XrayMotion[] {\n if (!main.part || main.part === XRAY_FX_PART) return [];\n const words = (n: string) => n.toLowerCase().split(/[_\\s]+/).filter((w) => w && !/^\\d+$/.test(w) && !['norm', 'torso', 'legs', 'head'].includes(w));\n const mw = new Set(words(main.name));\n const best = new Map<string, [XrayMotion, number]>();\n for (const x of list) {\n if (x.kind !== 'xray' || !x.part || x.part === main.part || x.part === XRAY_FX_PART || x.load) continue;\n const w = words(x.name);\n const score = w.filter((k) => mw.has(k)).length * 10 + (w.some((k) => k === 'idle' || k === 'stand') ? 2 : 0) - w.length * 0.1 - (x.name.endsWith('_') ? 1 : 0);\n const cur = best.get(x.part);\n if (!cur || score > cur[1]) best.set(x.part, [x, score]);\n }\n return [...best.values()].map(([x]) => x);\n}\n"],"names":[],"mappings":"AAwBO,MAAM,eAAe;AAMrB,SAAS,WAAW,MAAkB,MAAuC;AAClF,MAAI,CAAC,KAAK,QAAQ,KAAK,SAAS,qBAAqB,CAAA;AACrD,QAAM,QAAQ,CAAC,MAAc,EAAE,YAAA,EAAc,MAAM,QAAQ,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC,QAAQ,KAAK,CAAC,KAAK,CAAC,CAAC,QAAQ,SAAS,QAAQ,MAAM,EAAE,SAAS,CAAC,CAAC;AAClJ,QAAM,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,CAAC;AACnC,QAAM,2BAAW,IAAA;AACjB,aAAW,KAAK,MAAM;AACpB,QAAI,EAAE,SAAS,UAAU,CAAC,EAAE,QAAQ,EAAE,SAAS,KAAK,QAAQ,EAAE,SAAS,gBAAgB,EAAE,KAAM;AAC/F,UAAM,IAAI,MAAM,EAAE,IAAI;AACtB,UAAM,QAAQ,EAAE,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,EAAE,SAAS,MAAM,EAAE,KAAK,CAAC,MAAM,MAAM,UAAU,MAAM,OAAO,IAAI,IAAI,KAAK,EAAE,SAAS,OAAO,EAAE,KAAK,SAAS,GAAG,IAAI,IAAI;AAC7J,UAAM,MAAM,KAAK,IAAI,EAAE,IAAI;AAC3B,QAAI,CAAC,OAAO,QAAQ,IAAI,CAAC,EAAG,MAAK,IAAI,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;AAAA,EACzD;AACA,SAAO,CAAC,GAAG,KAAK,OAAA,CAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;AAC1C;"}
|
package/dist/object.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export declare const EMESH: {
|
|
|
29
29
|
readonly VMAPS2: 4114;
|
|
30
30
|
readonly SG: 4115;
|
|
31
31
|
};
|
|
32
|
-
/**
|
|
32
|
+
/** Object flags (EditObject): dynamic, progressive, LOD, HOM, multiple usage, sound occluder, HQ export. */
|
|
33
33
|
export declare const EOBJ_FLAG: {
|
|
34
34
|
readonly DYNAMIC: 1;
|
|
35
35
|
readonly PROGRESSIVE: 2;
|
|
@@ -54,10 +54,10 @@ export interface XrVmap {
|
|
|
54
54
|
dim: number;
|
|
55
55
|
poly: boolean;
|
|
56
56
|
type: number;
|
|
57
|
-
/** size × dim
|
|
57
|
+
/** size × dim values. */
|
|
58
58
|
data: Float32Array;
|
|
59
59
|
vertices: Uint32Array;
|
|
60
|
-
/**
|
|
60
|
+
/** For "poly" maps: the face number of every record. */
|
|
61
61
|
faces: Uint32Array | null;
|
|
62
62
|
}
|
|
63
63
|
export interface XrEditMesh {
|
|
@@ -65,12 +65,12 @@ export interface XrEditMesh {
|
|
|
65
65
|
flags: number;
|
|
66
66
|
options: [number, number];
|
|
67
67
|
points: Float32Array;
|
|
68
|
-
/**
|
|
68
|
+
/** 3 corners per face: [point, vmref]. */
|
|
69
69
|
faces: Uint32Array;
|
|
70
70
|
sg: Uint32Array | null;
|
|
71
|
-
/** vmref ->
|
|
71
|
+
/** vmref -> a list of [vmap number, index in the vmap]. */
|
|
72
72
|
vmrefs: Array<Array<[number, number]>>;
|
|
73
|
-
/**
|
|
73
|
+
/** Surface -> face numbers. */
|
|
74
74
|
sfaces: Array<{
|
|
75
75
|
surface: string;
|
|
76
76
|
faces: Uint32Array;
|
|
@@ -90,20 +90,20 @@ export interface XrObject {
|
|
|
90
90
|
modifier: string;
|
|
91
91
|
modified: number;
|
|
92
92
|
} | null;
|
|
93
|
-
/**
|
|
93
|
+
/** Top-level chunks the reader does not parse (bones, animations…): kept as is. */
|
|
94
94
|
extra: Array<{
|
|
95
95
|
id: number;
|
|
96
96
|
data: Uint8Array;
|
|
97
97
|
}>;
|
|
98
98
|
}
|
|
99
|
-
/**
|
|
99
|
+
/** Chunks [id u32, size u32] within a range. */
|
|
100
100
|
export declare function chunksOf(buf: ArrayBuffer, start: number, end: number): Array<{
|
|
101
101
|
id: number;
|
|
102
102
|
start: number;
|
|
103
103
|
end: number;
|
|
104
104
|
}>;
|
|
105
105
|
export declare function parseObject(buf: ArrayBuffer): XrObject;
|
|
106
|
-
/**
|
|
106
|
+
/** Display mesh: per face corner, a point + UV from vmref; normals by smoothing groups. */
|
|
107
107
|
export interface XrObjectMesh {
|
|
108
108
|
name: string;
|
|
109
109
|
surface: XrSurface | undefined;
|
|
@@ -115,7 +115,7 @@ export interface XrObjectMesh {
|
|
|
115
115
|
weight?: Float32Array;
|
|
116
116
|
}
|
|
117
117
|
export declare function objectMeshes(o: XrObject, smoothing?: 'auto' | 'cop' | 'soc', boneOfMap?: Map<string, number>): XrObjectMesh[];
|
|
118
|
-
/**
|
|
118
|
+
/** Object bone: name, parent (number), vertex weight map name, bind pose. */
|
|
119
119
|
export interface XrObjectBone {
|
|
120
120
|
name: string;
|
|
121
121
|
parent: number;
|
|
@@ -132,7 +132,7 @@ export interface XrObjectSkeleton {
|
|
|
132
132
|
motions: Uint8Array | null;
|
|
133
133
|
motionRefs: string[];
|
|
134
134
|
}
|
|
135
|
-
/**
|
|
135
|
+
/** Bones (0x0921), body parts (0x0923/0x0919), animations (0x0916, like .skls) and .omf references (0x0926) from the object chunks. */
|
|
136
136
|
export declare function objectSkeleton(o: XrObject): XrObjectSkeleton | null;
|
|
137
|
-
/**
|
|
137
|
+
/** Vertex weights of a face corner (vmref): up to 4 bones from weight maps (type-1 vmaps), normalized. */
|
|
138
138
|
export declare function cornerWeights(m: XrEditMesh, vmref: number, boneOfMap: Map<string, number>): [number[], number[]];
|