ldr 0.0.0-alpha.1 → 0.3.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/index.js ADDED
@@ -0,0 +1,1239 @@
1
+ // src/spec.ts
2
+ var norm = (s) => s.trim().toLowerCase().replace(/\\/g, "/").replace(/\/+/g, "/");
3
+ function parseColor(tok) {
4
+ if (/^0x/i.test(tok))
5
+ return parseInt(tok, 16);
6
+ return +tok;
7
+ }
8
+ var MOVED_RE = /^~?\s*moved\s+to\s+(.+)$/i;
9
+ function texNames(rest) {
10
+ const out = [];
11
+ let i = 0;
12
+ while (i < rest.length) {
13
+ while (rest[i] === " " || rest[i] === "\t")
14
+ i++;
15
+ if (i >= rest.length)
16
+ break;
17
+ if (rest[i] === '"') {
18
+ let v = "";
19
+ i++;
20
+ while (i < rest.length && rest[i] !== '"') {
21
+ if (rest[i] === "\\" && i + 1 < rest.length)
22
+ i++;
23
+ v += rest[i++];
24
+ }
25
+ i++;
26
+ out.push(v);
27
+ } else {
28
+ let j = i;
29
+ while (j < rest.length && rest[j] !== " " && rest[j] !== "\t")
30
+ j++;
31
+ out.push(rest.slice(i, j));
32
+ i = j;
33
+ }
34
+ }
35
+ return out;
36
+ }
37
+ function parseTexMap(tail, next = false) {
38
+ const m = /^\s*(\S+)\s+(.*)$/.exec(tail);
39
+ if (!m)
40
+ return null;
41
+ const method = m[1].toUpperCase();
42
+ const n = method === "PLANAR" ? 9 : method === "CYLINDRICAL" ? 10 : method === "SPHERICAL" ? 11 : 0;
43
+ if (!n)
44
+ return null;
45
+ const parts = m[2].trim().split(/\s+/);
46
+ if (parts.length < n + 1)
47
+ return null;
48
+ const v = parts.slice(0, n).map(Number);
49
+ if (!v.every(Number.isFinite))
50
+ return null;
51
+ let rest = m[2].trim();
52
+ for (let k = 0;k < n; k++)
53
+ rest = rest.replace(/^\S+\s*/, "");
54
+ const names = texNames(rest);
55
+ if (!names.length)
56
+ return null;
57
+ const t = { method, p1: v.slice(0, 3), p2: v.slice(3, 6), p3: v.slice(6, 9), texture: names[0] };
58
+ if (n >= 10)
59
+ t.a = v[9];
60
+ if (n === 11)
61
+ t.b = v[10];
62
+ const g = names.findIndex((x, i) => i > 0 && x.toUpperCase() === "GLOSSMAP");
63
+ if (g > 0 && names[g + 1])
64
+ t.glossmap = names[g + 1];
65
+ if (next)
66
+ t.next = true;
67
+ return t;
68
+ }
69
+ var b64 = (s) => {
70
+ const clean = s.replace(/[^A-Za-z0-9+/]/g, "");
71
+ const pad = clean + "=".repeat((4 - clean.length % 4) % 4);
72
+ const bin = atob(pad);
73
+ const out = new Uint8Array(bin.length);
74
+ for (let i = 0;i < bin.length; i++)
75
+ out[i] = bin.charCodeAt(i);
76
+ return out;
77
+ };
78
+ function geomLine(tk) {
79
+ const t = +tk[0];
80
+ if (!(t >= 1 && t <= 5) || tk.length < 2)
81
+ return null;
82
+ const color = parseColor(tk[1]);
83
+ const nums = tk.slice(2).map(Number);
84
+ if (t === 1)
85
+ return { t: 1, color, m: nums.slice(0, 12), file: norm(tk.slice(14).join(" ")) };
86
+ if (t === 2)
87
+ return { t: 2, color, p: nums.slice(0, 6) };
88
+ if (t === 3)
89
+ return { t: 3, color, p: nums.slice(0, 9) };
90
+ if (t === 4)
91
+ return { t: 4, color, p: nums.slice(0, 12) };
92
+ return { t: 5, color, p: nums.slice(0, 12) };
93
+ }
94
+ function parse(src, name = "main.ldr") {
95
+ const files = new Map;
96
+ let cur = null;
97
+ let main = "";
98
+ let data;
99
+ let dataCur = null;
100
+ let texStack = [];
101
+ let texNext = null;
102
+ const closeData = () => {
103
+ if (!dataCur)
104
+ return;
105
+ const bytes = b64(dataCur.chunks.join(""));
106
+ const png = bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71;
107
+ (data ??= new Map).set(norm(dataCur.name), { name: dataCur.name, bytes, ...png ? { mime: "image/png" } : {} });
108
+ dataCur = null;
109
+ };
110
+ const start = (n) => {
111
+ closeData();
112
+ cur = { name: norm(n), cmds: [] };
113
+ files.set(cur.name, cur);
114
+ if (!main)
115
+ main = cur.name;
116
+ texStack = [];
117
+ texNext = null;
118
+ };
119
+ let skip = false, sawBlock = false;
120
+ const firstBlock = () => {
121
+ if (sawBlock)
122
+ return;
123
+ sawBlock = true;
124
+ files.clear();
125
+ cur = null;
126
+ main = "";
127
+ };
128
+ const annotate = (c, fromColon) => {
129
+ if (texNext) {
130
+ c.tex = texNext;
131
+ texNext = null;
132
+ return;
133
+ }
134
+ const top = texStack[texStack.length - 1];
135
+ if (!top)
136
+ return;
137
+ if (top.fallback) {
138
+ if (!fromColon)
139
+ c.fallback = true;
140
+ } else
141
+ c.tex = top.map;
142
+ };
143
+ for (const raw of src.split(/\r?\n/)) {
144
+ const line = raw.trim();
145
+ if (!line)
146
+ continue;
147
+ const tk = line.split(/\s+/);
148
+ const t = +tk[0];
149
+ if (t === 0) {
150
+ const text = line.slice(1).trim();
151
+ if (tk[1] === "FILE") {
152
+ firstBlock();
153
+ skip = false;
154
+ start(text.slice(5));
155
+ continue;
156
+ }
157
+ if (tk[1] === "NOFILE") {
158
+ closeData();
159
+ cur = null;
160
+ skip = true;
161
+ continue;
162
+ }
163
+ if (tk[1] === "!DATA") {
164
+ firstBlock();
165
+ closeData();
166
+ cur = null;
167
+ skip = false;
168
+ dataCur = { name: text.slice(5).trim(), chunks: [] };
169
+ continue;
170
+ }
171
+ if (skip)
172
+ continue;
173
+ if (dataCur) {
174
+ if (tk[1] === "!:")
175
+ dataCur.chunks.push(tk.slice(2).join(""));
176
+ continue;
177
+ }
178
+ if (!cur)
179
+ start(name);
180
+ const c = { t: 0, text };
181
+ if (tk[1] === "STEP") {
182
+ c.step = true;
183
+ texStack = [];
184
+ texNext = null;
185
+ } else if (tk[1] === "BFC")
186
+ c.bfc = tk.slice(2).join(" ");
187
+ else if (tk[1] === "ROTSTEP") {
188
+ c.meta = "ROTSTEP";
189
+ if ((tk[2] ?? "").toUpperCase() === "END")
190
+ c.rotstep = { x: 0, y: 0, z: 0, mode: "END" };
191
+ else {
192
+ const mode = (tk[5] ?? "REL").toUpperCase();
193
+ c.rotstep = { x: +tk[2] || 0, y: +tk[3] || 0, z: +tk[4] || 0, mode: ["REL", "ABS", "ADD"].includes(mode) ? mode : "REL" };
194
+ }
195
+ } else if (tk[1] === "BUFEXCHG") {
196
+ c.meta = "BUFEXCHG";
197
+ const op = (tk[2] ?? "").toUpperCase();
198
+ c.bufexchg = { op: op === "RETRIEVE" ? "RETRIEVE" : "STORE", buf: tk[3] ?? "A" };
199
+ } else if (tk[1]?.startsWith("!"))
200
+ c.meta = tk[1];
201
+ if (tk[1] === "!TEXMAP") {
202
+ const op = (tk[2] ?? "").toUpperCase();
203
+ texNext = null;
204
+ if (op === "START" || op === "NEXT") {
205
+ const map = parseTexMap(text.replace(/^!TEXMAP\s+\S+/i, ""), op === "NEXT");
206
+ c.texmap = { op, ...map ? { map } : {} };
207
+ if (map) {
208
+ (cur.texmaps ??= []).push(map);
209
+ if (op === "START")
210
+ texStack.push({ map, fallback: false });
211
+ else
212
+ texNext = map;
213
+ }
214
+ } else if (op === "FALLBACK") {
215
+ c.texmap = { op };
216
+ const top = texStack[texStack.length - 1];
217
+ if (top)
218
+ top.fallback = true;
219
+ } else if (op === "END") {
220
+ c.texmap = { op };
221
+ texStack.pop();
222
+ }
223
+ } else if (tk[1] === "!:") {
224
+ texNext = null;
225
+ const g = geomLine(tk.slice(2));
226
+ if (g) {
227
+ annotate(g, true);
228
+ c.geom = g;
229
+ }
230
+ } else
231
+ texNext = null;
232
+ const moved = text.match(MOVED_RE);
233
+ if (moved && !cur.movedTo) {
234
+ let target = moved[1].trim().replace(/\\/g, "/");
235
+ if (!/\.(dat|ldr|mpd)$/i.test(target))
236
+ target += ".dat";
237
+ cur.movedTo = norm(target);
238
+ }
239
+ cur.cmds.push(c);
240
+ continue;
241
+ }
242
+ if (dataCur || skip)
243
+ continue;
244
+ if (!cur)
245
+ start(name);
246
+ const g = geomLine(tk);
247
+ if (!g)
248
+ continue;
249
+ annotate(g, false);
250
+ cur.cmds.push(g);
251
+ }
252
+ closeData();
253
+ return data ? { main, files, data } : { main, files };
254
+ }
255
+ function resolveTexture(doc, name) {
256
+ if (!doc.data)
257
+ return null;
258
+ const n = norm(name);
259
+ return doc.data.get(n) ?? doc.data.get(n.replace(/^textures\//, "")) ?? doc.data.get("textures/" + n) ?? null;
260
+ }
261
+ var sub3 = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
262
+ var dot3 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
263
+ var cross3 = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
264
+ var unit3 = (a) => {
265
+ const l = Math.hypot(a[0], a[1], a[2]) || 1;
266
+ return [a[0] / l, a[1] / l, a[2] / l];
267
+ };
268
+ var DEG = Math.PI / 180;
269
+ function texmapUV(map, p) {
270
+ const w = texmapTriangleUV(map, [p, p, p]);
271
+ const u = w[0], v = w[1];
272
+ return { u, v, inside: u >= -0.000001 && u <= 1 + 0.000001 && v >= -0.000001 && v <= 1 + 0.000001 };
273
+ }
274
+ var radial = (p, c, n) => {
275
+ const d = sub3(p, c);
276
+ const k = dot3(d, n);
277
+ const r = [d[0] - n[0] * k, d[1] - n[1] * k, d[2] - n[2] * k];
278
+ return Math.hypot(r[0], r[1], r[2]) < 0.00001 ? null : unit3(r);
279
+ };
280
+ function texmapTriangleUV(map, pts) {
281
+ const { p1, p2, p3 } = map;
282
+ const out = new Array(pts.length * 2).fill(0);
283
+ if (map.method === "PLANAR") {
284
+ const e1 = sub3(p2, p1), e2 = sub3(p3, p1);
285
+ const l1 = dot3(e1, e1) || 1, l2 = dot3(e2, e2) || 1;
286
+ pts.forEach((p, i) => {
287
+ const d = sub3(p, p1);
288
+ out[i * 2] = dot3(d, e1) / l1;
289
+ out[i * 2 + 1] = dot3(d, e2) / l2;
290
+ });
291
+ return out;
292
+ }
293
+ const cyl = map.method === "CYLINDRICAL";
294
+ const n = cyl ? unit3(sub3(p2, p1)) : unit3(cross3(sub3(p1, p2), sub3(p3, p2)).map((x) => -x));
295
+ const dir = cyl ? radial(p3, p1, n) : unit3(sub3(p2, p1));
296
+ const A = (map.a ?? 360) * DEG;
297
+ let base = null, baseAng = 0;
298
+ const sAng = (q) => {
299
+ if (!base) {
300
+ base = q;
301
+ baseAng = Math.atan2(dot3(cross3(dir, q), n), dot3(q, dir));
302
+ return baseAng;
303
+ }
304
+ return Math.atan2(dot3(cross3(base, q), n), dot3(q, base)) + baseAng;
305
+ };
306
+ const miss = [];
307
+ let uSum = 0;
308
+ const h = cyl ? Math.hypot(...sub3(p2, p1)) || 1 : 0, n2 = cyl ? null : unit3(cross3(n, sub3(p2, p1))), B = (map.b ?? 180) * DEG;
309
+ pts.forEach((p, i) => {
310
+ if (cyl) {
311
+ const q = dir ? radial(p, p1, n) : null;
312
+ if (q) {
313
+ const a = sAng(q);
314
+ out[i * 2] = 0.5 + a / A;
315
+ uSum += out[i * 2];
316
+ } else
317
+ miss.push(i);
318
+ out[i * 2 + 1] = dot3(n, sub3(p2, p)) / h;
319
+ } else {
320
+ const pd = unit3(sub3(p, p1));
321
+ const c = dot3(pd, n);
322
+ if (Math.abs(c) > 1 - 0.000000001) {
323
+ miss.push(i);
324
+ out[i * 2 + 1] = 0.5 + (c > 0 ? -Math.PI / 2 : Math.PI / 2) / B;
325
+ return;
326
+ }
327
+ const a = sAng(radial(p, p1, n));
328
+ uSum += a;
329
+ out[i * 2] = 0.5 + a / A;
330
+ const th = -a, ct = Math.cos(th), st = Math.sin(th), cr = cross3(n, pd), k = dot3(n, pd) * (1 - ct);
331
+ const r = unit3([pd[0] * ct + cr[0] * st + n[0] * k, pd[1] * ct + cr[1] * st + n[1] * k, pd[2] * ct + cr[2] * st + n[2] * k]);
332
+ out[i * 2 + 1] = 0.5 + Math.atan2(dot3(cross3(dir, r), n2), dot3(r, dir)) / B;
333
+ }
334
+ });
335
+ if (miss.length && miss.length < pts.length) {
336
+ const avg = cyl ? uSum / (pts.length - miss.length) : 0.5 + uSum / (pts.length - miss.length) / A;
337
+ for (const i of miss)
338
+ out[i * 2] = avg;
339
+ }
340
+ return out;
341
+ }
342
+ function transformTexMap(map, m) {
343
+ const x = (q) => [m[3] * q[0] + m[4] * q[1] + m[5] * q[2] + m[0], m[6] * q[0] + m[7] * q[1] + m[8] * q[2] + m[1], m[9] * q[0] + m[10] * q[1] + m[11] * q[2] + m[2]];
344
+ return { ...map, p1: x(map.p1), p2: x(map.p2), p3: x(map.p3) };
345
+ }
346
+ function mul(a, b) {
347
+ const [ax, ay, az, a0, a1, a2, a3, a4, a5, a6, a7, a8] = a, [bx, by, bz, b0, b1, b2, b3, b4, b5, b6, b7, b8] = b;
348
+ return [
349
+ a0 * bx + a1 * by + a2 * bz + ax,
350
+ a3 * bx + a4 * by + a5 * bz + ay,
351
+ a6 * bx + a7 * by + a8 * bz + az,
352
+ a0 * b0 + a1 * b3 + a2 * b6,
353
+ a0 * b1 + a1 * b4 + a2 * b7,
354
+ a0 * b2 + a1 * b5 + a2 * b8,
355
+ a3 * b0 + a4 * b3 + a5 * b6,
356
+ a3 * b1 + a4 * b4 + a5 * b7,
357
+ a3 * b2 + a4 * b5 + a5 * b8,
358
+ a6 * b0 + a7 * b3 + a8 * b6,
359
+ a6 * b1 + a7 * b4 + a8 * b7,
360
+ a6 * b2 + a7 * b5 + a8 * b8
361
+ ];
362
+ }
363
+ var I = [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1];
364
+ var isDirectColor = (c) => c > 16777215;
365
+ function smoothNormals(pos, edges, cond, creaseDeg = 35) {
366
+ const q = (i, a) => `${a[i].toFixed(2)},${a[i + 1].toFixed(2)},${a[i + 2].toFixed(2)}`;
367
+ const ek = (a, b) => a < b ? a + "|" + b : b + "|" + a;
368
+ const hard = new Set, soft = new Set;
369
+ for (let i = 0;i < edges.length; i += 6)
370
+ hard.add(ek(q(i, edges), q(i + 3, edges)));
371
+ const stride = cond.length > 0 && cond.length % 12 === 0 ? 12 : 6;
372
+ for (let i = 0;i < cond.length; i += stride)
373
+ soft.add(ek(q(i, cond), q(i + 3, cond)));
374
+ const nT = pos.length / 9, fn = [], vk = [];
375
+ for (let t = 0;t < nT; t++) {
376
+ const a = t * 9;
377
+ const u = [pos[a + 3] - pos[a], pos[a + 4] - pos[a + 1], pos[a + 5] - pos[a + 2]], v = [pos[a + 6] - pos[a], pos[a + 7] - pos[a + 1], pos[a + 8] - pos[a + 2]];
378
+ const n = [u[1] * v[2] - u[2] * v[1], u[2] * v[0] - u[0] * v[2], u[0] * v[1] - u[1] * v[0]];
379
+ const l = Math.hypot(...n) || 1;
380
+ fn.push(n.map((x) => x / l));
381
+ for (let k = 0;k < 3; k++)
382
+ vk.push(q(a + k * 3, pos));
383
+ }
384
+ const byV = new Map;
385
+ vk.forEach((k, i) => (byV.get(k) ?? byV.set(k, []).get(k)).push(i));
386
+ const edgeFaces = new Map;
387
+ for (let t = 0;t < nT; t++)
388
+ for (let k = 0;k < 3; k++) {
389
+ const e = ek(vk[t * 3 + k], vk[t * 3 + (k + 1) % 3]);
390
+ (edgeFaces.get(e) ?? edgeFaces.set(e, []).get(e)).push(t);
391
+ }
392
+ const cosC = Math.cos(creaseDeg * Math.PI / 180), out = new Float32Array(pos.length);
393
+ const joinable = (e, a, b) => {
394
+ if (hard.has(e))
395
+ return false;
396
+ const d = fn[a][0] * fn[b][0] + fn[a][1] * fn[b][1] + fn[a][2] * fn[b][2];
397
+ return soft.has(e) ? d > -0.2 : d > cosC;
398
+ };
399
+ for (const [key, corners] of byV) {
400
+ const faces = corners.map((c) => c / 3 | 0), par = faces.map((_, i) => i);
401
+ const f = (i) => par[i] === i ? i : par[i] = f(par[i]);
402
+ for (let i = 0;i < faces.length; i++)
403
+ for (let j = i + 1;j < faces.length; j++) {
404
+ const A = faces[i], B = faces[j];
405
+ const va = [0, 1, 2].map((k) => vk[A * 3 + k]), vb = new Set([0, 1, 2].map((k) => vk[B * 3 + k]));
406
+ for (const o of va)
407
+ if (o !== key && vb.has(o)) {
408
+ const e = ek(key, o);
409
+ if ((edgeFaces.get(e)?.length ?? 0) >= 2 && joinable(e, A, B))
410
+ par[f(i)] = f(j);
411
+ }
412
+ }
413
+ const acc = new Map;
414
+ faces.forEach((F, i) => {
415
+ const r = f(i), s = acc.get(r) ?? [0, 0, 0];
416
+ s[0] += fn[F][0];
417
+ s[1] += fn[F][1];
418
+ s[2] += fn[F][2];
419
+ acc.set(r, s);
420
+ });
421
+ corners.forEach((c, i) => {
422
+ const s = acc.get(f(i)), l = Math.hypot(...s) || 1;
423
+ out[c * 3] = s[0] / l;
424
+ out[c * 3 + 1] = s[1] / l;
425
+ out[c * 3 + 2] = s[2] / l;
426
+ });
427
+ }
428
+ return out;
429
+ }
430
+ // src/model.ts
431
+ function flatten(doc, opts = {}) {
432
+ const out = [];
433
+ const maxDepth = opts.maxDepth ?? 64;
434
+ const walk = (d, fname, m, color, path, stepPath, depth) => {
435
+ const f = d.files.get(fname);
436
+ if (!f || depth > maxDepth)
437
+ return;
438
+ let s = 0;
439
+ for (const c of f.cmds) {
440
+ if (c.t === 0 && c.step)
441
+ s++;
442
+ if (c.t !== 1)
443
+ continue;
444
+ const col = c.color === 16 ? color : c.color, mm = mul(m, c.m), sp = [...stepPath, s];
445
+ if (d.files.has(c.file)) {
446
+ walk(d, c.file, mm, col, path + "/" + c.file, sp, depth + 1);
447
+ continue;
448
+ }
449
+ const ext = opts.resolveModel?.(c.file);
450
+ if (ext) {
451
+ walk(ext, ext.main, mm, col, path + "/" + c.file, sp, depth + 1);
452
+ continue;
453
+ }
454
+ out.push({ part: c.file, color: col, m: mm, step: sp[0], steps: sp, path });
455
+ }
456
+ };
457
+ walk(doc, doc.main, I, 16, doc.main, [], 0);
458
+ return out;
459
+ }
460
+ function parseModel(text, name = "main.ldr", opts) {
461
+ return flatten(parse(text, name), opts);
462
+ }
463
+ function sequenceSteps(insts) {
464
+ const key = (i) => i.steps.map((x) => String(x).padStart(6, "0")).join(".");
465
+ const uniq = [...new Set(insts.map(key))].sort();
466
+ const rank = new Map(uniq.map((k, j) => [k, j]));
467
+ const stepOf = new Int32Array(insts.length);
468
+ insts.forEach((i, k) => stepOf[k] = rank.get(key(i)));
469
+ return { count: uniq.length, stepOf };
470
+ }
471
+ function parseLDConfig(text) {
472
+ const out = new Map;
473
+ for (const l of text.split(/\r?\n/)) {
474
+ const m = /!COLOUR\s+(\S+)\s+CODE\s+(\d+)\s+VALUE\s+(#[0-9A-Fa-f]{6})\s+EDGE\s+(\S+)(?:.*?ALPHA\s+(\d+))?/.exec(l);
475
+ if (m)
476
+ out.set(+m[2], { code: +m[2], name: m[1], hex: m[3].toUpperCase(), edge: m[4], alpha: m[5] ? +m[5] : 255 });
477
+ }
478
+ return out;
479
+ }
480
+ // src/render.ts
481
+ import * as THREE from "three";
482
+ async function loadPack(url) {
483
+ const res = await fetch(url);
484
+ const t0 = performance.now();
485
+ let buf;
486
+ if (url.endsWith(".gz"))
487
+ buf = await new Response(res.body.pipeThrough(new DecompressionStream("gzip"))).arrayBuffer();
488
+ else if (url.endsWith(".br"))
489
+ buf = await new Response(res.body.pipeThrough(new DecompressionStream("brotli"))).arrayBuffer();
490
+ else if (url.endsWith(".zst")) {
491
+ const raw = new Uint8Array(await res.arrayBuffer());
492
+ const t1 = performance.now();
493
+ const { decompress } = await import("fzstd");
494
+ const o = decompress(raw);
495
+ buf = o.buffer.slice(o.byteOffset, o.byteOffset + o.byteLength);
496
+ globalThis.__decodeMs = performance.now() - t1;
497
+ } else
498
+ buf = await res.arrayBuffer();
499
+ if (!url.endsWith(".zst"))
500
+ globalThis.__decodeMs = performance.now() - t0;
501
+ return decodePack(buf);
502
+ }
503
+ function decodePack(buf) {
504
+ const hl = new DataView(buf).getUint32(0, true);
505
+ const header = JSON.parse(new TextDecoder().decode(new Uint8Array(buf, 4, hl)));
506
+ const base = 4 + hl;
507
+ const arr = (r) => r && (r[2] === "f32" ? new Float32Array(buf, base + r[0], r[1]) : r[2] === "u16" ? new Uint16Array(buf, base + r[0], r[1]) : r[2] === "u8" ? new Uint8Array(buf, base + r[0], r[1]) : new Uint32Array(buf, base + r[0], r[1]));
508
+ return { header, arr, bytes: buf.byteLength };
509
+ }
510
+ function geom(b, arr) {
511
+ if (!b)
512
+ return null;
513
+ const g = new THREE.BufferGeometry;
514
+ g.setAttribute("position", new THREE.BufferAttribute(arr(b.pos), 3));
515
+ g.setAttribute("normal", new THREE.BufferAttribute(arr(b.nrm), 3));
516
+ g.setAttribute("ldrColor", new THREE.BufferAttribute(arr(b.col), 4));
517
+ if (b.idx)
518
+ g.setIndex(new THREE.BufferAttribute(arr(b.idx), 1));
519
+ if (b.uv)
520
+ g.setAttribute("ldrUV", new THREE.BufferAttribute(arr(b.uv), 2));
521
+ return g;
522
+ }
523
+ function makeMaterial(side) {
524
+ const m = new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 0.45, side });
525
+ m.onBeforeCompile = (s) => {
526
+ s.vertexShader = `attribute vec4 ldrColor;
527
+ ` + s.vertexShader.replace("#include <color_vertex>", `#ifdef USE_INSTANCING_COLOR
528
+ vColor = vec4(ldrColor.a > 0.5 ? pow(ldrColor.rgb, vec3(2.2)) : instanceColor.rgb, 1.0); // baked LDConfig colours are sRGB
529
+ #else
530
+ vColor = vec4(pow(ldrColor.rgb, vec3(2.2)), 1.0);
531
+ #endif`);
532
+ };
533
+ m.customProgramCacheKey = () => "ldrColor";
534
+ return m;
535
+ }
536
+ function makeTexturedMaterial(side, map, wrapU = false) {
537
+ const m = makeMaterial(side);
538
+ const base = m.onBeforeCompile;
539
+ m.onBeforeCompile = (sh, r) => {
540
+ base.call(m, sh, r);
541
+ sh.uniforms.ldrMap = { value: map };
542
+ sh.uniforms.ldrWrapU = { value: wrapU ? 1 : 0 };
543
+ sh.vertexShader = `attribute vec2 ldrUV; varying vec2 vLdrUV;
544
+ ` + sh.vertexShader.replace("#include <uv_vertex>", `#include <uv_vertex>
545
+ vLdrUV = ldrUV;`);
546
+ sh.fragmentShader = `uniform sampler2D ldrMap; uniform int ldrWrapU; varying vec2 vLdrUV;
547
+ ` + sh.fragmentShader.replace("#include <color_fragment>", `#include <color_fragment>
548
+ { vec2 uv = vec2(ldrWrapU == 1 ? fract(vLdrUV.x) : vLdrUV.x, vLdrUV.y); vec4 t = texture2D(ldrMap, uv);
549
+ float inside = step(0.0, uv.x) * step(uv.x, 1.0) * step(0.0, uv.y) * step(uv.y, 1.0);
550
+ diffuseColor.rgb = mix(diffuseColor.rgb, t.rgb, t.a * inside); }`);
551
+ };
552
+ m.customProgramCacheKey = () => "ldrColorTex";
553
+ m.userData.ldrMap = map;
554
+ return m;
555
+ }
556
+ function packTexture(pack, i) {
557
+ const t = new THREE.Texture;
558
+ t.flipY = false;
559
+ t.colorSpace = THREE.SRGBColorSpace;
560
+ t.wrapS = t.wrapT = THREE.ClampToEdgeWrapping;
561
+ const e = pack.header.textures?.[i];
562
+ const bytes = e?.data ? pack.arr(e.data) : null;
563
+ if (!bytes || typeof createImageBitmap === "undefined")
564
+ return { texture: t, ready: Promise.resolve() };
565
+ const ready = createImageBitmap(new Blob([bytes], { type: e.mime || "image/png" }), { premultiplyAlpha: "none", colorSpaceConversion: "none" }).then((img) => {
566
+ t.image = img;
567
+ t.needsUpdate = true;
568
+ }).catch(() => {});
569
+ return { texture: t, ready };
570
+ }
571
+ var lineMat = () => new THREE.ShaderMaterial({
572
+ vertexShader: `attribute vec4 im0; attribute vec4 im1; attribute vec4 im2; attribute vec4 im3;
573
+ void main(){ gl_Position = projectionMatrix * modelViewMatrix * mat4(im0,im1,im2,im3) * vec4(position,1.0); }`,
574
+ fragmentShader: `void main(){ gl_FragColor = vec4(0.13,0.13,0.13,1.0); }`
575
+ });
576
+ var sharedLineMat = () => new THREE.ShaderMaterial({
577
+ vertexShader: `attribute mat4 instanceMatrix; void main(){ gl_Position = projectionMatrix * modelViewMatrix * instanceMatrix * vec4(position,1.0); }`,
578
+ fragmentShader: `void main(){ gl_FragColor = vec4(0.13,0.13,0.13,1.0); }`
579
+ });
580
+ var weldLines = (pos) => {
581
+ const map = new Map;
582
+ const P = [];
583
+ const I = [];
584
+ for (let i = 0;i < pos.length; i += 3) {
585
+ const k = pos[i].toFixed(2) + "," + pos[i + 1].toFixed(2) + "," + pos[i + 2].toFixed(2);
586
+ let j = map.get(k);
587
+ if (j === undefined) {
588
+ j = P.length / 3;
589
+ map.set(k, j);
590
+ P.push(pos[i], pos[i + 1], pos[i + 2]);
591
+ }
592
+ I.push(j);
593
+ }
594
+ return { P: new Float32Array(P), I: P.length / 3 < 65536 ? new Uint16Array(I) : new Uint32Array(I) };
595
+ };
596
+ function createInstancedEdges(pos, im, material = sharedLineMat()) {
597
+ return sharedLines(pos, im, material);
598
+ }
599
+ function conditionalLines(pack, entry, which = "all") {
600
+ if (which === "all") {
601
+ const a = conditionalLines(pack, entry, "body"), b = conditionalLines(pack, entry, "under");
602
+ if (!a || !b)
603
+ return a ?? b;
604
+ const o = new Float32Array(a.length + b.length);
605
+ o.set(a);
606
+ o.set(b, a.length);
607
+ return o;
608
+ }
609
+ const c = which === "under" ? entry?.condlu : entry?.condl;
610
+ if (!c)
611
+ return null;
612
+ const { arr } = pack;
613
+ if (c.raw)
614
+ return arr(c.raw);
615
+ const pools = [entry.body ? arr(entry.body.pos) : [], entry.under ? arr(entry.under.pos) : [], entry.edges ? arr(entry.edges) : [], c.x ? arr(c.x) : []];
616
+ const idx = arr(c.idx);
617
+ const out = new Float32Array(idx.length * 3);
618
+ const starts = [];
619
+ let base = 0;
620
+ for (const p of pools) {
621
+ starts.push(base);
622
+ base += p.length / 3;
623
+ }
624
+ let prev = 0;
625
+ for (let i = 0;i < idx.length; i++) {
626
+ const z = idx[i];
627
+ const j = prev + (z & 1 ? -(z + 1) / 2 : z / 2);
628
+ prev = j;
629
+ let k = 3;
630
+ while (k > 0 && j < starts[k])
631
+ k--;
632
+ const p = pools[k], o = (j - starts[k]) * 3;
633
+ out[i * 3] = p[o];
634
+ out[i * 3 + 1] = p[o + 1];
635
+ out[i * 3 + 2] = p[o + 2];
636
+ }
637
+ return out;
638
+ }
639
+ var condLineMat = () => new THREE.ShaderMaterial({
640
+ uniforms: { ldrViewProj: { value: new THREE.Matrix4 } },
641
+ vertexShader: `uniform mat4 ldrViewProj; attribute mat4 instanceMatrix; attribute vec3 ldrP1; attribute vec3 ldrC0; attribute vec3 ldrC1; attribute float ldrSide;
642
+ void main(){
643
+ mat4 mvp = ldrViewProj * instanceMatrix;
644
+ vec4 a = mvp * vec4(position, 1.0), b = mvp * vec4(ldrP1, 1.0), c0 = mvp * vec4(ldrC0, 1.0), c1 = mvp * vec4(ldrC1, 1.0);
645
+ vec2 A = a.xy / a.w, B = b.xy / b.w, C0 = c0.xy / c0.w, C1 = c1.xy / c1.w, d = B - A;
646
+ float s0 = d.x * (C0.y - A.y) - d.y * (C0.x - A.x), s1 = d.x * (C1.y - A.y) - d.y * (C1.x - A.x);
647
+ gl_Position = s0 * s1 >= 0.0 ? (ldrSide > 0.5 ? b : a) : vec4(2.0, 2.0, 2.0, 1.0);
648
+ }`,
649
+ fragmentShader: `void main(){ gl_FragColor = vec4(0.13,0.13,0.13,1.0); }`
650
+ });
651
+ function conditionalLineVisible(cond, i, mvp) {
652
+ const e = mvp.elements;
653
+ const pr = (o) => {
654
+ const x = cond[o], y = cond[o + 1], z = cond[o + 2];
655
+ const w = e[3] * x + e[7] * y + e[11] * z + e[15];
656
+ return [(e[0] * x + e[4] * y + e[8] * z + e[12]) / w, (e[1] * x + e[5] * y + e[9] * z + e[13]) / w];
657
+ };
658
+ const o = i * 12;
659
+ const A = pr(o), B = pr(o + 3), C0 = pr(o + 6), C1 = pr(o + 9);
660
+ const dx = B[0] - A[0], dy = B[1] - A[1];
661
+ const s0 = dx * (C0[1] - A[1]) - dy * (C0[0] - A[0]), s1 = dx * (C1[1] - A[1]) - dy * (C1[0] - A[0]);
662
+ return s0 * s1 >= 0;
663
+ }
664
+ function createInstancedConditionalEdges(cond, im, material = condLineMat()) {
665
+ return condEdges(condBuffers(cond), im, material);
666
+ }
667
+ function condBuffers(cond) {
668
+ const n = Math.floor(cond.length / 12);
669
+ const buf = new Float32Array(n * 24), side = new Float32Array(n * 2);
670
+ for (let l = 0;l < n; l++) {
671
+ for (let k = 0;k < 12; k++) {
672
+ const v = cond[l * 12 + k];
673
+ buf[l * 24 + k] = v;
674
+ buf[l * 24 + 12 + k] = v;
675
+ }
676
+ side[l * 2 + 1] = 1;
677
+ }
678
+ return { ib: new THREE.InterleavedBuffer(buf, 12), side: new THREE.BufferAttribute(side, 1) };
679
+ }
680
+ function condEdges({ ib, side }, im, material) {
681
+ const g = new THREE.InstancedBufferGeometry;
682
+ g.setAttribute("position", new THREE.InterleavedBufferAttribute(ib, 3, 0));
683
+ g.setAttribute("ldrP1", new THREE.InterleavedBufferAttribute(ib, 3, 3));
684
+ g.setAttribute("ldrC0", new THREE.InterleavedBufferAttribute(ib, 3, 6));
685
+ g.setAttribute("ldrC1", new THREE.InterleavedBufferAttribute(ib, 3, 9));
686
+ g.setAttribute("ldrSide", side);
687
+ g.setAttribute("instanceMatrix", im.instanceMatrix);
688
+ g.instanceCount = im.count;
689
+ const l = new THREE.LineSegments(g, material);
690
+ l.frustumCulled = false;
691
+ l.name = "ldrCondLines";
692
+ l.onBeforeRender = (_r, _s, cam, _g, mat) => {
693
+ const u = mat.uniforms?.ldrViewProj;
694
+ if (!u)
695
+ return;
696
+ l.modelViewMatrix.multiplyMatrices(cam.matrixWorldInverse, l.matrixWorld);
697
+ u.value.multiplyMatrices(cam.projectionMatrix, l.modelViewMatrix);
698
+ mat.uniformsNeedUpdate = true;
699
+ };
700
+ return l;
701
+ }
702
+ function sharedLines(pos, im, material = sharedLineMat()) {
703
+ const { P, I } = weldLines(pos);
704
+ const g = new THREE.InstancedBufferGeometry;
705
+ g.setAttribute("position", new THREE.BufferAttribute(P, 3));
706
+ g.setIndex(new THREE.BufferAttribute(I, 1));
707
+ g.setAttribute("instanceMatrix", im.instanceMatrix);
708
+ g.instanceCount = im.count;
709
+ const l = new THREE.LineSegments(g, material);
710
+ l.frustumCulled = false;
711
+ return l;
712
+ }
713
+ function instancedLines(pos, mats, count) {
714
+ const g = new THREE.InstancedBufferGeometry;
715
+ g.setAttribute("position", new THREE.BufferAttribute(pos, 3));
716
+ const ib = new THREE.InstancedInterleavedBuffer(mats, 16);
717
+ for (let i = 0;i < 4; i++)
718
+ g.setAttribute("im" + i, new THREE.InterleavedBufferAttribute(ib, 4, i * 4));
719
+ g.instanceCount = count;
720
+ const l = new THREE.LineSegments(g, lineMat());
721
+ l.frustumCulled = false;
722
+ return l;
723
+ }
724
+ var toM4 = (m, o = 0) => new THREE.Matrix4().set(m[o + 3], m[o + 4], m[o + 5], m[o], m[o + 6], m[o + 7], m[o + 8], m[o + 1], m[o + 9], m[o + 10], m[o + 11], m[o + 2], 0, 0, 0, 1);
725
+ var mul12 = (a, b) => {
726
+ const r = (i, j) => a[3 + i * 3] * b[3 + j] + a[4 + i * 3] * b[6 + j] + a[5 + i * 3] * b[9 + j];
727
+ return [a[0] + a[3] * b[0] + a[4] * b[1] + a[5] * b[2], a[1] + a[6] * b[0] + a[7] * b[1] + a[8] * b[2], a[2] + a[9] * b[0] + a[10] * b[1] + a[11] * b[2], r(0, 0), r(0, 1), r(0, 2), r(1, 0), r(1, 1), r(1, 2), r(2, 0), r(2, 1), r(2, 2)];
728
+ };
729
+ function computeCulling(insts, H) {
730
+ const upright = (m) => Math.abs(m[7] - 1) < 0.001 && Math.abs(m[4]) < 0.001 && Math.abs(m[10]) < 0.001;
731
+ const CELL = 40;
732
+ const rects = new Map;
733
+ const lk = (y) => Math.round(y * 2) / 2;
734
+ insts.forEach((it, idx) => {
735
+ const p = H.parts[it.part];
736
+ if (!p || !p.mask || !upright(it.m))
737
+ return;
738
+ const b = p.bbox;
739
+ const xs = [], zs = [];
740
+ for (const x of [b[0], b[3]])
741
+ for (const z of [b[2], b[5]]) {
742
+ const w = [it.m[0] + it.m[3] * x + it.m[5] * z, it.m[2] + it.m[9] * x + it.m[11] * z];
743
+ xs.push(w[0]);
744
+ zs.push(w[1]);
745
+ }
746
+ const r = [Math.min(...xs), Math.max(...xs), Math.min(...zs), Math.max(...zs), idx];
747
+ const lv = lk(it.m[1] + b[4]);
748
+ for (let cx = Math.floor(r[0] / CELL);cx <= Math.floor(r[1] / CELL); cx++)
749
+ for (let cz = Math.floor(r[2] / CELL);cz <= Math.floor(r[3] / CELL); cz++) {
750
+ const k = lv + "|" + cx + "|" + cz;
751
+ (rects.get(k) || rects.set(k, []).get(k)).push(r);
752
+ }
753
+ });
754
+ const covered = (x, y, z, self) => {
755
+ const list = rects.get(lk(y) + "|" + Math.floor(x / CELL) + "|" + Math.floor(z / CELL));
756
+ if (!list)
757
+ return false;
758
+ for (const r of list) {
759
+ if (r[4] === self || !(x - 6 >= r[0] - 0.5 && x + 6 <= r[1] + 0.5 && z - 6 >= r[2] - 0.5 && z + 6 <= r[3] + 0.5))
760
+ continue;
761
+ const it = insts[r[4]];
762
+ const p = H.parts[it.part];
763
+ const m = it.m;
764
+ const dx = x - m[0], dy = y - m[1], dz = z - m[2];
765
+ const lx = m[3] * dx + m[6] * dy + m[9] * dz, lz = m[5] * dx + m[8] * dy + m[11] * dz;
766
+ const [nx, nz, bits] = p.mask;
767
+ let ok = true;
768
+ for (const [ox, oz] of [[0, 0], [5, 5], [-5, 5], [5, -5], [-5, -5]]) {
769
+ const ca = Math.floor((lx + ox - p.bbox[0]) / 20), cb = Math.floor((lz + oz - p.bbox[2]) / 20);
770
+ if (ca < 0 || cb < 0 || ca >= nx || cb >= nz || bits[ca * nz + cb] !== "1") {
771
+ ok = false;
772
+ break;
773
+ }
774
+ }
775
+ if (ok)
776
+ return true;
777
+ }
778
+ return false;
779
+ };
780
+ const studs = [];
781
+ const studTops = new Set;
782
+ const key3 = (x, y, z) => `${Math.round(x)},${Math.round(y)},${Math.round(z)}`;
783
+ insts.forEach((it, idx) => {
784
+ const p = H.parts[it.part];
785
+ if (!p)
786
+ return;
787
+ for (const s of p.studs) {
788
+ const w = mul12(it.m, s.slice(2));
789
+ const color = s[1] === "16" ? it.color : s[1];
790
+ const up = upright(w);
791
+ studs.push({ kind: s[0], color, w, hidden: up && covered(w[0], w[1], w[2], idx) });
792
+ if (up)
793
+ studTops.add(key3(w[0], w[1], w[2]));
794
+ }
795
+ });
796
+ const underHidden = insts.map((it) => {
797
+ const p = H.parts[it.part];
798
+ if (!p || !p.under || !upright(it.m))
799
+ return false;
800
+ const b = p.bbox;
801
+ const nx = Math.round((b[3] - b[0]) / 20), nz = Math.round((b[5] - b[2]) / 20);
802
+ if (!nx || !nz || nx * nz > 4096)
803
+ return false;
804
+ for (let a = 0;a < nx; a++)
805
+ for (let c = 0;c < nz; c++) {
806
+ const lx = b[0] + 10 + 20 * a, lz = b[2] + 10 + 20 * c;
807
+ const x = it.m[0] + it.m[3] * lx + it.m[5] * lz, z = it.m[2] + it.m[9] * lx + it.m[11] * lz;
808
+ if (!studTops.has(key3(x, it.m[1] + b[4], z)))
809
+ return false;
810
+ }
811
+ return true;
812
+ });
813
+ return { studs, underHidden };
814
+ }
815
+ function buildLdrScene(insts, pack, P, color, mark) {
816
+ const { header: H, arr } = pack;
817
+ const root = new THREE.Group;
818
+ const matF = makeMaterial(P.side === "front" ? THREE.FrontSide : THREE.DoubleSide), matB = makeMaterial(P.side === "front" ? THREE.BackSide : THREE.DoubleSide);
819
+ const matD = makeMaterial(THREE.DoubleSide);
820
+ let nUncert = 0;
821
+ const det = (m) => m[3] * (m[7] * m[11] - m[8] * m[10]) - m[4] * (m[6] * m[11] - m[8] * m[9]) + m[5] * (m[6] * m[10] - m[7] * m[9]);
822
+ const byPart = new Map;
823
+ insts.forEach((it, i) => {
824
+ if (H.parts[it.part]) {
825
+ const k = it.part + (det(it.m) < 0 ? "#m" : "");
826
+ (byPart.get(k) || byPart.set(k, []).get(k)).push(i);
827
+ }
828
+ });
829
+ const cull = computeCulling(insts, H);
830
+ mark("culling");
831
+ const edgeObjs = [];
832
+ const condObjs = [];
833
+ const wantCond = P.cond !== false && P.edges === "idx";
834
+ let condMat = null;
835
+ const cmat = () => condMat ??= condLineMat();
836
+ const condCache = new Map;
837
+ const addCond = (entry, im, which = "body") => {
838
+ if (!wantCond)
839
+ return null;
840
+ const ck = which === "under" ? entry.condlu : entry.condl;
841
+ if (!ck)
842
+ return null;
843
+ let cb = condCache.get(ck);
844
+ if (cb === undefined) {
845
+ const c = conditionalLines(pack, entry, which);
846
+ cb = c && c.length ? condBuffers(c) : null;
847
+ condCache.set(ck, cb);
848
+ }
849
+ if (!cb)
850
+ return null;
851
+ const l = condEdges(cb, im, cmat());
852
+ edgeObjs.push(l);
853
+ condObjs.push(l);
854
+ root.add(l);
855
+ return l;
856
+ };
857
+ const stats = { studsTotal: cull.studs.length, studsHidden: cull.studs.filter((s) => s.hidden).length, undersTotal: 0, undersHidden: 0 };
858
+ const toggles = [];
859
+ const studMeshes = [];
860
+ const wantTex = P.tex !== false && !!H.textures;
861
+ const texCache = new Map;
862
+ const texMats = new Map;
863
+ const pending = [];
864
+ const texMat = (t, base, wrapU) => {
865
+ const role = base === matF ? "F" : base === matB ? "B" : "D";
866
+ const k = t + ":" + role + ":" + (wrapU ? 1 : 0);
867
+ let m = texMats.get(k);
868
+ if (!m) {
869
+ let tx = texCache.get(t);
870
+ if (!tx) {
871
+ const r = packTexture(pack, t);
872
+ tx = r.texture;
873
+ pending.push(r.ready);
874
+ texCache.set(t, tx);
875
+ }
876
+ texMats.set(k, m = makeTexturedMaterial(base.side, tx, wrapU));
877
+ }
878
+ return m;
879
+ };
880
+ let texBatches = 0;
881
+ for (const [pk, ids] of byPart) {
882
+ const part = pk.replace(/#m$/, "");
883
+ const p = H.parts[part];
884
+ const mat = P.uncertDouble !== false && p.uncert ? (nUncert++, matD) : pk.endsWith("#m") ? matB : matF;
885
+ const textured = wantTex && !!p.tex;
886
+ const body = geom(textured ? p.tbody : p.body, arr);
887
+ let bodyIm = null;
888
+ const place = (im) => {
889
+ ids.forEach((id, k) => {
890
+ im.setMatrixAt(k, toM4(insts[id].m));
891
+ im.setColorAt(k, color(insts[id].color));
892
+ });
893
+ im.computeBoundingSphere();
894
+ root.add(im);
895
+ };
896
+ if (body) {
897
+ const im = bodyIm = new THREE.InstancedMesh(body, mat, ids.length);
898
+ place(im);
899
+ }
900
+ if (textured)
901
+ for (const tb of p.tex) {
902
+ const g = geom(tb, arr);
903
+ const im = new THREE.InstancedMesh(g, texMat(tb.t, mat, !!tb.wrapU), ids.length);
904
+ texBatches++;
905
+ if (bodyIm) {
906
+ im.instanceMatrix = bodyIm.instanceMatrix;
907
+ im.instanceColor = bodyIm.instanceColor;
908
+ im.boundingSphere = bodyIm.boundingSphere;
909
+ root.add(im);
910
+ } else {
911
+ place(im);
912
+ bodyIm = im;
913
+ }
914
+ }
915
+ if (P.edges === "idx" && p.edges && bodyIm) {
916
+ const l = sharedLines(arr(p.edges), bodyIm);
917
+ edgeObjs.push(l);
918
+ root.add(l);
919
+ }
920
+ if (bodyIm)
921
+ addCond(p, bodyIm);
922
+ const under = geom(p.under, arr);
923
+ if (under) {
924
+ const ord = [...ids.filter((i) => !cull.underHidden[i]), ...ids.filter((i) => cull.underHidden[i])];
925
+ const vis = ids.filter((i) => !cull.underHidden[i]).length;
926
+ stats.undersTotal += ids.length;
927
+ stats.undersHidden += ids.length - vis;
928
+ const im = new THREE.InstancedMesh(under, mat, ids.length);
929
+ ord.forEach((id, k) => {
930
+ im.setMatrixAt(k, toM4(insts[id].m));
931
+ im.setColorAt(k, color(insts[id].color));
932
+ });
933
+ im.computeBoundingSphere();
934
+ im.count = P.cullUnder ? vis : ids.length;
935
+ root.add(im);
936
+ toggles.push({ mesh: im, all: ids.length, visible: vis, flag: "under" });
937
+ {
938
+ const cl = addCond(p, im, "under");
939
+ if (cl) {
940
+ toggles.push({ mesh: { set count(n) {
941
+ cl.geometry.instanceCount = n;
942
+ } }, all: ids.length, visible: vis, flag: "under" });
943
+ cl.geometry.instanceCount = im.count;
944
+ }
945
+ }
946
+ }
947
+ if (P.edges === true && p.edges) {
948
+ const mats = new Float32Array(ids.length * 16);
949
+ ids.forEach((id, k) => toM4(insts[id].m).toArray(mats, k * 16));
950
+ {
951
+ const l = instancedLines(arr(p.edges), mats, ids.length);
952
+ edgeObjs.push(l);
953
+ root.add(l);
954
+ }
955
+ }
956
+ }
957
+ const byKind = new Map;
958
+ for (const s of cull.studs) {
959
+ const k = s.kind + (det(s.w) < 0 ? "#m" : "");
960
+ (byKind.get(k) || byKind.set(k, []).get(k)).push(s);
961
+ }
962
+ for (const [kk, list] of byKind) {
963
+ const kind = kk.replace(/#m$/, "");
964
+ const mat = kk.endsWith("#m") ? matB : matF;
965
+ const k = H.studKinds[kind];
966
+ const g = geom(k.body, arr);
967
+ if (!g)
968
+ continue;
969
+ const ord = [...list.filter((s) => !s.hidden), ...list.filter((s) => s.hidden)];
970
+ const vis = list.filter((s) => !s.hidden).length;
971
+ const im = new THREE.InstancedMesh(g, mat, ord.length);
972
+ ord.forEach((s, i) => {
973
+ im.setMatrixAt(i, toM4(s.w));
974
+ im.setColorAt(i, color(s.color));
975
+ });
976
+ im.computeBoundingSphere();
977
+ im.count = P.cullStuds ? vis : ord.length;
978
+ root.add(im);
979
+ toggles.push({ mesh: im, all: ord.length, visible: vis, flag: "studs" });
980
+ studMeshes.push({ im, kind });
981
+ if (P.edges === "idx" && k.edges) {
982
+ const l = sharedLines(arr(k.edges), im);
983
+ edgeObjs.push(l);
984
+ root.add(l);
985
+ toggles.push({ mesh: { set count(n) {
986
+ l.geometry.instanceCount = n;
987
+ } }, all: ord.length, visible: vis, flag: "studs" });
988
+ l.geometry.instanceCount = im.count;
989
+ }
990
+ {
991
+ const cl = P.condStuds === false ? null : addCond(k, im);
992
+ if (cl) {
993
+ toggles.push({ mesh: { set count(n) {
994
+ cl.geometry.instanceCount = n;
995
+ } }, all: ord.length, visible: vis, flag: "studs" });
996
+ cl.geometry.instanceCount = im.count;
997
+ }
998
+ }
999
+ if (P.edges === true && k.edges) {
1000
+ const mats = new Float32Array(ord.length * 16);
1001
+ ord.forEach((s, i) => toM4(s.w).toArray(mats, i * 16));
1002
+ const l = instancedLines(arr(k.edges), mats, P.cullStuds ? vis : ord.length);
1003
+ edgeObjs.push(l);
1004
+ root.add(l);
1005
+ toggles.push({ mesh: { set count(n) {
1006
+ l.geometry.instanceCount = n;
1007
+ } }, all: ord.length, visible: vis, flag: "studs" });
1008
+ }
1009
+ }
1010
+ stats.uncertParts = Object.values(H.parts).filter((x) => x.uncert).length;
1011
+ stats.uncertBatches = nUncert;
1012
+ let condOn = true, edgesOn = true;
1013
+ const setEdges = (v) => {
1014
+ edgesOn = v;
1015
+ edgeObjs.forEach((o) => o.visible = v && (condOn || !condObjs.includes(o)));
1016
+ };
1017
+ const setCond = (v) => {
1018
+ condOn = v;
1019
+ condObjs.forEach((o) => o.visible = v && edgesOn);
1020
+ };
1021
+ stats.condBatches = condObjs.length;
1022
+ stats.texBatches = texBatches;
1023
+ stats.textures = texCache.size;
1024
+ const texturesReady = Promise.all(pending).then(() => {});
1025
+ const setCull = (studs, under) => {
1026
+ for (const t of toggles)
1027
+ t.mesh.count = (t.flag === "studs" ? studs : under) ? t.visible : t.all;
1028
+ };
1029
+ const box = new THREE.Box3;
1030
+ for (const it of insts) {
1031
+ const p = H.parts[it.part];
1032
+ if (!p)
1033
+ continue;
1034
+ box.expandByPoint(new THREE.Vector3(it.m[0], it.m[1], it.m[2]));
1035
+ }
1036
+ const setSide = (front) => {
1037
+ matF.side = front ? THREE.FrontSide : THREE.DoubleSide;
1038
+ matB.side = front ? THREE.BackSide : THREE.DoubleSide;
1039
+ matF.needsUpdate = matB.needsUpdate = true;
1040
+ for (const [k, m] of texMats) {
1041
+ const role = k.split(":")[1];
1042
+ m.side = role === "D" || !front ? THREE.DoubleSide : role === "B" ? THREE.BackSide : THREE.FrontSide;
1043
+ m.needsUpdate = true;
1044
+ }
1045
+ };
1046
+ const swapStuds = (alt) => {
1047
+ const saved = studMeshes.map((s) => s.im.geometry);
1048
+ for (const s of studMeshes) {
1049
+ const k2 = alt.header.studKinds[s.kind] ? s.kind : Object.keys(alt.header.studKinds).find((k) => k.replace("p/8/", "p/") === s.kind.replace("p/8/", "p/"));
1050
+ if (k2)
1051
+ s.im.geometry = geom(alt.header.studKinds[k2].body, alt.arr);
1052
+ }
1053
+ return () => studMeshes.forEach((s, i) => s.im.geometry = saved[i]);
1054
+ };
1055
+ return { root, stats, setCull, setSide, setEdges, setCond, swapStuds, box, texturesReady };
1056
+ }
1057
+ // src/scene.ts
1058
+ import * as THREE2 from "three";
1059
+ function colorFn(src, pack, fallback) {
1060
+ const table = new Map;
1061
+ const put = (k, v) => table.set(String(k), new THREE2.Color(typeof v === "string" ? v : v.hex));
1062
+ if (typeof src === "function") {
1063
+ return (c) => {
1064
+ let col = table.get(c);
1065
+ if (!col) {
1066
+ const v = src(+c);
1067
+ col = v === undefined ? direct(c) ?? fallback : new THREE2.Color(v);
1068
+ table.set(c, col);
1069
+ }
1070
+ return col;
1071
+ };
1072
+ }
1073
+ const base = src ?? pack.header.colors ?? {};
1074
+ if (base instanceof Map)
1075
+ for (const [k, v] of base)
1076
+ put(k, v);
1077
+ else
1078
+ for (const k in base)
1079
+ put(k, base[k]);
1080
+ return (c) => table.get(c) ?? direct(c) ?? fallback;
1081
+ }
1082
+ var direct = (c) => {
1083
+ const n = +c;
1084
+ return n > 16777215 && n >>> 24 === 2 ? new THREE2.Color(n & 16777215) : null;
1085
+ };
1086
+ var plasticCache = new WeakMap;
1087
+ function toPlastic(old) {
1088
+ let m = plasticCache.get(old);
1089
+ if (!m) {
1090
+ const p = new THREE2.MeshPhysicalMaterial({ vertexColors: true, roughness: 0.3, clearcoat: 0.3, clearcoatRoughness: 0.2, specularIntensity: 0.6, side: old.side });
1091
+ p.onBeforeCompile = old.onBeforeCompile;
1092
+ p.customProgramCacheKey = () => "Phys" + old.customProgramCacheKey() + old.side;
1093
+ p.userData = old.userData;
1094
+ plasticCache.set(old, m = p);
1095
+ }
1096
+ return m;
1097
+ }
1098
+ function createLdrObject(instances, pack, options = {}) {
1099
+ const o = { edges: true, conditionalLines: true, cullStuds: true, cullUndersides: true, side: "front", material: "plastic", ...options };
1100
+ const color = colorFn(o.colors, pack, new THREE2.Color(o.fallbackColor ?? 10528169));
1101
+ const all = instances.map((i) => ({ part: i.part, color: String(i.color), m: i.m, steps: i.steps }));
1102
+ const key = (i) => i.steps.map((x) => String(x).padStart(6, "0")).join(".");
1103
+ const uniq = [...new Set(all.map(key))].sort();
1104
+ const rank = new Map(uniq.map((k, j) => [k, j]));
1105
+ const stepOf = all.map((i) => rank.get(key(i)));
1106
+ const missing = [...new Set(all.filter((i) => !pack.header.parts[i.part]).map((i) => i.part))];
1107
+ const object = new THREE2.Group;
1108
+ object.name = "ldr";
1109
+ if (!o.ldrawSpace)
1110
+ object.rotation.x = Math.PI;
1111
+ if (o.scale && o.scale !== 1)
1112
+ object.scale.setScalar(o.scale);
1113
+ const pairs = new Map;
1114
+ let cur = null;
1115
+ let { edges, conditionalLines: cond, cullStuds: cs, cullUndersides: cu, side } = o;
1116
+ const stats = { instances: all.length, drawn: 0, missing, studsTotal: 0, studsHidden: 0, undersTotal: 0, undersHidden: 0, buildMs: 0 };
1117
+ const disposeCur = () => {
1118
+ if (!cur)
1119
+ return;
1120
+ object.remove(cur.root);
1121
+ const mats = new Set;
1122
+ cur.root.traverse((x) => {
1123
+ x.geometry?.dispose?.();
1124
+ if (x.material)
1125
+ mats.add(x.material);
1126
+ if (x.dispose && x.isInstancedMesh)
1127
+ x.dispose();
1128
+ });
1129
+ for (const m of [...mats, ...pairs.values()]) {
1130
+ m.userData.ldrMap?.dispose();
1131
+ m.dispose();
1132
+ }
1133
+ cur = null;
1134
+ };
1135
+ const build = (step) => {
1136
+ const t0 = performance.now();
1137
+ disposeCur();
1138
+ const insts = step == null ? all : all.filter((_, k) => stepOf[k] <= step);
1139
+ cur = buildLdrScene(insts, pack, { pack: "", cullStuds: cs, cullUnder: cu, edges: edges ? "idx" : false, side, cond, condStuds: o.conditionalStudLines !== false, tex: o.textures !== false }, color, () => {});
1140
+ pairs.clear();
1141
+ if (o.material === "plastic")
1142
+ cur.root.traverse((x) => {
1143
+ if (x.isMesh && x.material?.isMeshStandardMaterial) {
1144
+ const old = x.material;
1145
+ x.material = toPlastic(old);
1146
+ pairs.set(x.material, old);
1147
+ }
1148
+ });
1149
+ if (!edges)
1150
+ cur.setEdges(false);
1151
+ if (!cond)
1152
+ cur.setCond(false);
1153
+ object.add(cur.root);
1154
+ Object.assign(stats, { drawn: insts.filter((i) => pack.header.parts[i.part]).length, studsTotal: cur.stats.studsTotal, studsHidden: cur.stats.studsHidden, undersTotal: cur.stats.undersTotal, undersHidden: cur.stats.undersHidden, condBatches: cur.stats.condBatches ?? 0, texBatches: cur.stats.texBatches ?? 0, textures: cur.stats.textures ?? 0, buildMs: performance.now() - t0 });
1155
+ api.texturesReady = cur.texturesReady;
1156
+ };
1157
+ const api = { texturesReady: Promise.resolve() };
1158
+ build(o.step ?? null);
1159
+ return Object.assign(api, {
1160
+ object,
1161
+ stats,
1162
+ stepCount: uniq.length,
1163
+ setStep: (s) => build(s),
1164
+ setEdges: (v) => {
1165
+ edges = v;
1166
+ cur.setEdges(v);
1167
+ },
1168
+ setConditionalLines: (v) => {
1169
+ cond = v;
1170
+ cur.setCond(v);
1171
+ },
1172
+ setCulling: (a, b) => {
1173
+ cs = a;
1174
+ cu = b;
1175
+ cur.setCull(a, b);
1176
+ },
1177
+ setSide: (s) => {
1178
+ side = s;
1179
+ cur.setSide(s === "front");
1180
+ for (const [p, old] of pairs) {
1181
+ p.side = old.side;
1182
+ p.needsUpdate = true;
1183
+ }
1184
+ },
1185
+ getBox: (target = new THREE2.Box3) => {
1186
+ object.updateMatrixWorld(true);
1187
+ target.makeEmpty();
1188
+ object.traverse((x) => {
1189
+ if (x.isInstancedMesh && x.count) {
1190
+ x.computeBoundingBox();
1191
+ const b = x.boundingBox.clone().applyMatrix4(x.matrixWorld);
1192
+ target.union(b);
1193
+ }
1194
+ });
1195
+ return target;
1196
+ },
1197
+ dispose: disposeCur
1198
+ });
1199
+ }
1200
+ // src/pack-io.ts
1201
+ import { decompress } from "fzstd";
1202
+ async function decompressPack(data) {
1203
+ const u = data instanceof Uint8Array ? data : new Uint8Array(data);
1204
+ const own = (o) => o.buffer.slice(o.byteOffset, o.byteOffset + o.byteLength);
1205
+ if (u[0] === 40 && u[1] === 181 && u[2] === 47 && u[3] === 253)
1206
+ return own(decompress(u));
1207
+ if (u[0] === 31 && u[1] === 139)
1208
+ return new Response(new Blob([u]).stream().pipeThrough(new DecompressionStream("gzip"))).arrayBuffer();
1209
+ return own(u);
1210
+ }
1211
+ export {
1212
+ I as IDENTITY,
1213
+ buildLdrScene,
1214
+ computeCulling,
1215
+ conditionalLineVisible,
1216
+ conditionalLines,
1217
+ createInstancedConditionalEdges,
1218
+ createInstancedEdges,
1219
+ createLdrObject,
1220
+ decodePack,
1221
+ decompressPack,
1222
+ flatten,
1223
+ isDirectColor,
1224
+ loadPack,
1225
+ makeMaterial,
1226
+ mul,
1227
+ norm,
1228
+ parse,
1229
+ parseColor,
1230
+ parseLDConfig,
1231
+ parseModel,
1232
+ parseTexMap,
1233
+ resolveTexture,
1234
+ sequenceSteps,
1235
+ smoothNormals,
1236
+ texmapTriangleUV,
1237
+ texmapUV,
1238
+ transformTexMap
1239
+ };