ldr 0.2.0 → 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 CHANGED
@@ -6,15 +6,139 @@ function parseColor(tok) {
6
6
  return +tok;
7
7
  }
8
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
+ }
9
94
  function parse(src, name = "main.ldr") {
10
95
  const files = new Map;
11
96
  let cur = null;
12
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
+ };
13
110
  const start = (n) => {
111
+ closeData();
14
112
  cur = { name: norm(n), cmds: [] };
15
113
  files.set(cur.name, cur);
16
114
  if (!main)
17
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;
18
142
  };
19
143
  for (const raw of src.split(/\r?\n/)) {
20
144
  const line = raw.trim();
@@ -25,19 +149,40 @@ function parse(src, name = "main.ldr") {
25
149
  if (t === 0) {
26
150
  const text = line.slice(1).trim();
27
151
  if (tk[1] === "FILE") {
152
+ firstBlock();
153
+ skip = false;
28
154
  start(text.slice(5));
29
155
  continue;
30
156
  }
31
157
  if (tk[1] === "NOFILE") {
158
+ closeData();
32
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(""));
33
176
  continue;
34
177
  }
35
178
  if (!cur)
36
179
  start(name);
37
180
  const c = { t: 0, text };
38
- if (tk[1] === "STEP")
181
+ if (tk[1] === "STEP") {
39
182
  c.step = true;
40
- else if (tk[1] === "BFC")
183
+ texStack = [];
184
+ texNext = null;
185
+ } else if (tk[1] === "BFC")
41
186
  c.bfc = tk.slice(2).join(" ");
42
187
  else if (tk[1] === "ROTSTEP") {
43
188
  c.meta = "ROTSTEP";
@@ -53,6 +198,37 @@ function parse(src, name = "main.ldr") {
53
198
  c.bufexchg = { op: op === "RETRIEVE" ? "RETRIEVE" : "STORE", buf: tk[3] ?? "A" };
54
199
  } else if (tk[1]?.startsWith("!"))
55
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;
56
232
  const moved = text.match(MOVED_RE);
57
233
  if (moved && !cur.movedTo) {
58
234
  let target = moved[1].trim().replace(/\\/g, "/");
@@ -63,22 +239,109 @@ function parse(src, name = "main.ldr") {
63
239
  cur.cmds.push(c);
64
240
  continue;
65
241
  }
242
+ if (dataCur || skip)
243
+ continue;
66
244
  if (!cur)
67
245
  start(name);
68
- const color = parseColor(tk[1]);
69
- const nums = tk.slice(2).map(Number);
70
- if (t === 1)
71
- cur.cmds.push({ t: 1, color, m: nums.slice(0, 12), file: norm(tk.slice(14).join(" ")) });
72
- else if (t === 2)
73
- cur.cmds.push({ t: 2, color, p: nums.slice(0, 6) });
74
- else if (t === 3)
75
- cur.cmds.push({ t: 3, color, p: nums.slice(0, 9) });
76
- else if (t === 4)
77
- cur.cmds.push({ t: 4, color, p: nums.slice(0, 12) });
78
- else if (t === 5)
79
- cur.cmds.push({ t: 5, color, p: nums.slice(0, 12) });
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;
80
292
  }
81
- return { main, files };
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) };
82
345
  }
83
346
  function mul(a, b) {
84
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;
@@ -241,7 +504,7 @@ function decodePack(buf) {
241
504
  const hl = new DataView(buf).getUint32(0, true);
242
505
  const header = JSON.parse(new TextDecoder().decode(new Uint8Array(buf, 4, hl)));
243
506
  const base = 4 + hl;
244
- 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]) : new Uint32Array(buf, base + r[0], r[1]));
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]));
245
508
  return { header, arr, bytes: buf.byteLength };
246
509
  }
247
510
  function geom(b, arr) {
@@ -253,6 +516,8 @@ function geom(b, arr) {
253
516
  g.setAttribute("ldrColor", new THREE.BufferAttribute(arr(b.col), 4));
254
517
  if (b.idx)
255
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));
256
521
  return g;
257
522
  }
