@gmvjs/dunia1 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.
@@ -0,0 +1,127 @@
1
+ const isRealTree = (u) => {
2
+ if (u.length < 160) return false;
3
+ const dv = new DataView(u.buffer, u.byteOffset, u.byteLength);
4
+ if (dv.getUint32(8, true) !== 136) return false;
5
+ const s = new TextDecoder("latin1").decode(u.subarray(24, 136));
6
+ return /realtrees?\\/i.test(s);
7
+ };
8
+ function parseRealTree(u) {
9
+ if (!isRealTree(u)) throw new Error("Не RealTree");
10
+ const dv = new DataView(u.buffer, u.byteOffset, u.byteLength), dl = dv.getUint32(20, true);
11
+ let e = u.indexOf(0, 24);
12
+ if (e < 0 || e > 136) e = 136;
13
+ const path = new TextDecoder("latin1").decode(u.subarray(24, e));
14
+ const dataEnd = Math.min(u.length, 136 + dl);
15
+ let minPtr = Infinity;
16
+ const hdr = Array.from({ length: 12 }, (_, i) => dv.getUint32(280 + i * 4, true));
17
+ for (const i of [5, 6, 8, 9, 11]) if (hdr[i] > 65536) minPtr = Math.min(minPtr, hdr[i]);
18
+ for (let o = 136; o + 4 <= dataEnd; o += 4) {
19
+ const v = dv.getUint32(o, true);
20
+ if (v >= 16777216 && v < minPtr && (v & 4294901760) === (minPtr & 4294901760)) minPtr = v;
21
+ }
22
+ if (!Number.isFinite(minPtr)) throw new Error("RealTree: нет указателей");
23
+ const base = minPtr - 864, off = (p) => p - base + 136;
24
+ const [nNodes, pNodes, nCards, pCards] = [hdr[4], hdr[5], hdr[10], hdr[11]];
25
+ const branches = [];
26
+ let cur = null, height = 0;
27
+ for (let r = 0; r < nNodes; r++) {
28
+ const o = off(pNodes) + r * 76;
29
+ if (o < 136 || o + 76 > dataEnd) break;
30
+ const x = dv.getFloat32(o + 52, true), y = dv.getFloat32(o + 56, true), z = dv.getFloat32(o + 60, true), tip = dv.getUint32(o + 64, true), sc = dv.getFloat32(o + 68, true);
31
+ if (tip > 1e5 || ![x, y, z, sc].every(Number.isFinite) || Math.max(Math.abs(x), Math.abs(y), Math.abs(z)) > 1e3) break;
32
+ if (!cur || tip > cur.tips[cur.tips.length - 1]) {
33
+ cur = { pts: [], tips: [], scale: sc > 0 && sc < 10 ? sc : 1 };
34
+ branches.push(cur);
35
+ }
36
+ cur.pts.push([x, y, z]);
37
+ cur.tips.push(tip);
38
+ height = Math.max(height, z);
39
+ }
40
+ const cards = [];
41
+ for (let i = 0; i < nCards; i++) {
42
+ const o = off(pCards) + i * 92;
43
+ if (o < 136 || o + 92 > dataEnd) break;
44
+ const f = (k) => dv.getFloat32(o + k, true);
45
+ const n = [f(20), f(24), f(28)], a = [f(68), f(72), f(76)];
46
+ if (![...n, ...a].every(Number.isFinite) || Math.hypot(...a) > 50) continue;
47
+ if (Math.hypot(...a) > 1e-3) cards.push({ n, a });
48
+ }
49
+ const materials = [];
50
+ const tail = new TextDecoder("latin1").decode(u.subarray(dataEnd, Math.min(u.length, dataEnd + 4096)));
51
+ for (const m of tail.matchAll(/graphics\\[\w\\ .-]+?\.mlm/gi)) materials.push(m[0]);
52
+ return { path, branches: branches.filter((b) => b.pts.length >= 2), cards, materials, height };
53
+ }
54
+ const LEAF_SCALE = 0.7, TAPER = 1.1;
55
+ const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
56
+ const cross = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
57
+ const norm = (a) => {
58
+ const l = Math.hypot(a[0], a[1], a[2]) || 1;
59
+ return [a[0] / l, a[1] / l, a[2] / l];
60
+ };
61
+ function realTreeMeshes(t, bark, leaf, sides = 4) {
62
+ const R0 = Math.max(0.02, t.height * 0.025);
63
+ const P = [], N = [], U = [], I = [];
64
+ for (const b of t.branches) {
65
+ const t0 = b.tips[0] + 1, first = P.length / 3;
66
+ let len = 0;
67
+ b.pts.forEach((p, i) => {
68
+ const d = norm(sub(b.pts[Math.min(i + 1, b.pts.length - 1)], b.pts[Math.max(i - 1, 0)]));
69
+ const up = Math.abs(d[2]) < 0.9 ? [0, 0, 1] : [1, 0, 0], s1 = norm(cross(d, up)), s2 = cross(d, s1);
70
+ const r = R0 * b.scale * Math.max(0.15, Math.pow((b.tips[i] + 1) / t0, TAPER));
71
+ if (i) len += Math.hypot(...sub(p, b.pts[i - 1]));
72
+ for (let k = 0; k <= sides; k++) {
73
+ const a = k / sides * Math.PI * 2, c = Math.cos(a), s = Math.sin(a);
74
+ const nx = s1[0] * c + s2[0] * s, ny = s1[1] * c + s2[1] * s, nz = s1[2] * c + s2[2] * s;
75
+ P.push(p[0] + nx * r, p[1] + ny * r, p[2] + nz * r);
76
+ N.push(nx, ny, nz);
77
+ U.push(k / sides, len);
78
+ }
79
+ });
80
+ for (let i = 0; i + 1 < b.pts.length; i++) for (let k = 0; k < sides; k++) {
81
+ const a = first + i * (sides + 1) + k, c = a + sides + 1;
82
+ I.push(a, c, a + 1, a + 1, c, c + 1);
83
+ }
84
+ }
85
+ const out = [];
86
+ const mk = (name, m, p, n, uv, idx, alpha) => ({
87
+ name,
88
+ texture: m.texture,
89
+ material: m.material,
90
+ normalMap: m.normal,
91
+ vertexCount: p.length / 3,
92
+ pos: Float32Array.from(p),
93
+ nor: Float32Array.from(n),
94
+ uv: Float32Array.from(uv),
95
+ bone: new Float32Array(0),
96
+ weight: new Float32Array(0),
97
+ indices: p.length / 3 > 65535 ? Uint32Array.from(idx) : Uint16Array.from(idx),
98
+ skinned: false,
99
+ ...alpha ? { alphaTest: true } : {}
100
+ });
101
+ if (I.length) out.push(mk("RealTree · ветви (приближённо)", bark, P, N, U, I, false));
102
+ if (t.cards.length) {
103
+ const LP = [], LN = [], LU = [], LI = [];
104
+ let k = 0;
105
+ t.branches.forEach((b, bi) => {
106
+ if (bi === 0 && t.branches.length > 1) return;
107
+ for (const at of [b.pts.length - 1, Math.floor(b.pts.length / 2)]) {
108
+ const c = b.pts[at], card = t.cards[k++ % t.cards.length], A = card.a.map((v) => v * LEAF_SCALE), n = norm(card.n), B = cross(n, A);
109
+ const v0 = LP.length / 3;
110
+ for (const [sa, sb, u, v] of [[-1, -1, 0, 1], [1, -1, 1, 1], [1, 1, 1, 0], [-1, 1, 0, 0]]) {
111
+ LP.push(c[0] + A[0] * sa + B[0] * sb, c[1] + A[1] * sa + B[1] * sb, c[2] + A[2] * sa + B[2] * sb);
112
+ LN.push(n[0], n[1], n[2]);
113
+ LU.push(u, v);
114
+ }
115
+ LI.push(v0, v0 + 1, v0 + 2, v0, v0 + 2, v0 + 3);
116
+ }
117
+ });
118
+ if (LI.length) out.push(mk("RealTree · листва (приближённо)", leaf, LP, LN, LU, LI, true));
119
+ }
120
+ return out;
121
+ }
122
+ export {
123
+ isRealTree,
124
+ parseRealTree,
125
+ realTreeMeshes
126
+ };
127
+ //# sourceMappingURL=realtree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtree.js","sources":["../src/realtree.ts"],"sourcesContent":["// Деревья и кусты RealTree (Far Cry 2): в архиве — по хешу пути, имени нет; внутри — путь «…\\Realtrees\\RT_*.rta».\n// Файл: u32 размер, u32 размер Havok-части, u32 136 (начало данных), 0, 0, u32 размер данных; с 24 — путь; с 136 — дамп\n// памяти редактора (указатели — адреса; база — наименьший указатель − 0x360), после данных — список материалов (.mlm,\n// читаются как .xbm: кора «RealtreeTrunk» и листва), затем — Havok (коллизия).\n// Заголовок в 0x118: u32, u32, f32, u32 уровней ветвей, u32 узлов, ptr узлов, ptr, u32 ветвей, ptr, ptr параметров,\n// u32 карточек листвы, ptr карточек.\n// Узел (76 байт; поля со смещением 52 от начала записи): позиция f32×3, u32 — узлов до конца ветви (у начала ветви\n// больше, чем у предыдущего узла — так ветви делятся), f32 масштаб уровня (ствол 1 или 2, ветви 0.5).\n// Карточка листвы (92 байта): f32 размер с 16, нормаль с 20, касательная с 32, ось полукарточки с 68 (её длина = размер).\n// Сетку дерева игра строит сама по параметрам ветвей — здесь приближение: ветви — трубки по узлам, листва — карточки\n// шаблонов у концов ветвей.\nimport type { Mesh } from '@gmvjs/core/types';\n\nexport interface RealTree { path: string; branches: Array<{ pts: number[][]; tips: number[]; scale: number }>; cards: Array<{ n: number[]; a: number[] }>; materials: string[]; height: number }\n\nexport const isRealTree = (u: Uint8Array): boolean => {\n if (u.length < 160) return false;\n const dv = new DataView(u.buffer, u.byteOffset, u.byteLength);\n if (dv.getUint32(8, true) !== 136) return false;\n const s = new TextDecoder('latin1').decode(u.subarray(24, 136));\n return /realtrees?\\\\/i.test(s);\n};\n\nexport function parseRealTree(u: Uint8Array): RealTree {\n if (!isRealTree(u)) throw new Error('Не RealTree');\n const dv = new DataView(u.buffer, u.byteOffset, u.byteLength), dl = dv.getUint32(20, true);\n let e = u.indexOf(0, 24); if (e < 0 || e > 136) e = 136;\n const path = new TextDecoder('latin1').decode(u.subarray(24, e));\n const dataEnd = Math.min(u.length, 136 + dl);\n // база указателей: наименьший указатель-адрес в данных − 0x360 (первая структура после заголовка)\n let minPtr = Infinity;\n const hdr = Array.from({ length: 12 }, (_, i) => dv.getUint32(0x118 + i * 4, true));\n for (const i of [5, 6, 8, 9, 11]) if (hdr[i]! > 0x10000) minPtr = Math.min(minPtr, hdr[i]!);\n for (let o = 136; o + 4 <= dataEnd; o += 4) { const v = dv.getUint32(o, true); if (v >= 0x01000000 && v < minPtr && (v & 0xffff0000) === (minPtr & 0xffff0000)) minPtr = v; }\n if (!Number.isFinite(minPtr)) throw new Error('RealTree: нет указателей');\n const base = minPtr - 0x360, off = (p: number) => p - base + 0x88;\n const [nNodes, pNodes, nCards, pCards] = [hdr[4]!, hdr[5]!, hdr[10]!, hdr[11]!];\n const branches: RealTree['branches'] = [];\n let cur: RealTree['branches'][number] | null = null, height = 0;\n for (let r = 0; r < nNodes; r++) {\n const o = off(pNodes) + r * 76;\n if (o < 136 || o + 76 > dataEnd) break;\n const x = dv.getFloat32(o + 52, true), y = dv.getFloat32(o + 56, true), z = dv.getFloat32(o + 60, true), tip = dv.getUint32(o + 64, true), sc = dv.getFloat32(o + 68, true);\n if (tip > 100000 || ![x, y, z, sc].every(Number.isFinite) || Math.max(Math.abs(x), Math.abs(y), Math.abs(z)) > 1000) break;\n if (!cur || tip > cur.tips[cur.tips.length - 1]!) { cur = { pts: [], tips: [], scale: sc > 0 && sc < 10 ? sc : 1 }; branches.push(cur); }\n cur.pts.push([x, y, z]); cur.tips.push(tip);\n height = Math.max(height, z);\n }\n const cards: RealTree['cards'] = [];\n for (let i = 0; i < nCards; i++) {\n const o = off(pCards) + i * 92;\n if (o < 136 || o + 92 > dataEnd) break;\n const f = (k: number) => dv.getFloat32(o + k, true);\n const n = [f(20), f(24), f(28)], a = [f(68), f(72), f(76)];\n if (![...n, ...a].every(Number.isFinite) || Math.hypot(...a) > 50) continue;\n if (Math.hypot(...a) > 1e-3) cards.push({ n, a });\n }\n // материалы: строки «…\\.mlm» после данных\n const materials: string[] = [];\n const tail = new TextDecoder('latin1').decode(u.subarray(dataEnd, Math.min(u.length, dataEnd + 4096)));\n for (const m of tail.matchAll(/graphics\\\\[\\w\\\\ .-]+?\\.mlm/gi)) materials.push(m[0]);\n return { path, branches: branches.filter((b) => b.pts.length >= 2), cards, materials, height };\n}\n\n/** Масштаб карточек листвы и показатель сужения ветвей (подобраны на глаз по кустам и фикусу). */\nconst LEAF_SCALE = 0.7, TAPER = 1.1;\n\nconst sub = (a: number[], b: number[]) => [a[0]! - b[0]!, a[1]! - b[1]!, a[2]! - b[2]!];\nconst cross = (a: number[], b: number[]) => [a[1]! * b[2]! - a[2]! * b[1]!, a[2]! * b[0]! - a[0]! * b[2]!, a[0]! * b[1]! - a[1]! * b[0]!];\nconst norm = (a: number[]) => { const l = Math.hypot(a[0]!, a[1]!, a[2]!) || 1; return [a[0]! / l, a[1]! / l, a[2]! / l]; };\n\n/** Приближённая сетка: ветви — трубки (sides граней), листва — карточки у концов ветвей; bark/leaf — пути материалов .xbm. */\nexport function realTreeMeshes(t: RealTree, bark: { material?: string; texture: string; normal?: string }, leaf: { material?: string; texture: string; normal?: string }, sides = 4): Mesh[] {\n const R0 = Math.max(0.02, t.height * 0.025);\n const P: number[] = [], N: number[] = [], U: number[] = [], I: number[] = [];\n for (const b of t.branches) {\n const t0 = b.tips[0]! + 1, first = P.length / 3;\n let len = 0;\n b.pts.forEach((p, i) => {\n const d = norm(sub(b.pts[Math.min(i + 1, b.pts.length - 1)]!, b.pts[Math.max(i - 1, 0)]!));\n const up = Math.abs(d[2]!) < 0.9 ? [0, 0, 1] : [1, 0, 0], s1 = norm(cross(d, up)), s2 = cross(d, s1);\n const r = R0 * b.scale * Math.max(0.15, Math.pow((b.tips[i]! + 1) / t0, TAPER));\n if (i) len += Math.hypot(...sub(p, b.pts[i - 1]!));\n for (let k = 0; k <= sides; k++) {\n const a = (k / sides) * Math.PI * 2, c = Math.cos(a), s = Math.sin(a);\n const nx = s1[0]! * c + s2[0]! * s, ny = s1[1]! * c + s2[1]! * s, nz = s1[2]! * c + s2[2]! * s;\n P.push(p[0]! + nx * r, p[1]! + ny * r, p[2]! + nz * r); N.push(nx, ny, nz); U.push(k / sides, len);\n }\n });\n for (let i = 0; i + 1 < b.pts.length; i++) for (let k = 0; k < sides; k++) {\n const a = first + i * (sides + 1) + k, c = a + sides + 1;\n I.push(a, c, a + 1, a + 1, c, c + 1);\n }\n }\n const out: Mesh[] = [];\n const mk = (name: string, m: { material?: string; texture: string; normal?: string }, p: number[], n: number[], uv: number[], idx: number[], alpha: boolean): Mesh => ({\n name, texture: m.texture, material: m.material, normalMap: m.normal, vertexCount: p.length / 3, pos: Float32Array.from(p), nor: Float32Array.from(n), uv: Float32Array.from(uv),\n bone: new Float32Array(0), weight: new Float32Array(0), indices: p.length / 3 > 65535 ? Uint32Array.from(idx) : Uint16Array.from(idx), skinned: false, ...(alpha ? { alphaTest: true } : {}),\n });\n if (I.length) out.push(mk('RealTree · ветви (приближённо)', bark, P, N, U, I, false));\n // листва: у конца каждой ветви (кроме ствола) и у её середины — карточка шаблона (по кругу)\n if (t.cards.length) {\n const LP: number[] = [], LN: number[] = [], LU: number[] = [], LI: number[] = [];\n let k = 0;\n t.branches.forEach((b, bi) => {\n if (bi === 0 && t.branches.length > 1) return;\n for (const at of [b.pts.length - 1, Math.floor(b.pts.length / 2)]) {\n // карточка сужена (шаблон — полуразмер облака листвы, у концов ветвей оно меньше)\n const c = b.pts[at]!, card = t.cards[k++ % t.cards.length]!, A = card.a.map((v) => v * LEAF_SCALE), n = norm(card.n), B = cross(n, A);\n const v0 = LP.length / 3;\n for (const [sa, sb, u, v] of [[-1, -1, 0, 1], [1, -1, 1, 1], [1, 1, 1, 0], [-1, 1, 0, 0]] as const) {\n LP.push(c[0]! + A[0]! * sa + B[0]! * sb, c[1]! + A[1]! * sa + B[1]! * sb, c[2]! + A[2]! * sa + B[2]! * sb); LN.push(n[0]!, n[1]!, n[2]!); LU.push(u, v);\n }\n LI.push(v0, v0 + 1, v0 + 2, v0, v0 + 2, v0 + 3);\n }\n });\n if (LI.length) out.push(mk('RealTree · листва (приближённо)', leaf, LP, LN, LU, LI, true));\n }\n return out;\n}\n"],"names":[],"mappings":"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"}
package/dist/rml.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export interface RmlNode {
2
+ name: string;
3
+ value: string;
4
+ attrs: Record<string, string>;
5
+ children: RmlNode[];
6
+ }
7
+ export declare function parseRml(u: Uint8Array): RmlNode;
8
+ export declare const rmlChild: (n: RmlNode | undefined, name: string) => RmlNode | undefined;
9
+ export declare const rmlAll: (n: RmlNode | undefined, name: string) => RmlNode[];
10
+ /** Элементы XML по порядку (двоичный XML Dunia или текстовый — как в worlds/tmpla): имя, атрибуты, глубина. */
11
+ export declare function xmlElements(u: Uint8Array): Array<{
12
+ name: string;
13
+ attrs: Record<string, string>;
14
+ depth: number;
15
+ }>;
package/dist/rml.js ADDED
@@ -0,0 +1,77 @@
1
+ function parseRml(u) {
2
+ if (u.length < 5 || u[0] !== 0) throw new Error("Не двоичный XML Dunia");
3
+ const dv = new DataView(u.buffer, u.byteOffset, u.byteLength);
4
+ let p = 2;
5
+ const pk = () => {
6
+ if (p >= u.length) throw new Error("Двоичный XML обрезан");
7
+ const b = u[p++];
8
+ if (b < 254) return b;
9
+ if (b === 254 || p + 4 > u.length) throw new Error("Двоичный XML: неверное число");
10
+ const v = dv.getUint32(p, true);
11
+ p += 4;
12
+ return v;
13
+ };
14
+ const stSize = pk();
15
+ pk();
16
+ pk();
17
+ const node = (depth) => {
18
+ if (depth > 256) throw new Error("Двоичный XML: слишком глубоко");
19
+ const n = pk(), v = pk(), ac = pk(), cc = pk();
20
+ const a = [];
21
+ for (let i = 0; i < ac; i++) {
22
+ pk();
23
+ a.push([pk(), pk()]);
24
+ }
25
+ const c = [];
26
+ for (let i = 0; i < cc; i++) c.push(node(depth + 1));
27
+ return { n, v, a, c };
28
+ };
29
+ const raw = node(0);
30
+ if (p + stSize > u.length) throw new Error("Двоичный XML: таблица строк обрезана");
31
+ const st = u.subarray(p, p + stSize), dec = new TextDecoder("utf-8"), cache = /* @__PURE__ */ new Map();
32
+ const s = (i) => {
33
+ let r = cache.get(i);
34
+ if (r === void 0) {
35
+ let e = st.indexOf(0, i);
36
+ if (e < 0) e = st.length;
37
+ r = dec.decode(st.subarray(i, e));
38
+ cache.set(i, r);
39
+ }
40
+ return r;
41
+ };
42
+ const conv = (r) => ({ name: s(r.n), value: s(r.v), attrs: Object.fromEntries(r.a.map(([k, v]) => [s(k), s(v)])), children: r.c.map(conv) });
43
+ return conv(raw);
44
+ }
45
+ const rmlChild = (n, name) => n?.children.find((c) => c.name === name);
46
+ const rmlAll = (n, name) => n?.children.filter((c) => c.name === name) ?? [];
47
+ function xmlElements(u) {
48
+ const out = [];
49
+ if (u[0] === 0) {
50
+ const walk = (n, d) => {
51
+ out.push({ name: n.name, attrs: n.attrs, depth: d });
52
+ for (const c of n.children) walk(c, d + 1);
53
+ };
54
+ walk(parseRml(u), 0);
55
+ return out;
56
+ }
57
+ const text = new TextDecoder("utf-8").decode(u);
58
+ let depth = 0;
59
+ for (const m of text.matchAll(/<(\/?)([A-Za-z_][\w.-]*)([^>]*?)(\/?)>/g)) {
60
+ if (m[1]) {
61
+ depth--;
62
+ continue;
63
+ }
64
+ const attrs = {};
65
+ for (const a of m[3].matchAll(/([\w.:-]+)\s*=\s*"([^"]*)"/g)) attrs[a[1]] = a[2].replace(/&quot;/g, '"').replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&amp;/g, "&");
66
+ out.push({ name: m[2], attrs, depth });
67
+ if (!m[4]) depth++;
68
+ }
69
+ return out;
70
+ }
71
+ export {
72
+ parseRml,
73
+ rmlAll,
74
+ rmlChild,
75
+ xmlElements
76
+ };
77
+ //# sourceMappingURL=rml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rml.js","sources":["../src/rml.ts"],"sourcesContent":["// Двоичный XML Dunia (XmlResourceFile: worlds/<мир>/generated/<мир>.game.xml и др.).\n// u8 0, u8 ?, затем упакованные u32 (u8 < 0xFE или 0xFF + u32): размер таблицы строк, число узлов, число атрибутов;\n// узел: имя, значение (смещения в таблице строк), число атрибутов, число детей; атрибут: 0, имя, значение; дети — подряд.\n// После дерева — таблица строк (UTF-8, через \\0); имена и значения — смещения в ней.\n\nexport interface RmlNode { name: string; value: string; attrs: Record<string, string>; children: RmlNode[] }\n\nexport function parseRml(u: Uint8Array): RmlNode {\n if (u.length < 5 || u[0] !== 0) throw new Error('Не двоичный XML Dunia');\n const dv = new DataView(u.buffer, u.byteOffset, u.byteLength);\n let p = 2;\n const pk = () => {\n if (p >= u.length) throw new Error('Двоичный XML обрезан');\n const b = u[p++]!;\n if (b < 0xfe) return b;\n if (b === 0xfe || p + 4 > u.length) throw new Error('Двоичный XML: неверное число');\n const v = dv.getUint32(p, true); p += 4; return v;\n };\n const stSize = pk(); pk(); pk();\n type Raw = { n: number; v: number; a: Array<[number, number]>; c: Raw[] };\n const node = (depth: number): Raw => {\n if (depth > 256) throw new Error('Двоичный XML: слишком глубоко');\n const n = pk(), v = pk(), ac = pk(), cc = pk();\n const a: Array<[number, number]> = [];\n for (let i = 0; i < ac; i++) { pk(); a.push([pk(), pk()]); }\n const c: Raw[] = [];\n for (let i = 0; i < cc; i++) c.push(node(depth + 1));\n return { n, v, a, c };\n };\n const raw = node(0);\n if (p + stSize > u.length) throw new Error('Двоичный XML: таблица строк обрезана');\n const st = u.subarray(p, p + stSize), dec = new TextDecoder('utf-8'), cache = new Map<number, string>();\n const s = (i: number) => {\n let r = cache.get(i);\n if (r === undefined) { let e = st.indexOf(0, i); if (e < 0) e = st.length; r = dec.decode(st.subarray(i, e)); cache.set(i, r); }\n return r;\n };\n const conv = (r: Raw): RmlNode => ({ name: s(r.n), value: s(r.v), attrs: Object.fromEntries(r.a.map(([k, v]) => [s(k), s(v)])), children: r.c.map(conv) });\n return conv(raw);\n}\n\nexport const rmlChild = (n: RmlNode | undefined, name: string): RmlNode | undefined => n?.children.find((c) => c.name === name);\nexport const rmlAll = (n: RmlNode | undefined, name: string): RmlNode[] => n?.children.filter((c) => c.name === name) ?? [];\n\n/** Элементы XML по порядку (двоичный XML Dunia или текстовый — как в worlds/tmpla): имя, атрибуты, глубина. */\nexport function xmlElements(u: Uint8Array): Array<{ name: string; attrs: Record<string, string>; depth: number }> {\n const out: Array<{ name: string; attrs: Record<string, string>; depth: number }> = [];\n if (u[0] === 0) {\n const walk = (n: RmlNode, d: number) => { out.push({ name: n.name, attrs: n.attrs, depth: d }); for (const c of n.children) walk(c, d + 1); };\n walk(parseRml(u), 0);\n return out;\n }\n const text = new TextDecoder('utf-8').decode(u);\n let depth = 0;\n for (const m of text.matchAll(/<(\\/?)([A-Za-z_][\\w.-]*)([^>]*?)(\\/?)>/g)) {\n if (m[1]) { depth--; continue; }\n const attrs: Record<string, string> = {};\n for (const a of m[3]!.matchAll(/([\\w.:-]+)\\s*=\\s*\"([^\"]*)\"/g)) attrs[a[1]!] = a[2]!.replace(/&quot;/g, '\"').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&');\n out.push({ name: m[2]!, attrs, depth });\n if (!m[4]) depth++;\n }\n return out;\n}\n"],"names":[],"mappings":"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"}
package/dist/xbg.d.ts ADDED
@@ -0,0 +1,115 @@
1
+ import type { LoadContext } from '@gmvjs/core/plugin';
2
+ import { type SkeletonNode } from '@gmvjs/core/skeleton';
3
+ import type { Archive, Mesh, Model, NodeMotion, VFile } from '@gmvjs/core/types';
4
+ interface Chunk {
5
+ tag: string;
6
+ at: number;
7
+ size: number;
8
+ data: number;
9
+ dsize: number;
10
+ kids: Chunk[];
11
+ }
12
+ export declare function readChunks(u: Uint8Array): Chunk[];
13
+ export interface XbgNode {
14
+ name: string;
15
+ parent: number;
16
+ rot: number[];
17
+ pos: number[];
18
+ scale: number[];
19
+ }
20
+ export interface XbgCluster {
21
+ mat: number;
22
+ tris: number;
23
+ palette: number[];
24
+ }
25
+ export interface XbgPrim {
26
+ buffer: number;
27
+ part: number;
28
+ cluster: number;
29
+ firstIndex: number;
30
+ lastVertex: number;
31
+ firstVertex: number;
32
+ }
33
+ /** Вершинный буфер LOD: формат, шаг, число вершин, смещение в общих данных вершин LOD. */
34
+ export interface XbgBuffer {
35
+ format: number;
36
+ stride: number;
37
+ vcount: number;
38
+ base: number;
39
+ }
40
+ export interface XbgLod {
41
+ dist: number;
42
+ buffers: XbgBuffer[];
43
+ prims: XbgPrim[];
44
+ vAt: number;
45
+ iAt: number;
46
+ icount: number;
47
+ }
48
+ export interface Xbg {
49
+ materials: string[];
50
+ nodes: XbgNode[];
51
+ parts: Array<{
52
+ name: string;
53
+ lod: number;
54
+ node: number;
55
+ }>;
56
+ clusters: XbgCluster[][];
57
+ lods: XbgLod[];
58
+ pcmp: [number, number];
59
+ ucmp: [number, number];
60
+ u: Uint8Array;
61
+ /** Кости анимации (O2BM): обратные bind-матрицы (column-major) по порядку костей. */
62
+ o2bm: Float64Array[];
63
+ }
64
+ export declare function parseXbg(u: Uint8Array): Xbg;
65
+ export interface XbmMaterial {
66
+ name: string;
67
+ shader: string;
68
+ textures: Record<string, string>;
69
+ }
70
+ export declare function parseXbm(u: Uint8Array): XbmMaterial;
71
+ /**
72
+ * «Киты» персонажей (merc_kit, мультиплеерные классы, female_civilian_kit): все сменные части лежат в одной модели.
73
+ * По умолчанию видна одна часть на слот (голова и глаза к ней, тело её расы, волосы, верх, низ, обувь; «без шапки»,
74
+ * «без очков», «без снаряжения», если такие есть), «DUMMY…» скрыты. Возвращает имена частей, которые скрыть.
75
+ */
76
+ export declare function kitHiddenParts(names: readonly string[]): Set<string>;
77
+ /**
78
+ * Кости анимации -> узлы. Кость i списка O2BM — узел, чья мировая поза совпадает с позой кости с точностью до общего
79
+ * преобразования T (у животных T = 1; у людей кости O2BM — от таза в начале координат, повернуты на 90° вокруг Z).
80
+ * T ищется перебором узла для кости 0. В списке нет «…Nub», корня и частей, поэтому номера не совпадают с NODE.
81
+ * Возвращает узлы (−1 — не найден) и T (кадр анимации -> мир модели).
82
+ */
83
+ export declare function o2bmNodes(x: Xbg): {
84
+ nodes: number[];
85
+ frame: Float64Array;
86
+ };
87
+ /**
88
+ * Иерархия как в скелете анимаций: вспомогательные кости (колени, локти) в модели висят на тазу, а в .skeleton — на
89
+ * бедре/плече; без переподвешивания они не следуют за конечностью. Bind-поза сохраняется. parentOf — имя родителя по имени кости.
90
+ */
91
+ export declare function reparentNodes(sn: SkeletonNode[], parentOf: ReadonlyMap<string, string>): SkeletonNode[];
92
+ /**
93
+ * Узлы скелета: позы узлов — в пространстве родителя. frame — кадр вершин скиновых мешей: у людей вершины записаны
94
+ * в кадре костей O2BM (таз в начале координат), а узлы — в кадре модели; offset = (мировая bind-поза узла)⁻¹ · frame.
95
+ */
96
+ export declare function xbgSkeletonNodes(nodes: XbgNode[], frame?: ArrayLike<number>): SkeletonNode[];
97
+ /**
98
+ * Скелет анимаций модели: «<имя модели>_ref.skeleton» рядом, затем вверх по папкам — «hips_ref.skeleton»,
99
+ * «pelvis_ref.skeleton», «_common\\pelvis_ref.skeleton»; для людей вне graphics\\characters (actors) — общий скелет людей.
100
+ */
101
+ export declare function skeletonCandidates(modelPath: string, human?: boolean): string[];
102
+ /** Меши ближнего LOD модели .xbg (с материалами .xbm из архива); matCache — общий кэш материалов (для уровней). */
103
+ export declare function xbgMeshes(u: Uint8Array, arc: Archive | null, matCache?: Map<string, Promise<XbmMaterial | null>>, lodIndex?: number, parsed?: Xbg): Promise<{
104
+ x: Xbg;
105
+ lod: XbgLod;
106
+ meshes: Mesh[];
107
+ skinned: boolean;
108
+ missing: string[];
109
+ }>;
110
+ /** Скиновые меши в bind-позе как статика (для уровней): вершины через матрицы скина узлов модели. */
111
+ export declare function bakeBindPose(x: Xbg, meshes: Mesh[]): Mesh[];
112
+ export declare function loadXbg(main: VFile, ctx: LoadContext): Promise<Model>;
113
+ /** Анимации для модели (данные — при выборе): все .mab в папке скелета и глубже. */
114
+ export declare function listMabMotions(model: Model, archive: Archive): NodeMotion[];
115
+ export {};