@gmvjs/dunia1 0.0.0-stage → 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.
@@ -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 trees and bushes (Far Cry 2): stored in the archive by path hash, no name; inside is the path \"…\\Realtrees\\RT_*.rta\".\n// File: u32 size, u32 Havok part size, u32 136 (data start), 0, 0, u32 data size; from 24 the path; from 136 a dump\n// of editor memory (pointers are addresses; the base is the smallest pointer − 0x360), after the data a list of materials (.mlm,\n// read as .xbm: \"RealtreeTrunk\" bark and foliage), then Havok (collision).\n// Header at 0x118: u32, u32, f32, u32 branch levels, u32 nodes, ptr nodes, ptr, u32 branches, ptr, ptr parameters,\n// u32 foliage cards, ptr cards.\n// Node (76 bytes; fields at offset 52 from the record start): position f32×3, u32 = nodes to the branch end (at a branch start\n// it is larger than at the previous node; that is how branches split), f32 level scale (trunk 1 or 2, branches 0.5).\n// Foliage card (92 bytes): f32 size at 16, normal at 20, tangent at 32, half-card axis at 68 (its length = size).\n// The game builds the tree mesh itself from branch parameters; here is an approximation: branches as tubes along the nodes, foliage as\n// template cards at the branch ends.\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 // pointer base: the smallest address pointer in the data − 0x360 (the first structure after the header)\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 // materials: \"…\\.mlm\" strings after the data\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/** Foliage card scale and branch taper exponent (eyeballed on bushes and a ficus). */\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/** Approximate mesh: branches as tubes (`sides` faces), foliage as cards at branch ends; bark/leaf are .xbm material paths. */\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 // foliage: a template card at the end of every branch (except the trunk) and at its middle (in a circle)\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 // the card is narrowed (the template is half the foliage cloud size, which is smaller at branch ends)\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":"AAeO,MAAM,aAAa,CAAC,MAA2B;AACpD,MAAI,EAAE,SAAS,IAAK,QAAO;AAC3B,QAAM,KAAK,IAAI,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU;AAC5D,MAAI,GAAG,UAAU,GAAG,IAAI,MAAM,IAAK,QAAO;AAC1C,QAAM,IAAI,IAAI,YAAY,QAAQ,EAAE,OAAO,EAAE,SAAS,IAAI,GAAG,CAAC;AAC9D,SAAO,gBAAgB,KAAK,CAAC;AAC/B;AAEO,SAAS,cAAc,GAAyB;AACrD,MAAI,CAAC,WAAW,CAAC,EAAG,OAAM,IAAI,MAAM,aAAa;AACjD,QAAM,KAAK,IAAI,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,UAAU,GAAG,KAAK,GAAG,UAAU,IAAI,IAAI;AACzF,MAAI,IAAI,EAAE,QAAQ,GAAG,EAAE;AAAG,MAAI,IAAI,KAAK,IAAI,IAAK,KAAI;AACpD,QAAM,OAAO,IAAI,YAAY,QAAQ,EAAE,OAAO,EAAE,SAAS,IAAI,CAAC,CAAC;AAC/D,QAAM,UAAU,KAAK,IAAI,EAAE,QAAQ,MAAM,EAAE;AAE3C,MAAI,SAAS;AACb,QAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,MAAM,CAAC,GAAG,MAAM,GAAG,UAAU,MAAQ,IAAI,GAAG,IAAI,CAAC;AAClF,aAAW,KAAK,CAAC,GAAG,GAAG,GAAG,GAAG,EAAE,EAAG,KAAI,IAAI,CAAC,IAAK,MAAS,UAAS,KAAK,IAAI,QAAQ,IAAI,CAAC,CAAE;AAC1F,WAAS,IAAI,KAAK,IAAI,KAAK,SAAS,KAAK,GAAG;AAAE,UAAM,IAAI,GAAG,UAAU,GAAG,IAAI;AAAG,QAAI,KAAK,YAAc,IAAI,WAAW,IAAI,iBAAiB,SAAS,YAAa,UAAS;AAAA,EAAG;AAC5K,MAAI,CAAC,OAAO,SAAS,MAAM,EAAG,OAAM,IAAI,MAAM,0BAA0B;AACxE,QAAM,OAAO,SAAS,KAAO,MAAM,CAAC,MAAc,IAAI,OAAO;AAC7D,QAAM,CAAC,QAAQ,QAAQ,QAAQ,MAAM,IAAI,CAAC,IAAI,CAAC,GAAI,IAAI,CAAC,GAAI,IAAI,EAAE,GAAI,IAAI,EAAE,CAAE;AAC9E,QAAM,WAAiC,CAAA;AACvC,MAAI,MAA2C,MAAM,SAAS;AAC9D,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,IAAI,IAAI,MAAM,IAAI,IAAI;AAC5B,QAAI,IAAI,OAAO,IAAI,KAAK,QAAS;AACjC,UAAM,IAAI,GAAG,WAAW,IAAI,IAAI,IAAI,GAAG,IAAI,GAAG,WAAW,IAAI,IAAI,IAAI,GAAG,IAAI,GAAG,WAAW,IAAI,IAAI,IAAI,GAAG,MAAM,GAAG,UAAU,IAAI,IAAI,IAAI,GAAG,KAAK,GAAG,WAAW,IAAI,IAAI,IAAI;AAC1K,QAAI,MAAM,OAAU,CAAC,CAAC,GAAG,GAAG,GAAG,EAAE,EAAE,MAAM,OAAO,QAAQ,KAAK,KAAK,IAAI,KAAK,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,KAAK,IAAI,CAAC,CAAC,IAAI,IAAM;AACrH,QAAI,CAAC,OAAO,MAAM,IAAI,KAAK,IAAI,KAAK,SAAS,CAAC,GAAI;AAAE,YAAM,EAAE,KAAK,IAAI,MAAM,CAAA,GAAI,OAAO,KAAK,KAAK,KAAK,KAAK,KAAK,EAAA;AAAK,eAAS,KAAK,GAAG;AAAA,IAAG;AACxI,QAAI,IAAI,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;AAAG,QAAI,KAAK,KAAK,GAAG;AAC1C,aAAS,KAAK,IAAI,QAAQ,CAAC;AAAA,EAC7B;AACA,QAAM,QAA2B,CAAA;AACjC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,UAAM,IAAI,IAAI,MAAM,IAAI,IAAI;AAC5B,QAAI,IAAI,OAAO,IAAI,KAAK,QAAS;AACjC,UAAM,IAAI,CAAC,MAAc,GAAG,WAAW,IAAI,GAAG,IAAI;AAClD,UAAM,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,GAAG,EAAE,EAAE,GAAG,EAAE,EAAE,CAAC;AACzD,QAAI,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE,MAAM,OAAO,QAAQ,KAAK,KAAK,MAAM,GAAG,CAAC,IAAI,GAAI;AACnE,QAAI,KAAK,MAAM,GAAG,CAAC,IAAI,KAAM,OAAM,KAAK,EAAE,GAAG,GAAG;AAAA,EAClD;AAEA,QAAM,YAAsB,CAAA;AAC5B,QAAM,OAAO,IAAI,YAAY,QAAQ,EAAE,OAAO,EAAE,SAAS,SAAS,KAAK,IAAI,EAAE,QAAQ,UAAU,IAAI,CAAC,CAAC;AACrG,aAAW,KAAK,KAAK,SAAS,8BAA8B,EAAG,WAAU,KAAK,EAAE,CAAC,CAAC;AAClF,SAAO,EAAE,MAAM,UAAU,SAAS,OAAO,CAAC,MAAM,EAAE,IAAI,UAAU,CAAC,GAAG,OAAO,WAAW,OAAA;AACxF;AAGA,MAAM,aAAa,KAAK,QAAQ;AAEhC,MAAM,MAAM,CAAC,GAAa,MAAgB,CAAC,EAAE,CAAC,IAAK,EAAE,CAAC,GAAI,EAAE,CAAC,IAAK,EAAE,CAAC,GAAI,EAAE,CAAC,IAAK,EAAE,CAAC,CAAE;AACtF,MAAM,QAAQ,CAAC,GAAa,MAAgB,CAAC,EAAE,CAAC,IAAK,EAAE,CAAC,IAAK,EAAE,CAAC,IAAK,EAAE,CAAC,GAAI,EAAE,CAAC,IAAK,EAAE,CAAC,IAAK,EAAE,CAAC,IAAK,EAAE,CAAC,GAAI,EAAE,CAAC,IAAK,EAAE,CAAC,IAAK,EAAE,CAAC,IAAK,EAAE,CAAC,CAAE;AACxI,MAAM,OAAO,CAAC,MAAgB;AAAE,QAAM,IAAI,KAAK,MAAM,EAAE,CAAC,GAAI,EAAE,CAAC,GAAI,EAAE,CAAC,CAAE,KAAK;AAAG,SAAO,CAAC,EAAE,CAAC,IAAK,GAAG,EAAE,CAAC,IAAK,GAAG,EAAE,CAAC,IAAK,CAAC;AAAG;AAGnH,SAAS,eAAe,GAAa,MAA+D,MAA+D,QAAQ,GAAW;AAC3L,QAAM,KAAK,KAAK,IAAI,MAAM,EAAE,SAAS,KAAK;AAC1C,QAAM,IAAc,CAAA,GAAI,IAAc,CAAA,GAAI,IAAc,CAAA,GAAI,IAAc,CAAA;AAC1E,aAAW,KAAK,EAAE,UAAU;AAC1B,UAAM,KAAK,EAAE,KAAK,CAAC,IAAK,GAAG,QAAQ,EAAE,SAAS;AAC9C,QAAI,MAAM;AACV,MAAE,IAAI,QAAQ,CAAC,GAAG,MAAM;AACtB,YAAM,IAAI,KAAK,IAAI,EAAE,IAAI,KAAK,IAAI,IAAI,GAAG,EAAE,IAAI,SAAS,CAAC,CAAC,GAAI,EAAE,IAAI,KAAK,IAAI,IAAI,GAAG,CAAC,CAAC,CAAE,CAAC;AACzF,YAAM,KAAK,KAAK,IAAI,EAAE,CAAC,CAAE,IAAI,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,GAAG,KAAK,KAAK,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,MAAM,GAAG,EAAE;AACnG,YAAM,IAAI,KAAK,EAAE,QAAQ,KAAK,IAAI,MAAM,KAAK,KAAK,EAAE,KAAK,CAAC,IAAK,KAAK,IAAI,KAAK,CAAC;AAC9E,UAAI,EAAG,QAAO,KAAK,MAAM,GAAG,IAAI,GAAG,EAAE,IAAI,IAAI,CAAC,CAAE,CAAC;AACjD,eAAS,IAAI,GAAG,KAAK,OAAO,KAAK;AAC/B,cAAM,IAAK,IAAI,QAAS,KAAK,KAAK,GAAG,IAAI,KAAK,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;AACpE,cAAM,KAAK,GAAG,CAAC,IAAK,IAAI,GAAG,CAAC,IAAK,GAAG,KAAK,GAAG,CAAC,IAAK,IAAI,GAAG,CAAC,IAAK,GAAG,KAAK,GAAG,CAAC,IAAK,IAAI,GAAG,CAAC,IAAK;AAC7F,UAAE,KAAK,EAAE,CAAC,IAAK,KAAK,GAAG,EAAE,CAAC,IAAK,KAAK,GAAG,EAAE,CAAC,IAAK,KAAK,CAAC;AAAG,UAAE,KAAK,IAAI,IAAI,EAAE;AAAG,UAAE,KAAK,IAAI,OAAO,GAAG;AAAA,MACnG;AAAA,IACF,CAAC;AACD,aAAS,IAAI,GAAG,IAAI,IAAI,EAAE,IAAI,QAAQ,IAAK,UAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AACzE,YAAM,IAAI,QAAQ,KAAK,QAAQ,KAAK,GAAG,IAAI,IAAI,QAAQ;AACvD,QAAE,KAAK,GAAG,GAAG,IAAI,GAAG,IAAI,GAAG,GAAG,IAAI,CAAC;AAAA,IACrC;AAAA,EACF;AACA,QAAM,MAAc,CAAA;AACpB,QAAM,KAAK,CAAC,MAAc,GAA4D,GAAa,GAAa,IAAc,KAAe,WAA0B;AAAA,IACrK;AAAA,IAAM,SAAS,EAAE;AAAA,IAAS,UAAU,EAAE;AAAA,IAAU,WAAW,EAAE;AAAA,IAAQ,aAAa,EAAE,SAAS;AAAA,IAAG,KAAK,aAAa,KAAK,CAAC;AAAA,IAAG,KAAK,aAAa,KAAK,CAAC;AAAA,IAAG,IAAI,aAAa,KAAK,EAAE;AAAA,IAC9K,MAAM,IAAI,aAAa,CAAC;AAAA,IAAG,QAAQ,IAAI,aAAa,CAAC;AAAA,IAAG,SAAS,EAAE,SAAS,IAAI,QAAQ,YAAY,KAAK,GAAG,IAAI,YAAY,KAAK,GAAG;AAAA,IAAG,SAAS;AAAA,IAAO,GAAI,QAAQ,EAAE,WAAW,SAAS,CAAA;AAAA,EAAC;AAE5L,MAAI,EAAE,OAAQ,KAAI,KAAK,GAAG,kCAAkC,MAAM,GAAG,GAAG,GAAG,GAAG,KAAK,CAAC;AAEpF,MAAI,EAAE,MAAM,QAAQ;AAClB,UAAM,KAAe,CAAA,GAAI,KAAe,CAAA,GAAI,KAAe,CAAA,GAAI,KAAe,CAAA;AAC9E,QAAI,IAAI;AACR,MAAE,SAAS,QAAQ,CAAC,GAAG,OAAO;AAC5B,UAAI,OAAO,KAAK,EAAE,SAAS,SAAS,EAAG;AACvC,iBAAW,MAAM,CAAC,EAAE,IAAI,SAAS,GAAG,KAAK,MAAM,EAAE,IAAI,SAAS,CAAC,CAAC,GAAG;AAEjE,cAAM,IAAI,EAAE,IAAI,EAAE,GAAI,OAAO,EAAE,MAAM,MAAM,EAAE,MAAM,MAAM,GAAI,IAAI,KAAK,EAAE,IAAI,CAAC,MAAM,IAAI,UAAU,GAAG,IAAI,KAAK,KAAK,CAAC,GAAG,IAAI,MAAM,GAAG,CAAC;AACpI,cAAM,KAAK,GAAG,SAAS;AACvB,mBAAW,CAAC,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,GAAY;AAClG,aAAG,KAAK,EAAE,CAAC,IAAK,EAAE,CAAC,IAAK,KAAK,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,EAAE,CAAC,IAAK,KAAK,EAAE,CAAC,IAAK,IAAI,EAAE,CAAC,IAAK,EAAE,CAAC,IAAK,KAAK,EAAE,CAAC,IAAK,EAAE;AAAG,aAAG,KAAK,EAAE,CAAC,GAAI,EAAE,CAAC,GAAI,EAAE,CAAC,CAAE;AAAG,aAAG,KAAK,GAAG,CAAC;AAAA,QACxJ;AACA,WAAG,KAAK,IAAI,KAAK,GAAG,KAAK,GAAG,IAAI,KAAK,GAAG,KAAK,CAAC;AAAA,MAChD;AAAA,IACF,CAAC;AACD,QAAI,GAAG,OAAQ,KAAI,KAAK,GAAG,mCAAmC,MAAM,IAAI,IAAI,IAAI,IAAI,IAAI,CAAC;AAAA,EAC3F;AACA,SAAO;AACT;"}
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 elements in order (Dunia binary XML or text, as in worlds/tmpla): name, attributes, depth. */
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":["// Dunia binary XML (XmlResourceFile: worlds/<world>/generated/<world>.game.xml and others).\n// u8 0, u8 ?, then packed u32 (u8 < 0xFE or 0xFF + u32): string table size, node count, attribute count;\n// node: name, value (string table offsets), attribute count, child count; attribute: 0, name, value; children follow in sequence.\n// After the tree comes the string table (UTF-8, \\0-separated); names and values are offsets into it.\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 elements in order (Dunia binary XML or text, as in worlds/tmpla): name, attributes, depth. */\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 vertex buffer: format, stride, vertex count, offset in the LOD's shared vertex data. */
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
+ /** Animation bones (O2BM): inverse bind matrices (column-major) in bone order. */
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
+ * Character "kits" (merc_kit, multiplayer classes, female_civilian_kit): all swappable parts live in one model.
73
+ * By default one part per slot is visible (a head with its eyes, a body of its race, hair, top, bottom, shoes; "no hat",
74
+ * "no glasses", "no gear" where available), "DUMMY…" are hidden. Returns the names of the parts to hide.
75
+ */
76
+ export declare function kitHiddenParts(names: readonly string[]): Set<string>;
77
+ /**
78
+ * Animation bones -> nodes. Bone i of the O2BM list is the node whose world pose matches the bone pose up to a common
79
+ * transform T (for animals T = 1; for humans O2BM bones are relative to the pelvis at the origin, rotated 90° about Z).
80
+ * T is found by trying nodes for bone 0. The list lacks "…Nub", the root and parts, so numbers differ from NODE.
81
+ * Returns the nodes (−1 = not found) and T (animation frame -> model world).
82
+ */
83
+ export declare function o2bmNodes(x: Xbg): {
84
+ nodes: number[];
85
+ frame: Float64Array;
86
+ };
87
+ /**
88
+ * Hierarchy as in the animation skeleton: helper bones (knees, elbows) hang from the pelvis in the model but from the
89
+ * thigh/upper arm in .skeleton; without re-parenting they do not follow the limb. The bind pose is preserved. parentOf: parent name by bone name.
90
+ */
91
+ export declare function reparentNodes(sn: SkeletonNode[], parentOf: ReadonlyMap<string, string>): SkeletonNode[];
92
+ /**
93
+ * Skeleton nodes: node poses are in parent space. frame: the vertex frame of skinned meshes; for humans vertices are written
94
+ * in the O2BM bone frame (pelvis at the origin) while nodes are in the model frame; offset = (node world bind pose)⁻¹ · frame.
95
+ */
96
+ export declare function xbgSkeletonNodes(nodes: XbgNode[], frame?: ArrayLike<number>): SkeletonNode[];
97
+ /**
98
+ * The model's animation skeleton: "<model name>_ref.skeleton" next to it, then up the folders "hips_ref.skeleton",
99
+ * "pelvis_ref.skeleton", "_common\\pelvis_ref.skeleton"; for humans outside graphics\\characters (actors), the shared human skeleton.
100
+ */
101
+ export declare function skeletonCandidates(modelPath: string, human?: boolean): string[];
102
+ /** Near-LOD meshes of an .xbg model (with .xbm materials from the archive); matCache is a shared material cache (for levels). */
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
+ /** Skinned meshes in the bind pose as static geometry (for levels): vertices via the model nodes' skin matrices. */
111
+ export declare function bakeBindPose(x: Xbg, meshes: Mesh[]): Mesh[];
112
+ export declare function loadXbg(main: VFile, ctx: LoadContext): Promise<Model>;
113
+ /** Animations for the model (data on selection): every .mab in the skeleton folder and below. */
114
+ export declare function listMabMotions(model: Model, archive: Archive): NodeMotion[];
115
+ export {};