258
523
  function makeMaterial(side) {
@@ -268,6 +533,41 @@ function makeMaterial(side) {
268
533
  m.customProgramCacheKey = () => "ldrColor";
269
534
  return m;
270
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
+ }
271
571
  var lineMat = () => new THREE.ShaderMaterial({
272
572
  vertexShader: `attribute vec4 im0; attribute vec4 im1; attribute vec4 im2; attribute vec4 im3;
273
573
  void main(){ gl_Position = projectionMatrix * modelViewMatrix * mat4(im0,im1,im2,im3) * vec4(position,1.0); }`,
@@ -296,6 +596,109 @@ var weldLines = (pos) => {
296
596
  function createInstancedEdges(pos, im, material = sharedLineMat()) {
297
597
  return sharedLines(pos, im, material);
298
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
+ }
299
702
  function sharedLines(pos, im, material = sharedLineMat()) {
300
703
  const { P, I } = weldLines(pos);
301
704
  const g = new THREE.InstancedBufferGeometry;
@@ -426,29 +829,96 @@ function buildLdrScene(insts, pack, P, color, mark) {
426
829
  const cull = computeCulling(insts, H);
427
830
  mark("culling");
428
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
+ };
429
857
  const stats = { studsTotal: cull.studs.length, studsHidden: cull.studs.filter((s) => s.hidden).length, undersTotal: 0, undersHidden: 0 };
430
858
  const toggles = [];
431
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;
432
881
  for (const [pk, ids] of byPart) {
433
882
  const part = pk.replace(/#m$/, "");
434
883
  const p = H.parts[part];
435
884
  const mat = P.uncertDouble !== false && p.uncert ? (nUncert++, matD) : pk.endsWith("#m") ? matB : matF;
436
- const body = geom(p.body, arr);
885
+ const textured = wantTex && !!p.tex;
886
+ const body = geom(textured ? p.tbody : p.body, arr);
437
887
  let bodyIm = null;
438
- if (body) {
439
- const im = bodyIm = new THREE.InstancedMesh(body, mat, ids.length);
888
+ const place = (im) => {
440
889
  ids.forEach((id, k) => {
441
890
  im.setMatrixAt(k, toM4(insts[id].m));
442
891
  im.setColorAt(k, color(insts[id].color));
443
892
  });
444
893
  im.computeBoundingSphere();
445
894
  root.add(im);
895
+ };
896
+ if (body) {
897
+ const im = bodyIm = new THREE.InstancedMesh(body, mat, ids.length);
898
+ place(im);
446
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
+ }
447
915
  if (P.edges === "idx" && p.edges && bodyIm) {
448
916
  const l = sharedLines(arr(p.edges), bodyIm);
449
917
  edgeObjs.push(l);
450
918
  root.add(l);
451
919
  }
920
+ if (bodyIm)
921
+ addCond(p, bodyIm);
452
922
  const under = geom(p.under, arr);
453
923
  if (under) {
454
924
  const ord = [...ids.filter((i) => !cull.underHidden[i]), ...ids.filter((i) => cull.underHidden[i])];
@@ -464,6 +934,15 @@ function buildLdrScene(insts, pack, P, color, mark) {
464
934
  im.count = P.cullUnder ? vis : ids.length;
465
935
  root.add(im);
466
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
+ }
467
946
  }
468
947
  if (P.edges === true && p.edges) {
469
948
  const mats = new Float32Array(ids.length * 16);
@@ -508,6 +987,15 @@ function buildLdrScene(insts, pack, P, color, mark) {
508
987
  } }, all: ord.length, visible: vis, flag: "studs" });
509
988
  l.geometry.instanceCount = im.count;
510
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
+ }
511
999
  if (P.edges === true && k.edges) {
512
1000
  const mats = new Float32Array(ord.length * 16);
513
1001
  ord.forEach((s, i) => toM4(s.w).toArray(mats, i * 16));
@@ -521,7 +1009,19 @@ function buildLdrScene(insts, pack, P, color, mark) {
521
1009
  }
522
1010
  stats.uncertParts = Object.values(H.parts).filter((x) => x.uncert).length;
523
1011
  stats.uncertBatches = nUncert;
524
- const setEdges = (v) => edgeObjs.forEach((o) => o.visible = v);
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(() => {});
525
1025
  const setCull = (studs, under) => {
526
1026
  for (const t of toggles)
527
1027
  t.mesh.count = (t.flag === "studs" ? studs : under) ? t.visible : t.all;
@@ -537,6 +1037,11 @@ function buildLdrScene(insts, pack, P, color, mark) {
537
1037
  matF.side = front ? THREE.FrontSide : THREE.DoubleSide;
538
1038
  matB.side = front ? THREE.BackSide : THREE.DoubleSide;
539
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
+ }
540
1045
  };
541
1046
  const swapStuds = (alt) => {
542
1047
  const saved = studMeshes.map((s) => s.im.geometry);
@@ -547,7 +1052,7 @@ function buildLdrScene(insts, pack, P, color, mark) {
547
1052
  }
548
1053
  return () => studMeshes.forEach((s, i) => s.im.geometry = saved[i]);
549
1054
  };
550
- return { root, stats, setCull, setSide, setEdges, swapStuds, box };
1055
+ return { root, stats, setCull, setSide, setEdges, setCond, swapStuds, box, texturesReady };
551
1056
  }
552
1057
  // src/scene.ts
553
1058
  import * as THREE2 from "three";
@@ -584,13 +1089,14 @@ function toPlastic(old) {
584
1089
  if (!m) {
585
1090
  const p = new THREE2.MeshPhysicalMaterial({ vertexColors: true, roughness: 0.3, clearcoat: 0.3, clearcoatRoughness: 0.2, specularIntensity: 0.6, side: old.side });
586
1091
  p.onBeforeCompile = old.onBeforeCompile;
587
- p.customProgramCacheKey = () => "ldrColorPhys" + old.side;
1092
+ p.customProgramCacheKey = () => "Phys" + old.customProgramCacheKey() + old.side;
1093
+ p.userData = old.userData;
588
1094
  plasticCache.set(old, m = p);
589
1095
  }
590
1096
  return m;
591
1097
  }
592
1098
  function createLdrObject(instances, pack, options = {}) {
593
- const o = { edges: true, cullStuds: true, cullUndersides: true, side: "front", material: "plastic", ...options };
1099
+ const o = { edges: true, conditionalLines: true, cullStuds: true, cullUndersides: true, side: "front", material: "plastic", ...options };
594
1100
  const color = colorFn(o.colors, pack, new THREE2.Color(o.fallbackColor ?? 10528169));
595
1101
  const all = instances.map((i) => ({ part: i.part, color: String(i.color), m: i.m, steps: i.steps }));
596
1102
  const key = (i) => i.steps.map((x) => String(x).padStart(6, "0")).join(".");
@@ -606,7 +1112,7 @@ function createLdrObject(instances, pack, options = {}) {
606
1112
  object.scale.setScalar(o.scale);
607
1113
  const pairs = new Map;
608
1114
  let cur = null;
609
- let { edges, cullStuds: cs, cullUndersides: cu, side } = o;
1115
+ let { edges, conditionalLines: cond, cullStuds: cs, cullUndersides: cu, side } = o;
610
1116
  const stats = { instances: all.length, drawn: 0, missing, studsTotal: 0, studsHidden: 0, undersTotal: 0, undersHidden: 0, buildMs: 0 };
611
1117
  const disposeCur = () => {
612
1118
  if (!cur)
@@ -620,17 +1126,17 @@ function createLdrObject(instances, pack, options = {}) {
620
1126
  if (x.dispose && x.isInstancedMesh)
621
1127
  x.dispose();
622
1128
  });
623
- for (const m of mats)
1129
+ for (const m of [...mats, ...pairs.values()]) {
1130
+ m.userData.ldrMap?.dispose();
624
1131
  m.dispose();
625
- for (const old of pairs.values())
626
- old.dispose();
1132
+ }
627
1133
  cur = null;
628
1134
  };
629
1135
  const build = (step) => {
630
1136
  const t0 = performance.now();
631
1137
  disposeCur();
632
1138
  const insts = step == null ? all : all.filter((_, k) => stepOf[k] <= step);
633
- cur = buildLdrScene(insts, pack, { pack: "", cullStuds: cs, cullUnder: cu, edges: edges ? "idx" : false, side }, color, () => {});
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, () => {});
634
1140
  pairs.clear();
635
1141
  if (o.material === "plastic")
636
1142
  cur.root.traverse((x) => {
@@ -642,11 +1148,15 @@ function createLdrObject(instances, pack, options = {}) {
642
1148
  });
643
1149
  if (!edges)
644
1150
  cur.setEdges(false);
1151
+ if (!cond)
1152
+ cur.setCond(false);
645
1153
  object.add(cur.root);
646
- 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, buildMs: performance.now() - t0 });
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;
647
1156
  };
1157
+ const api = { texturesReady: Promise.resolve() };
648
1158
  build(o.step ?? null);
649
- return {
1159
+ return Object.assign(api, {
650
1160
  object,
651
1161
  stats,
652
1162
  stepCount: uniq.length,
@@ -655,6 +1165,10 @@ function createLdrObject(instances, pack, options = {}) {
655
1165
  edges = v;
656
1166
  cur.setEdges(v);
657
1167
  },
1168
+ setConditionalLines: (v) => {
1169
+ cond = v;
1170
+ cur.setCond(v);
1171
+ },
658
1172
  setCulling: (a, b) => {
659
1173
  cs = a;
660
1174
  cu = b;
@@ -681,7 +1195,7 @@ function createLdrObject(instances, pack, options = {}) {
681
1195
  return target;
682
1196
  },
683
1197
  dispose: disposeCur
684
- };
1198
+ });
685
1199
  }
686
1200
  // src/pack-io.ts
687
1201
  import { decompress } from "fzstd";
@@ -698,6 +1212,9 @@ export {
698
1212
  I as IDENTITY,
699
1213
  buildLdrScene,
700
1214
  computeCulling,
1215
+ conditionalLineVisible,
1216
+ conditionalLines,
1217
+ createInstancedConditionalEdges,
701
1218
  createInstancedEdges,
702
1219
  createLdrObject,
703
1220
  decodePack,
@@ -712,6 +1229,11 @@ export {
712
1229
  parseColor,
713
1230
  parseLDConfig,
714
1231
  parseModel,
1232
+ parseTexMap,
1233
+ resolveTexture,
715
1234
  sequenceSteps,
716
- smoothNormals
1235
+ smoothNormals,
1236
+ texmapTriangleUV,
1237
+ texmapUV,
1238
+ transformTexMap
717
1239
  };