@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.
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 (Far Cry 2): .xbg (геометрия) и .xbm (материал) — один контейнер «HSEM» (MESH):\n// u32 «MESH», u16 версия 0x2a, u16, u32 хеш, …, u32 [0x1c] число корневых чанков; чанк — u32 тег (байты наоборот),\n// u32 1, u32 полный размер, u32 размер своих данных, u32 число детей; дети идут перед своими данными.\n// Чанки .xbg:\n// RMTL — материалы: u32 n, u32 0, n × (u32 длина, путь .xbm, 0);\n// NODE — узлы: u32 n, u32 0, n × (i32 первый ребёнок, i32 следующий, i32 родитель, кватернион xyzw, позиция, масштаб,\n// i32, f32, f32, u32 длина, имя, 0, u32 хеш);\n// SKID — части: u32 n, n × (u32 хеш, u16 LOD, u16 узел); SKND — части: u32 n, n × (f32 дистанция, сфера f32×4,\n// рамка f32×6, u32 LOD, u32, u32 длина, имя «…_LOD0», 0), детский CLUS — по части: u32 k, k × кластер\n// (u16 материал, u16 треугольников ×2, u16 индексов, u16 шаг, u16 вершин, u16 формат, u16×48 — узлы для костей вершин);\n// LODS — геометрия: u32 число LOD, на LOD: f32 дистанция, u32 буферов, буферы (u32 формат, u32 шаг, u32 вершин,\n// u32 смещение в данных вершин LOD), u32 число примитивов, примитивы по 7×u32 (буфер, часть, кластер в части,\n// первый индекс, последняя вершина в буфере, смещение первой вершины в байтах, 0), u32 байт вершин +\n// выравнивание до 16 (по файлу), вершины всех буферов, u32 индексов + выравнивание, u16 индексы (от начала буфера);\n// PCMP / UCMP — распаковка позиций / UV: f32 сдвиг, f32 множитель (значение = s16 · множитель + сдвиг).\n// Вершина 16 байт (0x008a, трава): позиция, UV, цвет — нормали считаются по треугольникам.\n// Вершина 32 байта (формат 0x0bca): позиция s16×3 + s16 (1), UV s16×2, UV2 s16×2, нормаль u8×4 (BGRA), цвет u8×4,\n// касательная, бинормаль. 40 байт (0x0bda, скин): позиция, UV, UV2, веса u8×4, кости u8×4 (номер в таблице кластера),\n// нормаль, цвет, касательная, бинормаль. Треугольники — по часовой (D3D): при чтении меняем порядок.\n// Материал .xbm: тот же контейнер, чанк DMTL: 5 байт, u32+имя, u32+шейдер, u32 n, n × (u32+путь .xbt, u32+слот), числа.\n// Слоты цвета по шейдерам: Generic/Weapon/Vehicle/Hair/Leaf — DiffuseTexture1, Skin — SkinTexture, Cloth — FabricTexture.\n// Система координат — Z вверх, метры.\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: формат, шаг, число вершин, смещение в общих данных вершин LOD. */\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 /** Кости анимации (O2BM): обратные bind-матрицы (column-major) по порядку костей. */\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 * «Киты» персонажей (merc_kit, мультиплеерные классы, female_civilian_kit): все сменные части лежат в одной модели.\n * По умолчанию видна одна часть на слот (голова и глаза к ней, тело её расы, волосы, верх, низ, обувь; «без шапки»,\n * «без очков», «без снаряжения», если такие есть), «DUMMY…» скрыты. Возвращает имена частей, которые скрыть.\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; // не кит\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 — тело с руками, не одежда\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 // тело: своей расы; руки — под верх\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/** Раскладка вершины по шагу: смещение нормали (−1 — нет, считается по треугольникам), веса/кости с 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/** Шейдеры листвы, травы и волос — вырезание по альфе. */\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 * Кости анимации -> узлы. Кость i списка O2BM — узел, чья мировая поза совпадает с позой кости с точностью до общего\n * преобразования T (у животных T = 1; у людей кости O2BM — от таза в начале координат, повернуты на 90° вокруг Z).\n * T ищется перебором узла для кости 0. В списке нет «…Nub», корня и частей, поэтому номера не совпадают с NODE.\n * Возвращает узлы (−1 — не найден) и T (кадр анимации -> мир модели).\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 // быстрый путь: кадры совпадают\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 * Иерархия как в скелете анимаций: вспомогательные кости (колени, локти) в модели висят на тазу, а в .skeleton — на\n * бедре/плече; без переподвешивания они не следуют за конечностью. Bind-поза сохраняется. parentOf — имя родителя по имени кости.\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 * Узлы скелета: позы узлов — в пространстве родителя. frame — кадр вершин скиновых мешей: у людей вершины записаны\n * в кадре костей O2BM (таз в начале координат), а узлы — в кадре модели; offset = (мировая bind-поза узла)⁻¹ · 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 * Скелет анимаций модели: «<имя модели>_ref.skeleton» рядом, затем вверх по папкам — «hips_ref.skeleton»,\n * «pelvis_ref.skeleton», «_common\\\\pelvis_ref.skeleton»; для людей вне graphics\\\\characters (actors) — общий скелет людей.\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/** Меши ближнего LOD модели .xbg (с материалами .xbm из архива); matCache — общий кэш материалов (для уровней). */\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 // по умолчанию — целая машина днём: повреждённые состояния (…STATE02), ночные фары (…_N_…) и световые конусы скрыты\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/** Скиновые меши в bind-позе как статика (для уровней): вершины через матрицы скина узлов модели. */\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 // скелет анимаций: иерархия вспомогательных костей и номера костей в .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 { /* не скелет */ }\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 к модели: кости по именам из .skeleton, таз — из кадра анимации (Z вверх, земля) в пространство родителя-узла. */\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 // мировые bind-позы узлов (по иерархии скелета)\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 // поза корня в кадре анимации — из модели (у людей кость таза в .skeleton стоит в начале координат)\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/** Анимации для модели (данные — при выборе): все .mab в папке скелета и глубже. */\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