@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.
package/dist/xbg.js ADDED
@@ -0,0 +1,524 @@
1
+ import { skinnedBounds, boundsOf, computeNormals } from "@gmvjs/core/geometry";
2
+ import { rigidInv, mulCol, compose } from "@gmvjs/core/math";
3
+ import { NodeSkeleton } from "@gmvjs/core/skeleton";
4
+ import { parseSkeleton, matQuat, buildMabMotion, parseMab } from "./mab.js";
5
+ function readChunks(u) {
6
+ const dv = new DataView(u.buffer, u.byteOffset, u.byteLength);
7
+ if (u.length < 32 || dv.getUint32(0, true) !== 1296388936) throw new Error("Не модель Dunia (нет «MESH»)");
8
+ const walk = (p, end, depth) => {
9
+ const out = [];
10
+ while (p + 20 <= end) {
11
+ const tag = String.fromCharCode(u[p + 3], u[p + 2], u[p + 1], u[p]), size = dv.getUint32(p + 8, true), dsize = dv.getUint32(p + 12, true), nk = dv.getUint32(p + 16, true);
12
+ if (size < 20 || p + size > end || dsize > size - 20 || depth > 8) throw new Error(`XBG: чанк ${tag} повреждён`);
13
+ const data = p + size - dsize;
14
+ out.push({ tag, at: p, size, data, dsize, kids: nk ? walk(p + 20, data, depth + 1) : [] });
15
+ p += size;
16
+ }
17
+ return out;
18
+ };
19
+ return walk(32, u.length, 0);
20
+ }
21
+ class R {
22
+ constructor(u, p) {
23
+ this.u = u;
24
+ this.p = p;
25
+ this.dv = new DataView(u.buffer, u.byteOffset, u.byteLength);
26
+ }
27
+ u;
28
+ p;
29
+ dv;
30
+ u8() {
31
+ return this.u[this.p++];
32
+ }
33
+ u16() {
34
+ const v = this.dv.getUint16(this.p, true);
35
+ this.p += 2;
36
+ return v;
37
+ }
38
+ u32() {
39
+ const v = this.dv.getUint32(this.p, true);
40
+ this.p += 4;
41
+ return v;
42
+ }
43
+ i32() {
44
+ const v = this.dv.getInt32(this.p, true);
45
+ this.p += 4;
46
+ return v;
47
+ }
48
+ f32() {
49
+ const v = this.dv.getFloat32(this.p, true);
50
+ this.p += 4;
51
+ return v;
52
+ }
53
+ str() {
54
+ const n = this.u32();
55
+ if (n > 4096 || this.p + n > this.u.length) throw new Error(`Dunia: строка длиной ${n} на ${this.p - 4}`);
56
+ let s = "";
57
+ for (let i = 0; i < n; i++) s += String.fromCharCode(this.u[this.p + i]);
58
+ this.p += n + 1;
59
+ return s;
60
+ }
61
+ align16() {
62
+ this.p = this.p + 15 & -16;
63
+ }
64
+ }
65
+ function parseXbg(u) {
66
+ const chunks = readChunks(u);
67
+ const find = (t, list = chunks) => {
68
+ for (const c of list) {
69
+ if (c.tag === t) return c;
70
+ const k = find(t, c.kids);
71
+ if (k) return k;
72
+ }
73
+ return void 0;
74
+ };
75
+ const x = { materials: [], nodes: [], parts: [], clusters: [], lods: [], pcmp: [0, 1 / 32768], ucmp: [0, 1 / 32768], u, o2bm: [] };
76
+ const rm = find("RMTL");
77
+ if (rm) {
78
+ const r = new R(u, rm.data), n = r.u32();
79
+ r.u32();
80
+ for (let i = 0; i < n; i++) x.materials.push(r.str());
81
+ }
82
+ const nd = find("NODE");
83
+ if (nd) {
84
+ const r = new R(u, nd.data), n = r.u32();
85
+ r.u32();
86
+ for (let i = 0; i < n; i++) {
87
+ r.i32();
88
+ r.i32();
89
+ const parent = r.i32();
90
+ const q = [r.f32(), r.f32(), r.f32(), r.f32()], pos = [r.f32(), r.f32(), r.f32()], scale = [r.f32(), r.f32(), r.f32()];
91
+ r.i32();
92
+ r.f32();
93
+ r.f32();
94
+ const name = r.str();
95
+ r.u32();
96
+ x.nodes.push({ name, parent, rot: [q[3], q[0], q[1], q[2]], pos, scale });
97
+ }
98
+ }
99
+ const sk = find("SKND"), sid = find("SKID");
100
+ const partNode = [];
101
+ if (sid) {
102
+ const r = new R(u, sid.data), n = r.u32();
103
+ for (let i = 0; i < n; i++) {
104
+ r.u32();
105
+ r.u16();
106
+ const nd2 = r.u16();
107
+ partNode.push(nd2 === 65535 ? -1 : nd2);
108
+ }
109
+ }
110
+ if (sk) {
111
+ const r = new R(u, sk.data), n = r.u32();
112
+ for (let i = 0; i < n; i++) {
113
+ r.p += 44;
114
+ const lod = r.u32();
115
+ r.u32();
116
+ x.parts.push({ name: r.str(), lod, node: partNode[i] ?? -1 });
117
+ }
118
+ const cl = sk.kids.find((k) => k.tag === "CLUS");
119
+ if (cl) {
120
+ const r2 = new R(u, cl.data);
121
+ for (let i = 0; i < n && r2.p < cl.data + cl.dsize; i++) {
122
+ const k = r2.u32(), list = [];
123
+ for (let j = 0; j < k; j++) {
124
+ const mat = r2.u16(), tris = r2.u16();
125
+ r2.p += 10;
126
+ const palette = [];
127
+ for (let b = 0; b < 48; b++) palette.push(r2.u16());
128
+ list.push({ mat, tris, palette });
129
+ }
130
+ x.clusters.push(list);
131
+ }
132
+ }
133
+ }
134
+ const ld = find("LODS");
135
+ if (!ld) throw new Error("XBG: нет геометрии (LODS)");
136
+ {
137
+ const r = new R(u, ld.data), n = r.u32();
138
+ for (let l = 0; l < n; l++) {
139
+ const dist = r.f32(), nb = r.u32(), buffers = [];
140
+ for (let b = 0; b < nb; b++) buffers.push({ format: r.u32(), stride: r.u32(), vcount: r.u32(), base: r.u32() });
141
+ const np = r.u32(), prims = [];
142
+ for (let i = 0; i < np; i++) {
143
+ const buffer = r.u32(), part = r.u32(), cluster = r.u32(), firstIndex = r.u32(), lastVertex = r.u32(), voff = r.u32();
144
+ r.u32();
145
+ const bf = buffers[buffer];
146
+ if (!bf) throw new Error("XBG: примитив ссылается на несуществующий буфер");
147
+ prims.push({ buffer, part, cluster, firstIndex, lastVertex, firstVertex: (voff - bf.base) / bf.stride });
148
+ }
149
+ const vb = r.u32();
150
+ r.align16();
151
+ const vAt = r.p;
152
+ r.p += vb;
153
+ const ic = r.u32();
154
+ r.align16();
155
+ const iAt = r.p;
156
+ r.p += ic * 2;
157
+ if (r.p > ld.data + ld.dsize || vb !== buffers.reduce((s2, b) => s2 + b.vcount * b.stride, 0)) throw new Error("XBG: буфер LOD повреждён");
158
+ x.lods.push({ dist, buffers, prims, vAt, iAt, icount: ic });
159
+ }
160
+ }
161
+ const ob = find("O2BM");
162
+ if (ob && ob.dsize >= 4) {
163
+ const dv2 = new DataView(u.buffer, u.byteOffset, u.byteLength), n = dv2.getUint32(ob.data, true);
164
+ if (4 + n * 64 <= ob.dsize) for (let i = 0; i < n; i++) x.o2bm.push(Float64Array.from({ length: 16 }, (_, k) => dv2.getFloat32(ob.data + 4 + i * 64 + k * 4, true)));
165
+ }
166
+ const cmp = (t) => {
167
+ const c = find(t);
168
+ if (!c || c.dsize < 8) return null;
169
+ const dv = new DataView(u.buffer, u.byteOffset + c.data, 8);
170
+ return [dv.getFloat32(0, true), dv.getFloat32(4, true)];
171
+ };
172
+ x.pcmp = cmp("PCMP") ?? x.pcmp;
173
+ x.ucmp = cmp("UCMP") ?? x.ucmp;
174
+ return x;
175
+ }
176
+ function parseXbm(u) {
177
+ const c = readChunks(u).find((k) => k.tag === "DMTL");
178
+ if (!c) throw new Error("XBM: нет чанка DMTL");
179
+ const r = new R(u, c.data + 5);
180
+ const name = r.str(), shader = r.str(), n = r.u32(), textures = {};
181
+ for (let i = 0; i < n && i < 64; i++) {
182
+ const path = r.str(), slot = r.str();
183
+ if (path) textures[slot] = path;
184
+ }
185
+ return { name, shader, textures };
186
+ }
187
+ function kitHiddenParts(names) {
188
+ const parts = [...new Set(names)], hidden = /* @__PURE__ */ new Set();
189
+ const heads = parts.filter((n) => /HEAD/.test(n) && !/EYES|ROOT/.test(n));
190
+ if (heads.length < 2 && !parts.some((n) => /DUMMY/.test(n))) return hidden;
191
+ const key = (n) => n.replace(/^P_[A-Z]+_/, "").replace(/_LOD\d+$/, "").replace(/_/g, "");
192
+ const head = heads.find((n) => /CAUCASIANHEAD01/.test(n)) ?? heads[0];
193
+ const race = head && /NUBIAN/.test(head) ? "NUBIAN" : "CAUCASIAN";
194
+ const choose = (list, prefer = []) => {
195
+ if (list.length < 2) return;
196
+ let keep = list[0];
197
+ for (const re of prefer) {
198
+ const f = list.find((n) => re.test(n));
199
+ if (f) {
200
+ keep = f;
201
+ break;
202
+ }
203
+ }
204
+ for (const n of list) if (n !== keep) hidden.add(n);
205
+ };
206
+ for (const n of parts) if (/DUMMY/.test(n)) hidden.add(n);
207
+ for (const n of heads) if (n !== head) hidden.add(n);
208
+ if (head) {
209
+ for (const n of parts.filter((x) => /EYES/.test(x))) if (key(n).replace(/^EYES/, "") !== key(head)) hidden.add(n);
210
+ }
211
+ const rest = parts.filter((n) => !hidden.has(n) && !heads.includes(n) && !/EYES/.test(n));
212
+ const slot = (re, not) => rest.filter((n) => re.test(n) && true);
213
+ const top = slot(/_UB_|DRESS/);
214
+ choose(top);
215
+ const topKept = top.find((n) => !hidden.has(n)) ?? "";
216
+ const armsStyle = /BEATER/.test(topKept) ? "BEATER" : /VEST/.test(topKept) ? "LONGVEST" : "SHIRT";
217
+ for (const re of [/_ARMS$/, /BCHEST/, /_LEGS$/]) {
218
+ const list = slot(re);
219
+ choose(list, re.source === "_ARMS$" ? [new RegExp(`${race}_${armsStyle}_ARMS`), new RegExp(race)] : [new RegExp(race)]);
220
+ }
221
+ choose(slot(/HAIR/), [race === "NUBIAN" ? /AFRO/ : /CAUC/]);
222
+ choose(slot(/HAT|CAP\d|BEANIE|BANDANA/), [/NOHAT/]);
223
+ choose(slot(/GLASS|GOGGLE/), [/NOGLASS/]);
224
+ choose(slot(/_LB_.*(PANT|JEANS|SKIRT)|LEGS_.*SKIRT/));
225
+ choose(slot(/BOOT/));
226
+ choose(slot(/GEAR|BACKPACK|STRAPS|LEATHER_VEST|TACTICAL_VEST|FLACK_JACKET|VESTSPORT/), [/NOGEAR/]);
227
+ for (const j of ["BRACELET", "NECKLACE"]) choose(slot(new RegExp(j)));
228
+ return hidden;
229
+ }
230
+ const LAYOUT = { 16: { normal: -1, skin: false }, 32: { normal: 16, skin: false }, 40: { normal: 24, skin: true } };
231
+ const ALPHA_SHADERS = /* @__PURE__ */ new Set(["Leaf", "BigLeaf", "Grass", "Hair", "Decal"]);
232
+ const slotOf = (t, ...keys) => {
233
+ for (const k of keys) if (t[k]) return t[k];
234
+ return void 0;
235
+ };
236
+ function o2bmNodes(x) {
237
+ const N = xbgSkeletonNodes(x.nodes).map((s) => rigidInv(s.offset));
238
+ const O = x.o2bm.map((m) => rigidInv(m));
239
+ const id = new Float64Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
240
+ if (!O.length) return { nodes: [], frame: id };
241
+ const dist = (a, b) => Math.hypot(a[12] - b[12], a[13] - b[13], a[14] - b[14]) + Math.hypot(...[0, 1, 2, 4, 5, 6, 8, 9, 10].map((k) => a[k] - b[k]));
242
+ const match = (T, limit) => {
243
+ const out = [];
244
+ for (const o of O) {
245
+ const w = mulCol(T, o);
246
+ let bj = -1, bd = 0.02;
247
+ N.forEach((n, j) => {
248
+ const d = dist(n, w);
249
+ if (d < bd) {
250
+ bd = d;
251
+ bj = j;
252
+ }
253
+ });
254
+ out.push(bj);
255
+ if (out.length >= limit && out.every((v) => v < 0)) break;
256
+ }
257
+ return out;
258
+ };
259
+ let best = { n: -1, nodes: [], frame: id };
260
+ const direct = match(id, O.length);
261
+ best = { n: direct.filter((j) => j >= 0).length, nodes: direct, frame: id };
262
+ if (best.n < O.length) {
263
+ N.forEach((n, c) => {
264
+ const T = mulCol(n, rigidInv(O[0]));
265
+ const probe = match(T, 8);
266
+ if (probe.length < O.length && probe.every((v) => v < 0)) return;
267
+ const nodes = probe.length === O.length ? probe : match(T, O.length), k = nodes.filter((j) => j >= 0).length;
268
+ if (k > best.n) best = { n: k, nodes, frame: T };
269
+ });
270
+ }
271
+ return { nodes: best.nodes, frame: best.frame };
272
+ }
273
+ function reparentNodes(sn, parentOf) {
274
+ const byName = new Map(sn.map((n, i) => [n.name.toLowerCase(), i]));
275
+ const world = [];
276
+ sn.forEach((n, i) => {
277
+ world[i] = n.parent >= 0 ? mulCol(world[n.parent], n.local) : Float64Array.from(n.local);
278
+ });
279
+ return sn.map((n, i) => {
280
+ const pn = parentOf.get(n.name.toLowerCase());
281
+ const p = pn === void 0 ? void 0 : byName.get(pn);
282
+ if (p === void 0 || p === n.parent || p >= i) return n;
283
+ return { ...n, parent: p, local: mulCol(rigidInv(world[p]), world[i]) };
284
+ });
285
+ }
286
+ function xbgSkeletonNodes(nodes, frame) {
287
+ const local = nodes.map((n) => {
288
+ const m = new Float64Array(16);
289
+ compose(n.pos, n.rot, n.scale, m);
290
+ return m;
291
+ });
292
+ const world = [];
293
+ nodes.forEach((n, i) => {
294
+ world[i] = n.parent >= 0 && n.parent < i ? mulCol(world[n.parent], local[i]) : local[i];
295
+ });
296
+ return nodes.map((n, i) => ({ name: n.name, parent: n.parent >= 0 && n.parent < i ? n.parent : -1, local: local[i], offset: frame ? mulCol(rigidInv(world[i]), frame) : rigidInv(world[i]) }));
297
+ }
298
+ function skeletonCandidates(modelPath, human = false) {
299
+ const parts = modelPath.replace(/\//g, "\\").toLowerCase().split("\\"), stem = parts[parts.length - 1].replace(/\.xbg$/, "");
300
+ const dirs = parts.slice(0, -1), out = [`${dirs.join("\\")}\\${stem}_ref.skeleton`];
301
+ for (let n = dirs.length; n >= 2; n--) {
302
+ const dir = dirs.slice(0, n).join("\\");
303
+ out.push(`${dir}\\hips_ref.skeleton`, `${dir}\\pelvis_ref.skeleton`, `${dir}\\_common\\pelvis_ref.skeleton`);
304
+ }
305
+ if (human) out.push("graphics\\characters\\_common\\pelvis_ref.skeleton");
306
+ return out;
307
+ }
308
+ async function xbgMeshes(u, arc, matCache, lodIndex = 0, parsed) {
309
+ const x = parsed ?? parseXbg(u), lod = x.lods[Math.min(lodIndex, x.lods.length - 1)];
310
+ if (!lod) throw new Error("XBG: нет LOD");
311
+ const dv = new DataView(u.buffer, u.byteOffset, u.byteLength);
312
+ for (const b of lod.buffers) if (!LAYOUT[b.stride]) throw new Error(`XBG: вершина ${b.stride} байт (формат ${b.format.toString(16)}) пока не поддерживается`);
313
+ const skinned = lod.buffers.some((b) => LAYOUT[b.stride].skin);
314
+ const readMat = async (p) => {
315
+ const e = arc?.lookup(p);
316
+ if (!arc || !e) return null;
317
+ try {
318
+ return parseXbm(await arc.read(e));
319
+ } catch {
320
+ return null;
321
+ }
322
+ };
323
+ const mats = await Promise.all(x.materials.map((p) => {
324
+ if (!matCache) return readMat(p);
325
+ const k = p.toLowerCase();
326
+ let r = matCache.get(k);
327
+ if (!r) {
328
+ r = readMat(p);
329
+ matCache.set(k, r);
330
+ }
331
+ return r;
332
+ }));
333
+ const [po, ps] = x.pcmp, [uo, us] = x.ucmp;
334
+ const meshes = [];
335
+ lod.prims.forEach((pr, k) => {
336
+ const next = lod.prims[k + 1], end = next ? next.firstIndex : lod.icount;
337
+ const cl = x.clusters[pr.part]?.[pr.cluster], v0 = pr.firstVertex, nv = pr.lastVertex - v0 + 1;
338
+ const bf = lod.buffers[pr.buffer], L = LAYOUT[bf.stride];
339
+ if (nv <= 0 || end <= pr.firstIndex) return;
340
+ const pos = new Float32Array(nv * 3), nor = new Float32Array(nv * 3), uv = new Float32Array(nv * 2);
341
+ const bone = new Float32Array(nv * 4), weight = new Float32Array(nv * 4);
342
+ for (let i = 0; i < nv; i++) {
343
+ const o = lod.vAt + bf.base + (v0 + i) * bf.stride;
344
+ for (let c = 0; c < 3; c++) pos[i * 3 + c] = dv.getInt16(o + c * 2, true) * ps + po;
345
+ uv[i * 2] = dv.getInt16(o + 8, true) * us + uo;
346
+ uv[i * 2 + 1] = dv.getInt16(o + 10, true) * us + uo;
347
+ if (L.normal >= 0) {
348
+ const n = o + L.normal;
349
+ nor[i * 3] = u[n + 2] / 127.5 - 1;
350
+ nor[i * 3 + 1] = u[n + 1] / 127.5 - 1;
351
+ nor[i * 3 + 2] = u[n] / 127.5 - 1;
352
+ }
353
+ if (L.skin && cl) {
354
+ let sum = 0;
355
+ for (let b = 0; b < 4; b++) {
356
+ const w = u[o + 16 + b] / 255;
357
+ weight[i * 4 + b] = w;
358
+ sum += w;
359
+ bone[i * 4 + b] = cl.palette[u[o + 20 + b]] ?? 0;
360
+ }
361
+ if (sum > 0) for (let b = 0; b < 4; b++) weight[i * 4 + b] /= sum;
362
+ else weight[i * 4] = 1;
363
+ } else weight[i * 4] = 1;
364
+ }
365
+ const cnt = end - pr.firstIndex, indices = nv > 65535 ? new Uint32Array(cnt) : new Uint16Array(cnt);
366
+ for (let t = 0; t + 2 < cnt; t += 3) {
367
+ const a = lod.iAt + (pr.firstIndex + t) * 2;
368
+ indices[t] = dv.getUint16(a, true) - v0;
369
+ indices[t + 1] = dv.getUint16(a + 4, true) - v0;
370
+ indices[t + 2] = dv.getUint16(a + 2, true) - v0;
371
+ }
372
+ for (let t = 0; t < cnt; t++) if (indices[t] >= nv) throw new Error("XBG: индекс вне вершин примитива");
373
+ const normals = L.normal >= 0 ? nor : computeNormals(pos, indices);
374
+ const part = x.parts[pr.part], matPath = cl ? x.materials[cl.mat] : void 0, mat = cl ? mats[cl.mat] : null;
375
+ const partName = (part?.name ?? `part${pr.part}`).replace(/_LOD\d+$/i, "");
376
+ const tex = mat ? slotOf(mat.textures, "DiffuseTexture1", "SkinTexture", "FabricTexture", "DiffuseTexture", "DiffuseTexture2") : void 0;
377
+ const mesh = {
378
+ name: `${partName} · ${mat?.name ?? matPath?.split("\\").pop() ?? "?"}`,
379
+ texture: tex ?? "",
380
+ material: matPath,
381
+ vertexCount: nv,
382
+ pos,
383
+ nor: normals,
384
+ uv,
385
+ bone,
386
+ weight,
387
+ indices,
388
+ skinned: L.skin && !!cl
389
+ };
390
+ if (mat) {
391
+ const nm = slotOf(mat.textures, "NormalTexture1", "NormalTexture"), sp = slotOf(mat.textures, "SpecularTexture1", "SpecularTexture");
392
+ if (nm) mesh.normalMap = nm;
393
+ if (sp) mesh.surface = { specular: { map: sp, channel: 0 }, source: "Far Cry 2 .xbm" };
394
+ mesh.shader = mat.shader;
395
+ if (ALPHA_SHADERS.has(mat.shader)) mesh.alphaTest = true;
396
+ }
397
+ if (/STATE0*[2-9]$/i.test(partName) || /_N_/i.test(partName) || /LIGHT(HALO|CONE|BEAM)/i.test(mat?.name ?? "")) mesh.defaultHidden = true;
398
+ meshes.push(mesh);
399
+ });
400
+ if (!meshes.length) throw new Error("XBG: нет примитивов в ближнем LOD");
401
+ return { x, lod, meshes, skinned, missing: x.materials.filter((_, i) => !mats[i]) };
402
+ }
403
+ function bakeBindPose(x, meshes) {
404
+ if (!x.nodes.length) return meshes;
405
+ const sk = new NodeSkeleton(xbgSkeletonNodes(x.nodes, x.o2bm.length ? o2bmNodes(x).frame : void 0));
406
+ sk.pose(null, 0, false);
407
+ return meshes.map((m) => {
408
+ if (!m.skinned) return m;
409
+ const pos = new Float32Array(m.pos.length), nor = new Float32Array(m.nor.length);
410
+ for (let v = 0; v < m.vertexCount; v++) {
411
+ const px = m.pos[v * 3], py = m.pos[v * 3 + 1], pz = m.pos[v * 3 + 2], nx = m.nor[v * 3], ny = m.nor[v * 3 + 1], nz = m.nor[v * 3 + 2];
412
+ let ws = 0;
413
+ for (let k = 0; k < 4; k++) {
414
+ const w = m.weight[v * 4 + k];
415
+ if (!w) continue;
416
+ const M = sk.skinV[m.bone[v * 4 + k]];
417
+ if (!M) continue;
418
+ ws += w;
419
+ for (let c = 0; c < 3; c++) {
420
+ pos[v * 3 + c] += w * (M[c] * px + M[4 + c] * py + M[8 + c] * pz + M[12 + c]);
421
+ nor[v * 3 + c] += w * (M[c] * nx + M[4 + c] * ny + M[8 + c] * nz);
422
+ }
423
+ }
424
+ if (!ws) {
425
+ pos[v * 3] = px;
426
+ pos[v * 3 + 1] = py;
427
+ pos[v * 3 + 2] = pz;
428
+ nor[v * 3] = nx;
429
+ nor[v * 3 + 1] = ny;
430
+ nor[v * 3 + 2] = nz;
431
+ }
432
+ const l = Math.hypot(nor[v * 3], nor[v * 3 + 1], nor[v * 3 + 2]) || 1;
433
+ for (let c = 0; c < 3; c++) nor[v * 3 + c] /= l;
434
+ }
435
+ return { ...m, pos, nor, skinned: false, bone: new Float32Array(0), weight: new Float32Array(0) };
436
+ });
437
+ }
438
+ async function loadXbg(main, ctx) {
439
+ const arc = main.archive ?? ctx.archives.find((a) => a.kind === "dunia-fat") ?? null;
440
+ const { x, lod, meshes, skinned, missing } = await xbgMeshes(new Uint8Array(await main.arrayBuffer()), arc);
441
+ const kitHidden = kitHiddenParts(meshes.map((m) => m.name.split(" · ")[0]));
442
+ for (const m of meshes) if (kitHidden.has(m.name.split(" · ")[0])) m.defaultHidden = true;
443
+ let skeleton = null, skel = null;
444
+ let frame = null;
445
+ if (skinned && x.nodes.length) {
446
+ frame = x.o2bm.length ? o2bmNodes(x).frame : null;
447
+ let sn = xbgSkeletonNodes(x.nodes, frame ?? void 0);
448
+ const mp = main.path ?? main.name;
449
+ for (const c of arc ? skeletonCandidates(mp, x.nodes.some((n) => n.name === "Pelvis")) : []) {
450
+ const e = arc.lookup(c);
451
+ if (!e || !e.size) continue;
452
+ try {
453
+ skel = { path: c, bones: parseSkeleton(await arc.read(e)) };
454
+ break;
455
+ } catch {
456
+ }
457
+ }
458
+ if (skel) {
459
+ const bones = skel.bones;
460
+ sn = reparentNodes(sn, new Map(bones.filter((b) => b.parent >= 0).map((b) => [b.name.toLowerCase(), bones[b.parent].name.toLowerCase()])));
461
+ }
462
+ skeleton = new NodeSkeleton(sn);
463
+ }
464
+ const info = [
465
+ `Far Cry 2 (Dunia): ${main.name}`,
466
+ `LOD: ${x.lods.length} (показан ближний, до ${lod.dist} м), примитивов ${lod.prims.length}, вершин ${lod.buffers.reduce((s2, b) => s2 + b.vcount, 0)}${skinned ? ", скин" : ""}`,
467
+ `Узлов: ${x.nodes.length}, частей: ${new Set(lod.prims.map((p) => p.part)).size}, материалов: ${x.materials.length}`
468
+ ];
469
+ if (kitHidden.size) info.push(`Кит персонажа: показан один вариант на слот, скрыто частей ${kitHidden.size} (включаются в списке мешей)`);
470
+ if (missing.length) info.push(`Не найдены материалы: ${missing.slice(0, 3).map((m) => m.split("\\").pop()).join(", ")}${missing.length > 3 ? "…" : ""}`);
471
+ if (skel) info.push(`Скелет анимаций: ${skel.path.split("\\").slice(-2).join("\\")} (${skel.bones.length} костей)`);
472
+ const model = { meshes, skeleton, motions: [], bounds: skeleton ? skinnedBounds(skeleton, meshes) : boundsOf(meshes), info, defaultZUp: true };
473
+ if (skeleton && skel) attachMabMotions(model, skeleton, x, skel);
474
+ return model;
475
+ }
476
+ function attachMabMotions(model, sk, x, skel) {
477
+ const byName = new Map(x.nodes.map((n, i) => [n.name.toLowerCase(), i]));
478
+ const bones = skel.bones.map((b) => byName.get(b.name.toLowerCase()) ?? -1);
479
+ const world = [];
480
+ sk.nodes.forEach((n, i) => {
481
+ world[i] = n.parent >= 0 ? mulCol(world[n.parent], n.local) : Float64Array.from(n.local);
482
+ });
483
+ const fix = /* @__PURE__ */ new Map();
484
+ let root;
485
+ skel.bones.forEach((b, i) => {
486
+ const n = bones[i];
487
+ if (b.parent >= 0 || n < 0) return;
488
+ const p = sk.nodes[n].parent;
489
+ if (p >= 0) fix.set(i, rigidInv(world[p]));
490
+ if (!root) root = { bone: i, rot: matQuat(world[n]), pos: [world[n][12], world[n][13], world[n][14]] };
491
+ });
492
+ model.meta = { ...model.meta, duniaSkeleton: skel.path };
493
+ model.loadMotions = async (file) => {
494
+ const u = new Uint8Array(await file.arrayBuffer());
495
+ return [buildMabMotion(file.name.replace(/\.mab$/i, ""), parseMab(u), bones, fix, root)];
496
+ };
497
+ }
498
+ function listMabMotions(model, archive) {
499
+ const sp = model.meta?.duniaSkeleton;
500
+ if (!sp || !model.loadMotions) return [];
501
+ const dir = sp.replace(/\\/g, "/").toLowerCase().split("/").slice(0, -1).join("/") + "/";
502
+ const out = [];
503
+ for (const e of archive.entries) {
504
+ if (!e.path.endsWith(".mab") || !e.path.startsWith(dir)) continue;
505
+ const rel = e.path.slice(dir.length).replace(/\.mab$/, "");
506
+ out.push({ kind: "node", name: rel, tps: 30, chan: /* @__PURE__ */ new Map(), duration: 0, load: async () => (await model.loadMotions(archive.fileOf(e)))[0] });
507
+ }
508
+ return out.sort((a, b) => a.name.localeCompare(b.name));
509
+ }
510
+ export {
511
+ bakeBindPose,
512
+ kitHiddenParts,
513
+ listMabMotions,
514
+ loadXbg,
515
+ o2bmNodes,
516
+ parseXbg,
517
+ parseXbm,
518
+ readChunks,
519
+ reparentNodes,
520
+ skeletonCandidates,
521
+ xbgMeshes,
522
+ xbgSkeletonNodes
523
+ };
524
+ //# sourceMappingURL=xbg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xbg.js","sources":["../src/xbg.ts"],"sourcesContent":["// Dunia models (Far Cry 2): .xbg (geometry) and .xbm (material) share the \"HSEM\" (MESH) container:\n// u32 \"MESH\", u16 version 0x2a, u16, u32 hash, …, u32 [0x1c] root chunk count; a chunk is u32 tag (bytes reversed),\n// u32 1, u32 full size, u32 own data size, u32 child count; children come before their data.\n// .xbg chunks:\n// RMTL = materials: u32 n, u32 0, n × (u32 length, .xbm path, 0);\n// NODE = nodes: u32 n, u32 0, n × (i32 first child, i32 next, i32 parent, quaternion xyzw, position, scale,\n// i32, f32, f32, u32 length, name, 0, u32 hash);\n// SKID = parts: u32 n, n × (u32 hash, u16 LOD, u16 node); SKND = parts: u32 n, n × (f32 distance, sphere f32×4,\n// box f32×6, u32 LOD, u32, u32 length, name \"…_LOD0\", 0), a child CLUS per part: u32 k, k × cluster\n// (u16 material, u16 triangles ×2, u16 indices, u16 stride, u16 vertices, u16 format, u16×48 = nodes for vertex bones);\n// LODS = geometry: u32 LOD count, per LOD: f32 distance, u32 buffers, buffers (u32 format, u32 stride, u32 vertices,\n// u32 offset in the LOD vertex data), u32 primitive count, primitives of 7×u32 (buffer, part, cluster in the part,\n// first index, last vertex in the buffer, byte offset of the first vertex, 0), u32 vertex bytes +\n// alignment to 16 (in the file), vertices of all buffers, u32 indices + alignment, u16 indices (from the buffer start);\n// PCMP / UCMP = position / UV decompression: f32 offset, f32 multiplier (value = s16 · multiplier + offset).\n// 16-byte vertex (0x008a, grass): position, UV, color; normals are computed from triangles.\n// 32-byte vertex (format 0x0bca): position s16×3 + s16 (1), UV s16×2, UV2 s16×2, normal u8×4 (BGRA), color u8×4,\n// tangent, binormal. 40 bytes (0x0bda, skinned): position, UV, UV2, weights u8×4, bones u8×4 (cluster table index),\n// normal, color, tangent, binormal. Triangles are clockwise (D3D): the order is swapped on read.\n// .xbm material: the same container, DMTL chunk: 5 bytes, u32+name, u32+shader, u32 n, n × (u32+.xbt path, u32+slot), numbers.\n// Color slots by shader: Generic/Weapon/Vehicle/Hair/Leaf use DiffuseTexture1, Skin uses SkinTexture, Cloth uses FabricTexture.\n// Coordinate system: Z up, metres.\nimport { boundsOf, computeNormals, skinnedBounds } from '@gmvjs/core/geometry';\nimport { compose, mulCol, rigidInv } from '@gmvjs/core/math';\nimport type { LoadContext } from '@gmvjs/core/plugin';\nimport { NodeSkeleton, type SkeletonNode } from '@gmvjs/core/skeleton';\nimport type { Archive, Mesh, Model, NodeMotion, VFile } from '@gmvjs/core/types';\nimport { buildMabMotion, matQuat, parseMab, parseSkeleton, type DuniaSkelBone } from './mab';\n\ninterface Chunk { tag: string; at: number; size: number; data: number; dsize: number; kids: Chunk[] }\n\nexport function readChunks(u: Uint8Array): Chunk[] {\n const dv = new DataView(u.buffer, u.byteOffset, u.byteLength);\n if (u.length < 0x20 || dv.getUint32(0, true) !== 0x4d455348) throw new Error('Не модель Dunia (нет «MESH»)');\n const walk = (p: number, end: number, depth: number): Chunk[] => {\n const out: Chunk[] = [];\n while (p + 20 <= end) {\n const tag = String.fromCharCode(u[p + 3]!, u[p + 2]!, u[p + 1]!, u[p]!), size = dv.getUint32(p + 8, true), dsize = dv.getUint32(p + 12, true), nk = dv.getUint32(p + 16, true);\n if (size < 20 || p + size > end || dsize > size - 20 || depth > 8) throw new Error(`XBG: чанк ${tag} повреждён`);\n const data = p + size - dsize;\n out.push({ tag, at: p, size, data, dsize, kids: nk ? walk(p + 20, data, depth + 1) : [] });\n p += size;\n }\n return out;\n };\n return walk(0x20, u.length, 0);\n}\n\nclass R {\n readonly dv: DataView;\n constructor(readonly u: Uint8Array, public p: number) { this.dv = new DataView(u.buffer, u.byteOffset, u.byteLength); }\n u8() { return this.u[this.p++]!; }\n u16() { const v = this.dv.getUint16(this.p, true); this.p += 2; return v; }\n u32() { const v = this.dv.getUint32(this.p, true); this.p += 4; return v; }\n i32() { const v = this.dv.getInt32(this.p, true); this.p += 4; return v; }\n f32() { const v = this.dv.getFloat32(this.p, true); this.p += 4; return v; }\n str() {\n const n = this.u32();\n if (n > 4096 || this.p + n > this.u.length) throw new Error(`Dunia: строка длиной ${n} на ${this.p - 4}`);\n let s = ''; for (let i = 0; i < n; i++) s += String.fromCharCode(this.u[this.p + i]!); this.p += n + 1; return s;\n }\n align16() { this.p = (this.p + 15) & ~15; }\n}\n\nexport interface XbgNode { name: string; parent: number; rot: number[]; pos: number[]; scale: number[] }\nexport interface XbgCluster { mat: number; tris: number; palette: number[] }\nexport interface XbgPrim { buffer: number; part: number; cluster: number; firstIndex: number; lastVertex: number; firstVertex: number }\n/** LOD vertex buffer: format, stride, vertex count, offset in the LOD's shared vertex data. */\nexport interface XbgBuffer { format: number; stride: number; vcount: number; base: number }\nexport interface XbgLod { dist: number; buffers: XbgBuffer[]; prims: XbgPrim[]; vAt: number; iAt: number; icount: number }\nexport interface Xbg {\n materials: string[]; nodes: XbgNode[]; parts: Array<{ name: string; lod: number; node: number }>; clusters: XbgCluster[][];\n lods: XbgLod[]; pcmp: [number, number]; ucmp: [number, number]; u: Uint8Array;\n /** Animation bones (O2BM): inverse bind matrices (column-major) in bone order. */\n o2bm: Float64Array[];\n}\n\nexport function parseXbg(u: Uint8Array): Xbg {\n const chunks = readChunks(u);\n const find = (t: string, list = chunks): Chunk | undefined => { for (const c of list) { if (c.tag === t) return c; const k = find(t, c.kids); if (k) return k; } return undefined; };\n const x: Xbg = { materials: [], nodes: [], parts: [], clusters: [], lods: [], pcmp: [0, 1 / 32768], ucmp: [0, 1 / 32768], u, o2bm: [] };\n const rm = find('RMTL');\n if (rm) { const r = new R(u, rm.data), n = r.u32(); r.u32(); for (let i = 0; i < n; i++) x.materials.push(r.str()); }\n const nd = find('NODE');\n if (nd) {\n const r = new R(u, nd.data), n = r.u32(); r.u32();\n for (let i = 0; i < n; i++) {\n r.i32(); r.i32(); const parent = r.i32();\n const q = [r.f32(), r.f32(), r.f32(), r.f32()], pos = [r.f32(), r.f32(), r.f32()], scale = [r.f32(), r.f32(), r.f32()];\n r.i32(); r.f32(); r.f32();\n const name = r.str(); r.u32();\n x.nodes.push({ name, parent, rot: [q[3]!, q[0]!, q[1]!, q[2]!], pos, scale });\n }\n }\n const sk = find('SKND'), sid = find('SKID');\n const partNode: number[] = [];\n if (sid) { const r = new R(u, sid.data), n = r.u32(); for (let i = 0; i < n; i++) { r.u32(); r.u16(); const nd2 = r.u16(); partNode.push(nd2 === 0xffff ? -1 : nd2); } }\n if (sk) {\n const r = new R(u, sk.data), n = r.u32();\n for (let i = 0; i < n; i++) { r.p += 44; const lod = r.u32(); r.u32(); x.parts.push({ name: r.str(), lod, node: partNode[i] ?? -1 }); }\n const cl = sk.kids.find((k) => k.tag === 'CLUS');\n if (cl) {\n const r2 = new R(u, cl.data);\n for (let i = 0; i < n && r2.p < cl.data + cl.dsize; i++) {\n const k = r2.u32(), list: XbgCluster[] = [];\n for (let j = 0; j < k; j++) {\n const mat = r2.u16(), tris = r2.u16(); r2.p += 10;\n const palette: number[] = [];\n for (let b = 0; b < 48; b++) palette.push(r2.u16());\n list.push({ mat, tris, palette });\n }\n x.clusters.push(list);\n }\n }\n }\n const ld = find('LODS');\n if (!ld) throw new Error('XBG: нет геометрии (LODS)');\n {\n const r = new R(u, ld.data), n = r.u32();\n for (let l = 0; l < n; l++) {\n const dist = r.f32(), nb = r.u32(), buffers: XbgBuffer[] = [];\n for (let b = 0; b < nb; b++) buffers.push({ format: r.u32(), stride: r.u32(), vcount: r.u32(), base: r.u32() });\n const np = r.u32(), prims: XbgPrim[] = [];\n for (let i = 0; i < np; i++) {\n const buffer = r.u32(), part = r.u32(), cluster = r.u32(), firstIndex = r.u32(), lastVertex = r.u32(), voff = r.u32(); r.u32();\n const bf = buffers[buffer];\n if (!bf) throw new Error('XBG: примитив ссылается на несуществующий буфер');\n prims.push({ buffer, part, cluster, firstIndex, lastVertex, firstVertex: (voff - bf.base) / bf.stride });\n }\n const vb = r.u32(); r.align16(); const vAt = r.p; r.p += vb;\n const ic = r.u32(); r.align16(); const iAt = r.p; r.p += ic * 2;\n if (r.p > ld.data + ld.dsize || vb !== buffers.reduce((s2, b) => s2 + b.vcount * b.stride, 0)) throw new Error('XBG: буфер LOD повреждён');\n x.lods.push({ dist, buffers, prims, vAt, iAt, icount: ic });\n }\n }\n const ob = find('O2BM');\n if (ob && ob.dsize >= 4) {\n const dv2 = new DataView(u.buffer, u.byteOffset, u.byteLength), n = dv2.getUint32(ob.data, true);\n if (4 + n * 64 <= ob.dsize) for (let i = 0; i < n; i++) x.o2bm.push(Float64Array.from({ length: 16 }, (_, k) => dv2.getFloat32(ob.data + 4 + i * 64 + k * 4, true)));\n }\n const cmp = (t: string): [number, number] | null => { const c = find(t); if (!c || c.dsize < 8) return null; const dv = new DataView(u.buffer, u.byteOffset + c.data, 8); return [dv.getFloat32(0, true), dv.getFloat32(4, true)]; };\n x.pcmp = cmp('PCMP') ?? x.pcmp;\n x.ucmp = cmp('UCMP') ?? x.ucmp;\n return x;\n}\n\nexport interface XbmMaterial { name: string; shader: string; textures: Record<string, string> }\n\nexport function parseXbm(u: Uint8Array): XbmMaterial {\n const c = readChunks(u).find((k) => k.tag === 'DMTL');\n if (!c) throw new Error('XBM: нет чанка DMTL');\n const r = new R(u, c.data + 5);\n const name = r.str(), shader = r.str(), n = r.u32(), textures: Record<string, string> = {};\n for (let i = 0; i < n && i < 64; i++) { const path = r.str(), slot = r.str(); if (path) textures[slot] = path; }\n return { name, shader, textures };\n}\n\n/**\n * Character \"kits\" (merc_kit, multiplayer classes, female_civilian_kit): all swappable parts live in one model.\n * By default one part per slot is visible (a head with its eyes, a body of its race, hair, top, bottom, shoes; \"no hat\",\n * \"no glasses\", \"no gear\" where available), \"DUMMY…\" are hidden. Returns the names of the parts to hide.\n */\nexport function kitHiddenParts(names: readonly string[]): Set<string> {\n const parts = [...new Set(names)], hidden = new Set<string>();\n const heads = parts.filter((n) => /HEAD/.test(n) && !/EYES|ROOT/.test(n));\n if (heads.length < 2 && !parts.some((n) => /DUMMY/.test(n))) return hidden; // not a kit\n const key = (n: string) => n.replace(/^P_[A-Z]+_/, '').replace(/_LOD\\d+$/, '').replace(/_/g, '');\n const head = heads.find((n) => /CAUCASIANHEAD01/.test(n)) ?? heads[0];\n const race = head && /NUBIAN/.test(head) ? 'NUBIAN' : 'CAUCASIAN';\n const choose = (list: string[], prefer: RegExp[] = []) => {\n if (list.length < 2) return;\n let keep = list[0]!;\n for (const re of prefer) { const f = list.find((n) => re.test(n)); if (f) { keep = f; break; } }\n for (const n of list) if (n !== keep) hidden.add(n);\n };\n for (const n of parts) if (/DUMMY/.test(n)) hidden.add(n);\n for (const n of heads) if (n !== head) hidden.add(n);\n if (head) for (const n of parts.filter((x) => /EYES/.test(x))) if (key(n).replace(/^EYES/, '') !== key(head)) hidden.add(n);\n const rest = parts.filter((n) => !hidden.has(n) && !heads.includes(n) && !/EYES/.test(n));\n const slot = (re: RegExp, not?: RegExp) => rest.filter((n) => re.test(n) && !(not && not.test(n)));\n const top = slot(/_UB_|DRESS/); // TOP_BODY is the body with arms, not clothing\n choose(top);\n const topKept = top.find((n) => !hidden.has(n)) ?? '';\n const armsStyle = /BEATER/.test(topKept) ? 'BEATER' : /VEST/.test(topKept) ? 'LONGVEST' : 'SHIRT';\n // body of its own race; arms under the top\n for (const re of [/_ARMS$/, /BCHEST/, /_LEGS$/]) {\n const list = slot(re);\n choose(list, re.source === '_ARMS$' ? [new RegExp(`${race}_${armsStyle}_ARMS`), new RegExp(race)] : [new RegExp(race)]);\n }\n choose(slot(/HAIR/), [race === 'NUBIAN' ? /AFRO/ : /CAUC/]);\n choose(slot(/HAT|CAP\\d|BEANIE|BANDANA/), [/NOHAT/]);\n choose(slot(/GLASS|GOGGLE/), [/NOGLASS/]);\n choose(slot(/_LB_.*(PANT|JEANS|SKIRT)|LEGS_.*SKIRT/));\n choose(slot(/BOOT/));\n choose(slot(/GEAR|BACKPACK|STRAPS|LEATHER_VEST|TACTICAL_VEST|FLACK_JACKET|VESTSPORT/), [/NOGEAR/]);\n for (const j of ['BRACELET', 'NECKLACE']) choose(slot(new RegExp(j)));\n return hidden;\n}\n\n/** Vertex layout by stride: normal offset (−1 = none, computed from triangles), weights/bones from 16. */\nconst LAYOUT: Record<number, { normal: number; skin: boolean }> = { 16: { normal: -1, skin: false }, 32: { normal: 16, skin: false }, 40: { normal: 24, skin: true } };\n\n/** Foliage, grass and hair shaders: alpha cutout. */\nconst ALPHA_SHADERS = new Set(['Leaf', 'BigLeaf', 'Grass', 'Hair', 'Decal']);\n\nconst slotOf = (t: Record<string, string>, ...keys: string[]) => { for (const k of keys) if (t[k]) return t[k]; return undefined; };\n\n/**\n * Animation bones -> nodes. Bone i of the O2BM list is the node whose world pose matches the bone pose up to a common\n * transform T (for animals T = 1; for humans O2BM bones are relative to the pelvis at the origin, rotated 90° about Z).\n * T is found by trying nodes for bone 0. The list lacks \"…Nub\", the root and parts, so numbers differ from NODE.\n * Returns the nodes (−1 = not found) and T (animation frame -> model world).\n */\nexport function o2bmNodes(x: Xbg): { nodes: number[]; frame: Float64Array } {\n const N = xbgSkeletonNodes(x.nodes).map((s) => rigidInv(s.offset!));\n const O = x.o2bm.map((m) => rigidInv(m));\n const id: Float64Array = new Float64Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);\n if (!O.length) return { nodes: [], frame: id };\n const dist = (a: ArrayLike<number>, b: ArrayLike<number>) => Math.hypot(a[12]! - b[12]!, a[13]! - b[13]!, a[14]! - b[14]!) + Math.hypot(...[0, 1, 2, 4, 5, 6, 8, 9, 10].map((k) => a[k]! - b[k]!));\n const match = (T: ArrayLike<number>, limit: number) => {\n const out: number[] = [];\n for (const o of O) {\n const w = mulCol(T, o);\n let bj = -1, bd = 0.02;\n N.forEach((n, j) => { const d = dist(n, w); if (d < bd) { bd = d; bj = j; } });\n out.push(bj);\n if (out.length >= limit && out.every((v) => v < 0)) break;\n }\n return out;\n };\n // fast path: the frames match\n let best: { n: number; nodes: number[]; frame: Float64Array } = { n: -1, nodes: [], frame: id };\n const direct = match(id, O.length);\n best = { n: direct.filter((j) => j >= 0).length, nodes: direct, frame: id };\n if (best.n < O.length) {\n N.forEach((n, c) => {\n const T = mulCol(n, rigidInv(O[0]!));\n const probe = match(T, 8);\n if (probe.length < O.length && probe.every((v) => v < 0)) return;\n const nodes = probe.length === O.length ? probe : match(T, O.length), k = nodes.filter((j) => j >= 0).length;\n if (k > best.n) best = { n: k, nodes, frame: T };\n });\n }\n return { nodes: best.nodes, frame: best.frame };\n}\n\n/**\n * Hierarchy as in the animation skeleton: helper bones (knees, elbows) hang from the pelvis in the model but from the\n * 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.\n */\nexport function reparentNodes(sn: SkeletonNode[], parentOf: ReadonlyMap<string, string>): SkeletonNode[] {\n const byName = new Map(sn.map((n, i) => [n.name.toLowerCase(), i]));\n const world: Float64Array[] = [];\n sn.forEach((n, i) => { world[i] = n.parent >= 0 ? mulCol(world[n.parent]!, n.local) : Float64Array.from(n.local); });\n return sn.map((n, i) => {\n const pn = parentOf.get(n.name.toLowerCase());\n const p = pn === undefined ? undefined : byName.get(pn);\n if (p === undefined || p === n.parent || p >= i) return n;\n return { ...n, parent: p, local: mulCol(rigidInv(world[p]!), world[i]!) };\n });\n}\n\n/**\n * Skeleton nodes: node poses are in parent space. frame: the vertex frame of skinned meshes; for humans vertices are written\n * in the O2BM bone frame (pelvis at the origin) while nodes are in the model frame; offset = (node world bind pose)⁻¹ · frame.\n */\nexport function xbgSkeletonNodes(nodes: XbgNode[], frame?: ArrayLike<number>): SkeletonNode[] {\n const local = nodes.map((n) => { const m = new Float64Array(16); compose(n.pos, n.rot, n.scale, m); return m; });\n const world: Float64Array[] = [];\n nodes.forEach((n, i) => { world[i] = n.parent >= 0 && n.parent < i ? mulCol(world[n.parent]!, local[i]!) : local[i]!; });\n return nodes.map((n, i) => ({ name: n.name, parent: n.parent >= 0 && n.parent < i ? n.parent : -1, local: local[i]!, offset: frame ? mulCol(rigidInv(world[i]!), frame) : rigidInv(world[i]!) }));\n}\n\n/**\n * The model's animation skeleton: \"<model name>_ref.skeleton\" next to it, then up the folders \"hips_ref.skeleton\",\n * \"pelvis_ref.skeleton\", \"_common\\\\pelvis_ref.skeleton\"; for humans outside graphics\\\\characters (actors), the shared human skeleton.\n */\nexport function skeletonCandidates(modelPath: string, human = false): string[] {\n const parts = modelPath.replace(/\\//g, '\\\\').toLowerCase().split('\\\\'), stem = parts[parts.length - 1]!.replace(/\\.xbg$/, '');\n const dirs = parts.slice(0, -1), out = [`${dirs.join('\\\\')}\\\\${stem}_ref.skeleton`];\n for (let n = dirs.length; n >= 2; n--) {\n const dir = dirs.slice(0, n).join('\\\\');\n out.push(`${dir}\\\\hips_ref.skeleton`, `${dir}\\\\pelvis_ref.skeleton`, `${dir}\\\\_common\\\\pelvis_ref.skeleton`);\n }\n if (human) out.push('graphics\\\\characters\\\\_common\\\\pelvis_ref.skeleton');\n return out;\n}\n\n/** Near-LOD meshes of an .xbg model (with .xbm materials from the archive); matCache is a shared material cache (for levels). */\nexport async function xbgMeshes(u: Uint8Array, arc: Archive | null, matCache?: Map<string, Promise<XbmMaterial | null>>, lodIndex = 0, parsed?: Xbg) {\n const x = parsed ?? parseXbg(u), lod = x.lods[Math.min(lodIndex, x.lods.length - 1)];\n if (!lod) throw new Error('XBG: нет LOD');\n const dv = new DataView(u.buffer, u.byteOffset, u.byteLength);\n for (const b of lod.buffers) if (!LAYOUT[b.stride]) throw new Error(`XBG: вершина ${b.stride} байт (формат ${b.format.toString(16)}) пока не поддерживается`);\n const skinned = lod.buffers.some((b) => LAYOUT[b.stride]!.skin);\n const readMat = async (p: string) => {\n const e = arc?.lookup(p);\n if (!arc || !e) return null;\n try { return parseXbm(await arc.read(e)); } catch { return null; }\n };\n const mats = await Promise.all(x.materials.map((p) => {\n if (!matCache) return readMat(p);\n const k = p.toLowerCase();\n let r = matCache.get(k);\n if (!r) { r = readMat(p); matCache.set(k, r); }\n return r;\n }));\n const [po, ps] = x.pcmp, [uo, us] = x.ucmp;\n const meshes: Mesh[] = [];\n lod.prims.forEach((pr, k) => {\n const next = lod.prims[k + 1], end = next ? next.firstIndex : lod.icount;\n const cl = x.clusters[pr.part]?.[pr.cluster], v0 = pr.firstVertex, nv = pr.lastVertex - v0 + 1;\n const bf = lod.buffers[pr.buffer]!, L = LAYOUT[bf.stride]!;\n if (nv <= 0 || end <= pr.firstIndex) return;\n const pos = new Float32Array(nv * 3), nor = new Float32Array(nv * 3), uv = new Float32Array(nv * 2);\n const bone = new Float32Array(nv * 4), weight = new Float32Array(nv * 4);\n for (let i = 0; i < nv; i++) {\n const o = lod.vAt + bf.base + (v0 + i) * bf.stride;\n for (let c = 0; c < 3; c++) pos[i * 3 + c] = dv.getInt16(o + c * 2, true) * ps + po;\n uv[i * 2] = dv.getInt16(o + 8, true) * us + uo; uv[i * 2 + 1] = dv.getInt16(o + 10, true) * us + uo;\n if (L.normal >= 0) { const n = o + L.normal; nor[i * 3] = u[n + 2]! / 127.5 - 1; nor[i * 3 + 1] = u[n + 1]! / 127.5 - 1; nor[i * 3 + 2] = u[n]! / 127.5 - 1; }\n if (L.skin && cl) {\n let sum = 0;\n for (let b = 0; b < 4; b++) { const w = u[o + 16 + b]! / 255; weight[i * 4 + b] = w; sum += w; bone[i * 4 + b] = cl.palette[u[o + 20 + b]!] ?? 0; }\n if (sum > 0) for (let b = 0; b < 4; b++) weight[i * 4 + b]! /= sum; else weight[i * 4] = 1;\n } else weight[i * 4] = 1;\n }\n const cnt = end - pr.firstIndex, indices = nv > 65535 ? new Uint32Array(cnt) : new Uint16Array(cnt);\n for (let t = 0; t + 2 < cnt; t += 3) {\n const a = lod.iAt + (pr.firstIndex + t) * 2;\n indices[t] = dv.getUint16(a, true) - v0; indices[t + 1] = dv.getUint16(a + 4, true) - v0; indices[t + 2] = dv.getUint16(a + 2, true) - v0;\n }\n for (let t = 0; t < cnt; t++) if (indices[t]! >= nv) throw new Error('XBG: индекс вне вершин примитива');\n const normals = L.normal >= 0 ? nor : computeNormals(pos, indices);\n const part = x.parts[pr.part], matPath = cl ? x.materials[cl.mat] : undefined, mat = cl ? mats[cl.mat] : null;\n const partName = (part?.name ?? `part${pr.part}`).replace(/_LOD\\d+$/i, '');\n const tex = mat ? slotOf(mat.textures, 'DiffuseTexture1', 'SkinTexture', 'FabricTexture', 'DiffuseTexture', 'DiffuseTexture2') : undefined;\n const mesh: Mesh = {\n name: `${partName} · ${mat?.name ?? matPath?.split('\\\\').pop() ?? '?'}`,\n texture: tex ?? '', material: matPath, vertexCount: nv, pos, nor: normals, uv, bone, weight, indices, skinned: L.skin && !!cl,\n };\n if (mat) {\n const nm = slotOf(mat.textures, 'NormalTexture1', 'NormalTexture'), sp = slotOf(mat.textures, 'SpecularTexture1', 'SpecularTexture');\n if (nm) mesh.normalMap = nm;\n if (sp) mesh.surface = { specular: { map: sp, channel: 0 }, source: 'Far Cry 2 .xbm' };\n mesh.shader = mat.shader;\n if (ALPHA_SHADERS.has(mat.shader)) mesh.alphaTest = true;\n }\n // by default the whole car by day: damage states (…STATE02), night headlights (…_N_…) and light cones are hidden\n if (/STATE0*[2-9]$/i.test(partName) || /_N_/i.test(partName) || /LIGHT(HALO|CONE|BEAM)/i.test(mat?.name ?? '')) mesh.defaultHidden = true;\n meshes.push(mesh);\n });\n if (!meshes.length) throw new Error('XBG: нет примитивов в ближнем LOD');\n return { x, lod, meshes, skinned, missing: x.materials.filter((_, i) => !mats[i]) };\n}\n\n/** Skinned meshes in the bind pose as static geometry (for levels): vertices via the model nodes' skin matrices. */\nexport function bakeBindPose(x: Xbg, meshes: Mesh[]): Mesh[] {\n if (!x.nodes.length) return meshes;\n const sk = new NodeSkeleton(xbgSkeletonNodes(x.nodes, x.o2bm.length ? o2bmNodes(x).frame : undefined));\n sk.pose(null, 0, false);\n return meshes.map((m) => {\n if (!m.skinned) return m;\n const pos = new Float32Array(m.pos.length), nor = new Float32Array(m.nor.length);\n for (let v = 0; v < m.vertexCount; v++) {\n const px = m.pos[v * 3]!, py = m.pos[v * 3 + 1]!, pz = m.pos[v * 3 + 2]!, nx = m.nor[v * 3]!, ny = m.nor[v * 3 + 1]!, nz = m.nor[v * 3 + 2]!;\n let ws = 0;\n for (let k = 0; k < 4; k++) {\n const w = m.weight[v * 4 + k]!;\n if (!w) continue;\n const M = sk.skinV[m.bone[v * 4 + k]!];\n if (!M) continue;\n ws += w;\n for (let c = 0; c < 3; c++) {\n pos[v * 3 + c]! += w * (M[c]! * px + M[4 + c]! * py + M[8 + c]! * pz + M[12 + c]!);\n nor[v * 3 + c]! += w * (M[c]! * nx + M[4 + c]! * ny + M[8 + c]! * nz);\n }\n }\n if (!ws) { pos[v * 3] = px; pos[v * 3 + 1] = py; pos[v * 3 + 2] = pz; nor[v * 3] = nx; nor[v * 3 + 1] = ny; nor[v * 3 + 2] = nz; }\n const l = Math.hypot(nor[v * 3]!, nor[v * 3 + 1]!, nor[v * 3 + 2]!) || 1;\n for (let c = 0; c < 3; c++) nor[v * 3 + c]! /= l;\n }\n return { ...m, pos, nor, skinned: false, bone: new Float32Array(0), weight: new Float32Array(0) };\n });\n}\n\nexport async function loadXbg(main: VFile, ctx: LoadContext): Promise<Model> {\n const arc = main.archive ?? ctx.archives.find((a) => a.kind === 'dunia-fat') ?? null;\n const { x, lod, meshes, skinned, missing } = await xbgMeshes(new Uint8Array(await main.arrayBuffer()), arc);\n const kitHidden = kitHiddenParts(meshes.map((m) => m.name.split(' · ')[0]!));\n for (const m of meshes) if (kitHidden.has(m.name.split(' · ')[0]!)) m.defaultHidden = true;\n let skeleton: NodeSkeleton | null = null, skel: { path: string; bones: DuniaSkelBone[] } | null = null;\n let frame: Float64Array | null = null;\n if (skinned && x.nodes.length) {\n frame = x.o2bm.length ? o2bmNodes(x).frame : null;\n let sn = xbgSkeletonNodes(x.nodes, frame ?? undefined);\n // animation skeleton: helper bone hierarchy and bone numbers in .mab\n const mp = main.path ?? main.name;\n for (const c of arc ? skeletonCandidates(mp, x.nodes.some((n) => n.name === 'Pelvis')) : []) {\n const e = arc!.lookup(c);\n if (!e || !e.size) continue;\n try { skel = { path: c, bones: parseSkeleton(await arc!.read(e)) }; break; } catch { /* not a skeleton */ }\n }\n if (skel) {\n const bones = skel.bones;\n sn = reparentNodes(sn, new Map(bones.filter((b) => b.parent >= 0).map((b) => [b.name.toLowerCase(), bones[b.parent]!.name.toLowerCase()])));\n }\n skeleton = new NodeSkeleton(sn);\n }\n const info = [\n `Far Cry 2 (Dunia): ${main.name}`,\n `LOD: ${x.lods.length} (показан ближний, до ${lod.dist} м), примитивов ${lod.prims.length}, вершин ${lod.buffers.reduce((s2, b) => s2 + b.vcount, 0)}${skinned ? ', скин' : ''}`,\n `Узлов: ${x.nodes.length}, частей: ${new Set(lod.prims.map((p) => p.part)).size}, материалов: ${x.materials.length}`,\n ];\n if (kitHidden.size) info.push(`Кит персонажа: показан один вариант на слот, скрыто частей ${kitHidden.size} (включаются в списке мешей)`);\n if (missing.length) info.push(`Не найдены материалы: ${missing.slice(0, 3).map((m) => m.split('\\\\').pop()).join(', ')}${missing.length > 3 ? '…' : ''}`);\n if (skel) info.push(`Скелет анимаций: ${skel.path.split('\\\\').slice(-2).join('\\\\')} (${skel.bones.length} костей)`);\n const model: Model = { meshes, skeleton, motions: [], bounds: skeleton ? skinnedBounds(skeleton, meshes) : boundsOf(meshes), info, defaultZUp: true };\n if (skeleton && skel) attachMabMotions(model, skeleton, x, skel);\n return model;\n}\n\n/** .mab animations for the model: bones by name from .skeleton, the pelvis mapped from the animation frame (Z up, ground) into the parent node's space. */\nfunction attachMabMotions(model: Model, sk: NodeSkeleton, x: Xbg, skel: { path: string; bones: DuniaSkelBone[] }): void {\n const byName = new Map(x.nodes.map((n, i) => [n.name.toLowerCase(), i]));\n const bones = skel.bones.map((b) => byName.get(b.name.toLowerCase()) ?? -1);\n // node world bind poses (by the skeleton hierarchy)\n const world: Float64Array[] = [];\n sk.nodes.forEach((n, i) => { world[i] = n.parent >= 0 ? mulCol(world[n.parent]!, n.local) : Float64Array.from(n.local); });\n const fix = new Map<number, Float64Array>();\n let root: { bone: number; rot: number[]; pos: number[] } | undefined;\n skel.bones.forEach((b, i) => {\n const n = bones[i]!;\n if (b.parent >= 0 || n < 0) return;\n const p = sk.nodes[n]!.parent;\n if (p >= 0) fix.set(i, rigidInv(world[p]!));\n // root pose in the animation frame comes from the model (for humans the pelvis bone in .skeleton sits at the origin)\n if (!root) root = { bone: i, rot: matQuat(world[n]!), pos: [world[n]![12]!, world[n]![13]!, world[n]![14]!] };\n });\n model.meta = { ...model.meta, duniaSkeleton: skel.path };\n model.loadMotions = async (file) => {\n const u = new Uint8Array(await file.arrayBuffer());\n return [buildMabMotion(file.name.replace(/\\.mab$/i, ''), parseMab(u), bones, fix, root)];\n };\n}\n\n/** Animations for the model (data on selection): every .mab in the skeleton folder and below. */\nexport function listMabMotions(model: Model, archive: Archive): NodeMotion[] {\n const sp = model.meta?.duniaSkeleton as string | undefined;\n if (!sp || !model.loadMotions) return [];\n const dir = sp.replace(/\\\\/g, '/').toLowerCase().split('/').slice(0, -1).join('/') + '/';\n const out: NodeMotion[] = [];\n for (const e of archive.entries) {\n if (!e.path.endsWith('.mab') || !e.path.startsWith(dir)) continue;\n const rel = e.path.slice(dir.length).replace(/\\.mab$/, '');\n out.push({ kind: 'node', name: rel, tps: 30, chan: new Map(), duration: 0, load: async () => (await model.loadMotions!(archive.fileOf(e)))[0]! } as NodeMotion);\n }\n return out.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n"],"names":[],"mappings":"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"}
package/dist/xbt.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import type { DecodedImage } from '@gmvjs/core/types';
2
+ export declare function xbtDds(buf: ArrayBuffer): ArrayBuffer;
3
+ export declare const decodeXbt: (buf: ArrayBuffer) => DecodedImage;
package/dist/xbt.js ADDED
@@ -0,0 +1,14 @@
1
+ import { decodeDDS } from "@gmvjs/core/images/dds";
2
+ function xbtDds(buf) {
3
+ const dv = new DataView(buf);
4
+ if (buf.byteLength < 32 || dv.getUint32(0, true) !== 5784148) throw new Error("Не текстура Dunia (нет «TBX»)");
5
+ const off = dv.getUint32(8, true);
6
+ if (off + 128 > buf.byteLength || dv.getUint32(off, true) !== 542327876) throw new Error("XBT: нет DDS после заголовка");
7
+ return buf.slice(off);
8
+ }
9
+ const decodeXbt = (buf) => decodeDDS(xbtDds(buf));
10
+ export {
11
+ decodeXbt,
12
+ xbtDds
13
+ };
14
+ //# sourceMappingURL=xbt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"xbt.js","sources":["../src/xbt.ts"],"sourcesContent":["// Dunia (Far Cry 2) .xbt textures: \"TBX\\0\", u32 version 0x0b, u32 DDS offset (header size), u32 2, 12 bytes,\n// the path \"…_mip0.xbt\" (if the top level lives in a separate file), then a regular DDS (DXT1/DXT5, mips).\nimport { decodeDDS } from '@gmvjs/core/images/dds';\nimport type { DecodedImage } from '@gmvjs/core/types';\n\nexport function xbtDds(buf: ArrayBuffer): ArrayBuffer {\n const dv = new DataView(buf);\n if (buf.byteLength < 0x20 || dv.getUint32(0, true) !== 0x00584254) throw new Error('Не текстура Dunia (нет «TBX»)');\n const off = dv.getUint32(8, true);\n if (off + 128 > buf.byteLength || dv.getUint32(off, true) !== 0x20534444) throw new Error('XBT: нет DDS после заголовка');\n return buf.slice(off);\n}\n\nexport const decodeXbt = (buf: ArrayBuffer): DecodedImage => decodeDDS(xbtDds(buf));\n"],"names":[],"mappings":";AAKO,SAAS,OAAO,KAA+B;AACpD,QAAM,KAAK,IAAI,SAAS,GAAG;AAC3B,MAAI,IAAI,aAAa,MAAQ,GAAG,UAAU,GAAG,IAAI,MAAM,QAAY,OAAM,IAAI,MAAM,+BAA+B;AAClH,QAAM,MAAM,GAAG,UAAU,GAAG,IAAI;AAChC,MAAI,MAAM,MAAM,IAAI,cAAc,GAAG,UAAU,KAAK,IAAI,MAAM,UAAY,OAAM,IAAI,MAAM,8BAA8B;AACxH,SAAO,IAAI,MAAM,GAAG;AACtB;AAEO,MAAM,YAAY,CAAC,QAAmC,UAAU,OAAO,GAAG,CAAC;"}