ldr 0.4.0 → 0.5.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.
@@ -1,4 +1,4 @@
1
- // src/spec.ts
1
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/spec.ts
2
2
  var norm = (s) => s.trim().toLowerCase().replace(/\\/g, "/").replace(/\/+/g, "/");
3
3
  function parseColor(tok) {
4
4
  if (/^0x/i.test(tok))
@@ -647,7 +647,7 @@ function fileStepViews(f) {
647
647
  viewMemo.set(f, out);
648
648
  return out;
649
649
  }
650
- // src/model.ts
650
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/model.ts
651
651
  function flatten(doc, opts = {}) {
652
652
  const out = [];
653
653
  const maxDepth = opts.maxDepth ?? 64;
@@ -768,7 +768,7 @@ function parseLDConfig(text) {
768
768
  }
769
769
  return out;
770
770
  }
771
- // src/render.ts
771
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/render.ts
772
772
  import * as THREE from "three";
773
773
  async function loadPack(url) {
774
774
  const res = await fetch(url);
@@ -796,19 +796,60 @@ function decodePack(buf) {
796
796
  const header = JSON.parse(new TextDecoder().decode(new Uint8Array(buf, 4, hl)));
797
797
  const base = 4 + hl;
798
798
  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]));
799
- return { header, arr, bytes: buf.byteLength };
799
+ return { header, arr, bytes: buf.byteLength, buffer: buf };
800
800
  }
801
- function geom(b, arr) {
801
+ var toI8 = (a) => {
802
+ const o = new Int8Array(a.length);
803
+ for (let i = 0;i < a.length; i++)
804
+ o[i] = Math.round(Math.max(-1, Math.min(1, a[i])) * 127);
805
+ return o;
806
+ };
807
+ var toU8 = (a) => {
808
+ const o = new Uint8Array(a.length);
809
+ for (let i = 0;i < a.length; i++)
810
+ o[i] = Math.round(Math.max(0, Math.min(1, a[i])) * 255);
811
+ return o;
812
+ };
813
+ function flatNormals(pos, idx) {
814
+ const n = new Float32Array(pos.length);
815
+ for (let t = 0;t < idx.length; t += 3) {
816
+ const a = idx[t] * 3, b = idx[t + 1] * 3, c = idx[t + 2] * 3;
817
+ const ux = pos[b] - pos[a], uy = pos[b + 1] - pos[a + 1], uz = pos[b + 2] - pos[a + 2], vx = pos[c] - pos[a], vy = pos[c + 1] - pos[a + 1], vz = pos[c + 2] - pos[a + 2];
818
+ let nx = uy * vz - uz * vy, ny = uz * vx - ux * vz, nz = ux * vy - uy * vx;
819
+ const l = Math.hypot(nx, ny, nz) || 1;
820
+ nx /= l;
821
+ ny /= l;
822
+ nz /= l;
823
+ for (const o of [a, b, c]) {
824
+ n[o] = nx;
825
+ n[o + 1] = ny;
826
+ n[o + 2] = nz;
827
+ }
828
+ }
829
+ return n;
830
+ }
831
+ var geomCache = new WeakMap;
832
+ function geom(b, arr, quant = true) {
802
833
  if (!b)
803
834
  return null;
804
- const g = new THREE.BufferGeometry;
835
+ let c = geomCache.get(arr);
836
+ if (!c)
837
+ geomCache.set(arr, c = new Map);
838
+ const ck = quant ? b : b.pos;
839
+ let g = c.get(ck);
840
+ if (g)
841
+ return g;
842
+ g = new THREE.BufferGeometry;
805
843
  g.setAttribute("position", new THREE.BufferAttribute(arr(b.pos), 3));
806
- g.setAttribute("normal", new THREE.BufferAttribute(arr(b.nrm), 3));
807
- g.setAttribute("ldrColor", new THREE.BufferAttribute(arr(b.col), 4));
844
+ const nrm = b.nrm ? arr(b.nrm) : flatNormals(arr(b.pos), arr(b.idx));
845
+ const col = arr(b.col);
846
+ g.setAttribute("normal", quant ? new THREE.BufferAttribute(toI8(nrm), 3, true) : new THREE.BufferAttribute(nrm, 3));
847
+ g.setAttribute("ldrColor", col instanceof Uint8Array ? new THREE.BufferAttribute(col, 4, true) : quant ? new THREE.BufferAttribute(toU8(col), 4, true) : new THREE.BufferAttribute(col, 4));
808
848
  if (b.idx)
809
849
  g.setIndex(new THREE.BufferAttribute(arr(b.idx), 1));
810
850
  if (b.uv)
811
851
  g.setAttribute("ldrUV", new THREE.BufferAttribute(arr(b.uv), 2));
852
+ c.set(ck, g);
812
853
  return g;
813
854
  }
814
855
  function makeMaterial(side) {
@@ -1012,120 +1053,544 @@ function instancedLines(pos, mats, count) {
1012
1053
  l.frustumCulled = false;
1013
1054
  return l;
1014
1055
  }
1015
- 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);
1016
- var mul12 = (a, b) => {
1017
- 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];
1018
- 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)];
1056
+ function writeM4(t, o, m, mo = 0) {
1057
+ t[o] = m[mo + 3];
1058
+ t[o + 1] = m[mo + 6];
1059
+ t[o + 2] = m[mo + 9];
1060
+ t[o + 3] = 0;
1061
+ t[o + 4] = m[mo + 4];
1062
+ t[o + 5] = m[mo + 7];
1063
+ t[o + 6] = m[mo + 10];
1064
+ t[o + 7] = 0;
1065
+ t[o + 8] = m[mo + 5];
1066
+ t[o + 9] = m[mo + 8];
1067
+ t[o + 10] = m[mo + 11];
1068
+ t[o + 11] = 0;
1069
+ t[o + 12] = m[mo];
1070
+ t[o + 13] = m[mo + 1];
1071
+ t[o + 14] = m[mo + 2];
1072
+ t[o + 15] = 1;
1073
+ }
1074
+ function newIM(g, mat, n) {
1075
+ const im = new THREE.InstancedMesh(g, mat, 0);
1076
+ im.instanceMatrix = new THREE.InstancedBufferAttribute(new Float32Array(n * 16), 16);
1077
+ im.count = n;
1078
+ return im;
1079
+ }
1080
+ function fillInstances(im, ids, insts, color) {
1081
+ const M = im.instanceMatrix.array;
1082
+ const C = new Float32Array(ids.length * 3);
1083
+ for (let k = 0;k < ids.length; k++) {
1084
+ const it = insts[ids[k]];
1085
+ writeM4(M, k * 16, it.m);
1086
+ color(it.color).toArray(C, k * 3);
1087
+ }
1088
+ im.instanceColor = new THREE.InstancedBufferAttribute(C, 3);
1089
+ }
1090
+ var upright = (m, o = 0) => Math.abs(m[o + 7] - 1) < 0.001 && Math.abs(m[o + 4]) < 0.001 && Math.abs(m[o + 10]) < 0.001;
1091
+ var prepCache = new WeakMap;
1092
+ function prepPart(H, part) {
1093
+ let c = prepCache.get(H);
1094
+ if (!c)
1095
+ prepCache.set(H, c = new Map);
1096
+ let r = c.get(part);
1097
+ if (r !== undefined)
1098
+ return r;
1099
+ const p = H.parts[part];
1100
+ if (!p) {
1101
+ c.set(part, null);
1102
+ return null;
1103
+ }
1104
+ const n = p.studs.length;
1105
+ const studM = new Float64Array(n * 12);
1106
+ const kind = [], color = [];
1107
+ p.studs.forEach((s, i) => {
1108
+ kind.push(s[0]);
1109
+ color.push(s[1]);
1110
+ for (let k = 0;k < 12; k++)
1111
+ studM[i * 12 + k] = s[2 + k];
1112
+ });
1113
+ let mask = null, nx = 0, nz = 0;
1114
+ if (p.mask) {
1115
+ nx = p.mask[0];
1116
+ nz = p.mask[1];
1117
+ const bits = p.mask[2];
1118
+ mask = new Uint8Array(bits.length);
1119
+ for (let i = 0;i < bits.length; i++)
1120
+ mask[i] = bits.charCodeAt(i) === 49 ? 1 : 0;
1121
+ }
1122
+ c.set(part, r = { nStuds: n, studM, kind, color, mask, nx, nz });
1123
+ return r;
1124
+ }
1125
+ function mul12To(a, b, bo, w, ao = 0) {
1126
+ const b0 = b[bo], b1 = b[bo + 1], b2 = b[bo + 2];
1127
+ w[0] = a[ao] + a[ao + 3] * b0 + a[ao + 4] * b1 + a[ao + 5] * b2;
1128
+ w[1] = a[ao + 1] + a[ao + 6] * b0 + a[ao + 7] * b1 + a[ao + 8] * b2;
1129
+ w[2] = a[ao + 2] + a[ao + 9] * b0 + a[ao + 10] * b1 + a[ao + 11] * b2;
1130
+ for (let i = 0;i < 3; i++)
1131
+ for (let j = 0;j < 3; j++)
1132
+ w[3 + i * 3 + j] = a[ao + 3 + i * 3] * b[bo + 3 + j] + a[ao + 4 + i * 3] * b[bo + 6 + j] + a[ao + 5 + i * 3] * b[bo + 9 + j];
1133
+ }
1134
+ var det12 = (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]);
1135
+ var cellKey = (lv2, cx, cz) => lv2 < -1048576 || lv2 >= 1048576 || cx < -32768 || cx >= 32768 || cz < -32768 || cz >= 32768 ? -1 : (lv2 + 1048576) * 4294967296 + (cx + 32768) * 65536 + (cz + 32768);
1136
+ var topKey = (x, y, z) => {
1137
+ const rx = Math.round(x), ry = Math.round(y), rz = Math.round(z);
1138
+ return rx < -131072 || rx >= 131072 || ry < -65536 || ry >= 65536 || rz < -131072 || rz >= 131072 ? -1 : (rx + 131072) * 34359738368 + (ry + 65536) * 262144 + (rz + 131072);
1019
1139
  };
1020
- function computeCulling(insts, H) {
1021
- const upright = (m) => Math.abs(m[7] - 1) < 0.001 && Math.abs(m[4]) < 0.001 && Math.abs(m[10]) < 0.001;
1140
+
1141
+ class NumTable {
1142
+ keys;
1143
+ vals;
1144
+ mask;
1145
+ size = 0;
1146
+ constructor(cap = 1024) {
1147
+ let n = 1024;
1148
+ while (n < cap * 2)
1149
+ n *= 2;
1150
+ this.keys = new Float64Array(n).fill(-1);
1151
+ this.vals = new Int32Array(n);
1152
+ this.mask = n - 1;
1153
+ }
1154
+ slot(k) {
1155
+ const lo = k >>> 0, hi = k / 4294967296 >>> 0;
1156
+ let h = Math.imul(lo ^ Math.imul(hi, 2654435761), 2246822507);
1157
+ h ^= h >>> 15;
1158
+ return h & this.mask;
1159
+ }
1160
+ get(k) {
1161
+ const K = this.keys;
1162
+ let i = this.slot(k);
1163
+ for (;; ) {
1164
+ const x = K[i];
1165
+ if (x === k)
1166
+ return this.vals[i];
1167
+ if (x === -1)
1168
+ return -1;
1169
+ i = i + 1 & this.mask;
1170
+ }
1171
+ }
1172
+ put(k, v) {
1173
+ if ((this.size + 1) * 2 > this.keys.length)
1174
+ this.grow();
1175
+ const K = this.keys;
1176
+ let i = this.slot(k);
1177
+ for (;; ) {
1178
+ const x = K[i];
1179
+ if (x === k) {
1180
+ const o = this.vals[i];
1181
+ this.vals[i] = v;
1182
+ return o;
1183
+ }
1184
+ if (x === -1) {
1185
+ K[i] = k;
1186
+ this.vals[i] = v;
1187
+ this.size++;
1188
+ return -1;
1189
+ }
1190
+ i = i + 1 & this.mask;
1191
+ }
1192
+ }
1193
+ grow() {
1194
+ const ok = this.keys, ov = this.vals;
1195
+ const n = ok.length * 2;
1196
+ this.keys = new Float64Array(n).fill(-1);
1197
+ this.vals = new Int32Array(n);
1198
+ this.mask = n - 1;
1199
+ this.size = 0;
1200
+ for (let i = 0;i < ok.length; i++)
1201
+ if (ok[i] !== -1)
1202
+ this.put(ok[i], ov[i]);
1203
+ }
1204
+ }
1205
+ var STEP_NEVER = 2147483647;
1206
+ function forEachStud(insts, H, fn) {
1207
+ const w = new Float64Array(12);
1208
+ let j = 0;
1209
+ for (let idx = 0;idx < insts.length; idx++) {
1210
+ const it = insts[idx];
1211
+ const pp = prepPart(H, it.part);
1212
+ if (!pp)
1213
+ continue;
1214
+ for (let s = 0;s < pp.nStuds; s++) {
1215
+ mul12To(it.m, pp.studM, s * 12, w);
1216
+ const c = pp.color[s];
1217
+ fn(j++, idx, w, pp.kind[s], c === "16" ? it.color : c, s);
1218
+ }
1219
+ }
1220
+ }
1221
+ function cullCore(insts, H, steps) {
1022
1222
  const CELL = 40;
1023
- const rects = new Map;
1024
- const lk = (y) => Math.round(y * 2) / 2;
1025
- insts.forEach((it, idx) => {
1223
+ const rx0 = [], rx1 = [], rz0 = [], rz1 = [], rInst = [], rPrep = [], rBx = [], rBz = [];
1224
+ const cells = new NumTable(insts.length * 2);
1225
+ const nextE = [], rectE = [];
1226
+ for (let idx = 0;idx < insts.length; idx++) {
1227
+ const it = insts[idx];
1228
+ const m = it.m;
1026
1229
  const p = H.parts[it.part];
1027
- if (!p || !p.mask || !upright(it.m))
1028
- return;
1230
+ if (!p || !p.mask || !upright(m))
1231
+ continue;
1029
1232
  const b = p.bbox;
1030
- const xs = [], zs = [];
1031
- for (const x of [b[0], b[3]])
1032
- for (const z of [b[2], b[5]]) {
1033
- const w = [it.m[0] + it.m[3] * x + it.m[5] * z, it.m[2] + it.m[9] * x + it.m[11] * z];
1034
- xs.push(w[0]);
1035
- zs.push(w[1]);
1036
- }
1037
- const r = [Math.min(...xs), Math.max(...xs), Math.min(...zs), Math.max(...zs), idx];
1038
- const lv = lk(it.m[1] + b[4]);
1039
- for (let cx = Math.floor(r[0] / CELL);cx <= Math.floor(r[1] / CELL); cx++)
1040
- for (let cz = Math.floor(r[2] / CELL);cz <= Math.floor(r[3] / CELL); cz++) {
1041
- const k = lv + "|" + cx + "|" + cz;
1042
- (rects.get(k) || rects.set(k, []).get(k)).push(r);
1233
+ let x0 = Infinity, x1 = -Infinity, z0 = Infinity, z1 = -Infinity;
1234
+ for (let cxi = 0;cxi < 2; cxi++)
1235
+ for (let czi = 0;czi < 2; czi++) {
1236
+ const x = cxi ? b[3] : b[0], z = czi ? b[5] : b[2];
1237
+ const wx = m[0] + m[3] * x + m[5] * z, wz = m[2] + m[9] * x + m[11] * z;
1238
+ if (wx < x0)
1239
+ x0 = wx;
1240
+ if (wx > x1)
1241
+ x1 = wx;
1242
+ if (wz < z0)
1243
+ z0 = wz;
1244
+ if (wz > z1)
1245
+ z1 = wz;
1043
1246
  }
1044
- });
1045
- const covered = (x, y, z, self) => {
1046
- const list = rects.get(lk(y) + "|" + Math.floor(x / CELL) + "|" + Math.floor(z / CELL));
1047
- if (!list)
1048
- return false;
1049
- for (const r of list) {
1050
- 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))
1247
+ const ri = rInst.length;
1248
+ rx0.push(x0);
1249
+ rx1.push(x1);
1250
+ rz0.push(z0);
1251
+ rz1.push(z1);
1252
+ rInst.push(idx);
1253
+ rPrep.push(prepPart(H, it.part));
1254
+ rBx.push(b[0]);
1255
+ rBz.push(b[2]);
1256
+ const lv2 = Math.round((m[1] + b[4]) * 2);
1257
+ for (let cx = Math.floor(x0 / CELL);cx <= Math.floor(x1 / CELL); cx++)
1258
+ for (let cz = Math.floor(z0 / CELL);cz <= Math.floor(z1 / CELL); cz++) {
1259
+ const k = cellKey(lv2, cx, cz);
1260
+ if (k < 0)
1261
+ continue;
1262
+ const e = rectE.length;
1263
+ rectE.push(ri);
1264
+ nextE.push(cells.put(k, e));
1265
+ }
1266
+ }
1267
+ const SX = [0, 5, -5, 5, -5], SZ = [0, 5, 5, -5, -5];
1268
+ const coverStep = (x, y, z, self) => {
1269
+ const k = cellKey(Math.round(y * 2), Math.floor(x / CELL), Math.floor(z / CELL));
1270
+ if (k < 0)
1271
+ return STEP_NEVER;
1272
+ let best = STEP_NEVER;
1273
+ for (let e = cells.get(k);e >= 0; e = nextE[e]) {
1274
+ const ri = rectE[e];
1275
+ if (rInst[ri] === self || !(x - 6 >= rx0[ri] - 0.5 && x + 6 <= rx1[ri] + 0.5 && z - 6 >= rz0[ri] - 0.5 && z + 6 <= rz1[ri] + 0.5))
1051
1276
  continue;
1052
- const it = insts[r[4]];
1053
- const p = H.parts[it.part];
1054
- const m = it.m;
1277
+ const m = insts[rInst[ri]].m;
1278
+ const pp = rPrep[ri];
1279
+ const bx = rBx[ri], bz = rBz[ri];
1055
1280
  const dx = x - m[0], dy = y - m[1], dz = z - m[2];
1056
1281
  const lx = m[3] * dx + m[6] * dy + m[9] * dz, lz = m[5] * dx + m[8] * dy + m[11] * dz;
1057
- const [nx, nz, bits] = p.mask;
1282
+ const { nx, nz, mask: bits } = pp;
1058
1283
  let ok = true;
1059
- for (const [ox, oz] of [[0, 0], [5, 5], [-5, 5], [5, -5], [-5, -5]]) {
1060
- const ca = Math.floor((lx + ox - p.bbox[0]) / 20), cb = Math.floor((lz + oz - p.bbox[2]) / 20);
1061
- if (ca < 0 || cb < 0 || ca >= nx || cb >= nz || bits[ca * nz + cb] !== "1") {
1284
+ for (let s = 0;s < 5; s++) {
1285
+ const ca = Math.floor((lx + SX[s] - bx) / 20), cb = Math.floor((lz + SZ[s] - bz) / 20);
1286
+ if (ca < 0 || cb < 0 || ca >= nx || cb >= nz || bits[ca * nz + cb] !== 1) {
1062
1287
  ok = false;
1063
1288
  break;
1064
1289
  }
1065
1290
  }
1066
- if (ok)
1067
- return true;
1291
+ if (ok) {
1292
+ if (!steps)
1293
+ return 0;
1294
+ const st = steps[rInst[ri]];
1295
+ if (st < best)
1296
+ best = st;
1297
+ }
1068
1298
  }
1069
- return false;
1299
+ return best;
1070
1300
  };
1071
- const studs = [];
1072
- const studTops = new Set;
1073
- const key3 = (x, y, z) => `${Math.round(x)},${Math.round(y)},${Math.round(z)}`;
1074
- insts.forEach((it, idx) => {
1075
- const p = H.parts[it.part];
1076
- if (!p)
1301
+ let nStuds = 0;
1302
+ for (const it of insts) {
1303
+ const pp = prepPart(H, it.part);
1304
+ if (pp)
1305
+ nStuds += pp.nStuds;
1306
+ }
1307
+ const studHidden = new Uint8Array(nStuds), studUp = new Uint8Array(nStuds);
1308
+ const tops = new NumTable(nStuds);
1309
+ let studsHidden = 0;
1310
+ const studHideStep = steps ? new Int32Array(nStuds).fill(STEP_NEVER) : undefined;
1311
+ forEachStud(insts, H, (j, idx, w) => {
1312
+ if (!upright(w))
1313
+ return;
1314
+ studUp[j] = 1;
1315
+ const cs = coverStep(w[0], w[1], w[2], idx);
1316
+ if (cs !== STEP_NEVER) {
1317
+ studHidden[j] = 1;
1318
+ studsHidden++;
1319
+ if (studHideStep)
1320
+ studHideStep[j] = cs;
1321
+ }
1322
+ const k = topKey(w[0], w[1], w[2]);
1323
+ if (k < 0)
1324
+ return;
1325
+ if (!steps) {
1326
+ tops.put(k, 1);
1077
1327
  return;
1078
- for (const s of p.studs) {
1079
- const w = mul12(it.m, s.slice(2));
1080
- const color = s[1] === "16" ? it.color : s[1];
1081
- const up = upright(w);
1082
- studs.push({ kind: s[0], color, w, hidden: up && covered(w[0], w[1], w[2], idx) });
1083
- if (up)
1084
- studTops.add(key3(w[0], w[1], w[2]));
1085
1328
  }
1329
+ const st = steps[idx], o = tops.get(k);
1330
+ if (o < 0 || st < o)
1331
+ tops.put(k, st);
1086
1332
  });
1087
- const underHidden = insts.map((it) => {
1333
+ const underHidden = new Uint8Array(insts.length);
1334
+ const underHideStep = steps ? new Int32Array(insts.length).fill(STEP_NEVER) : undefined;
1335
+ for (let idx = 0;idx < insts.length; idx++) {
1336
+ const it = insts[idx];
1088
1337
  const p = H.parts[it.part];
1089
1338
  if (!p || !p.under || !upright(it.m))
1090
- return false;
1339
+ continue;
1091
1340
  const b = p.bbox;
1092
1341
  const nx = Math.round((b[3] - b[0]) / 20), nz = Math.round((b[5] - b[2]) / 20);
1093
1342
  if (!nx || !nz || nx * nz > 4096)
1094
- return false;
1095
- for (let a = 0;a < nx; a++)
1343
+ continue;
1344
+ const m = it.m;
1345
+ let all = true, at = 0;
1346
+ for (let a = 0;a < nx && all; a++)
1096
1347
  for (let c = 0;c < nz; c++) {
1097
1348
  const lx = b[0] + 10 + 20 * a, lz = b[2] + 10 + 20 * c;
1098
- 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;
1099
- if (!studTops.has(key3(x, it.m[1] + b[4], z)))
1100
- return false;
1349
+ const x = m[0] + m[3] * lx + m[5] * lz, z = m[2] + m[9] * lx + m[11] * lz;
1350
+ const k = topKey(x, m[1] + b[4], z);
1351
+ const t = k < 0 ? -1 : tops.get(k);
1352
+ if (t < 0) {
1353
+ all = false;
1354
+ break;
1355
+ }
1356
+ if (t > at)
1357
+ at = t;
1101
1358
  }
1102
- return true;
1359
+ if (all) {
1360
+ underHidden[idx] = 1;
1361
+ if (underHideStep)
1362
+ underHideStep[idx] = at;
1363
+ }
1364
+ }
1365
+ return { nStuds, studHidden, studUp, underHidden, studsHidden, studHideStep, underHideStep };
1366
+ }
1367
+ function computeCulling(insts, H) {
1368
+ const c = cullCore(insts, H);
1369
+ const studs = new Array(c.nStuds);
1370
+ forEachStud(insts, H, (j, _i, w, kind, color) => {
1371
+ studs[j] = { kind, color, w: Array.from(w), hidden: c.studHidden[j] === 1 };
1103
1372
  });
1104
- return { studs, underHidden };
1373
+ return { studs, underHidden: Array.from(c.underHidden, (x) => x === 1) };
1374
+ }
1375
+ function gridClusters(n, x, z, k) {
1376
+ if (n <= k || !isFinite(k)) {
1377
+ const a = new Int32Array(n);
1378
+ for (let i = 0;i < n; i++)
1379
+ a[i] = i;
1380
+ return [a];
1381
+ }
1382
+ let x0 = Infinity, x1 = -Infinity, z0 = Infinity, z1 = -Infinity;
1383
+ for (let i = 0;i < n; i++) {
1384
+ const a = x(i), b = z(i);
1385
+ if (a < x0)
1386
+ x0 = a;
1387
+ if (a > x1)
1388
+ x1 = a;
1389
+ if (b < z0)
1390
+ z0 = b;
1391
+ if (b > z1)
1392
+ z1 = b;
1393
+ }
1394
+ const G = Math.max(1, Math.ceil(Math.sqrt(n / k)));
1395
+ const sx = (x1 - x0) / G || 1, sz = (z1 - z0) / G || 1;
1396
+ const cell = new Int32Array(n), cnt = new Int32Array(G * G);
1397
+ for (let i = 0;i < n; i++) {
1398
+ const c = Math.min(G - 1, Math.floor((x(i) - x0) / sx)) * G + Math.min(G - 1, Math.floor((z(i) - z0) / sz));
1399
+ cell[i] = c;
1400
+ cnt[c]++;
1401
+ }
1402
+ const out = [];
1403
+ const at = new Int32Array(G * G).fill(-1);
1404
+ const fill = new Int32Array(G * G);
1405
+ for (let c = 0;c < G * G; c++)
1406
+ if (cnt[c]) {
1407
+ at[c] = out.length;
1408
+ out.push(new Int32Array(cnt[c]));
1409
+ }
1410
+ for (let i = 0;i < n; i++) {
1411
+ const c = cell[i];
1412
+ out[at[c]][fill[c]++] = i;
1413
+ }
1414
+ return out;
1415
+ }
1416
+
1417
+ class LdrLodRoot extends THREE.LOD {
1418
+ clusters = [];
1419
+ vpH = 1080;
1420
+ px = 0.5;
1421
+ edgePpl = 0.025;
1422
+ enabled = true;
1423
+ apply = () => {};
1424
+ inv = new THREE.Matrix4;
1425
+ cp = new THREE.Vector3;
1426
+ constructor() {
1427
+ super();
1428
+ this.autoUpdate = true;
1429
+ }
1430
+ raycast() {}
1431
+ update(camera) {
1432
+ if (!this.enabled || !this.clusters.length)
1433
+ return;
1434
+ const pm = camera.projectionMatrix.elements;
1435
+ const persp = camera.isPerspectiveCamera || pm[15] === 0;
1436
+ const k = this.vpH / 2 * Math.abs(pm[5]);
1437
+ this.inv.copy(this.matrixWorld).invert();
1438
+ this.cp.setFromMatrixPosition(camera.matrixWorld).applyMatrix4(this.inv);
1439
+ const { x, y, z } = this.cp;
1440
+ const tau = this.px, eppl = this.edgePpl;
1441
+ for (const c of this.clusters) {
1442
+ const cx = c.s.center.x - x, cy = c.s.center.y - y, cz = c.s.center.z - z;
1443
+ const d = Math.sqrt(cx * cx + cy * cy + cz * cz) - c.s.radius;
1444
+ const ppl = persp ? d > 0.000001 ? k / d : Infinity : k;
1445
+ let L = 0;
1446
+ for (let i = c.lv.length - 1;i > 0; i--)
1447
+ if (c.lv[i].err * ppl <= tau) {
1448
+ L = i;
1449
+ break;
1450
+ }
1451
+ const e = ppl >= eppl;
1452
+ if (L !== c.cur || e !== c.edgeOk) {
1453
+ c.cur = L;
1454
+ c.edgeOk = e;
1455
+ this.apply(c);
1456
+ }
1457
+ }
1458
+ }
1459
+ }
1460
+ function buildStudMeshes(insts, H, cull, arr, Q, LOD, color, matF, matB, steps, colorIndex) {
1461
+ const bucketOf = new Map;
1462
+ const bKind = [], bMirror = [];
1463
+ const bAll = [];
1464
+ const studBucket = new Uint16Array(cull.nStuds);
1465
+ const sx = new Float32Array(cull.nStuds), sz = new Float32Array(cull.nStuds);
1466
+ forEachStud(insts, H, (j, _i, w, kind) => {
1467
+ const neg = det12(w) < 0;
1468
+ const k = neg ? kind + "#m" : kind;
1469
+ let b = bucketOf.get(k);
1470
+ if (b === undefined) {
1471
+ b = bKind.length;
1472
+ bucketOf.set(k, b);
1473
+ bKind.push(kind);
1474
+ bMirror.push(neg);
1475
+ bAll.push(0);
1476
+ }
1477
+ studBucket[j] = b;
1478
+ bAll[b]++;
1479
+ sx[j] = w[0];
1480
+ sz[j] = w[2];
1481
+ });
1482
+ const bIds = bAll.map((n) => new Int32Array(n));
1483
+ {
1484
+ const f = bAll.map(() => 0);
1485
+ for (let j = 0;j < cull.nStuds; j++) {
1486
+ const b = studBucket[j];
1487
+ bIds[b][f[b]++] = j;
1488
+ }
1489
+ }
1490
+ const studCl = new Int32Array(cull.nStuds), studSlot = new Int32Array(cull.nStuds);
1491
+ const sMesh = [];
1492
+ bKind.forEach((kind, b) => {
1493
+ const g = geom(H.studKinds[kind].body, arr, Q);
1494
+ const ids = bIds[b];
1495
+ for (const c of gridClusters(ids.length, (i) => sx[ids[i]], (i) => sz[ids[i]], LOD ? 16384 : Infinity)) {
1496
+ const ci = sMesh.length;
1497
+ let v = 0;
1498
+ for (const i of c)
1499
+ if (!cull.studHidden[ids[i]])
1500
+ v++;
1501
+ let vc = 0, hc = v;
1502
+ for (const i of c) {
1503
+ const j = ids[i];
1504
+ studCl[j] = ci;
1505
+ studSlot[j] = cull.studHidden[j] ? hc++ : vc++;
1506
+ }
1507
+ if (!g) {
1508
+ sMesh.push(null);
1509
+ continue;
1510
+ }
1511
+ const im = newIM(g, bMirror[b] ? matB : matF, c.length);
1512
+ im.instanceColor = new THREE.InstancedBufferAttribute(new Float32Array(c.length * 3), 3);
1513
+ const n = c.length;
1514
+ sMesh.push({ im, kind, vis: v, ...steps ? { slots: { own: new Int32Array(n), hide: new Int32Array(n), inst: new Int32Array(n), stud: new Int16Array(n), col: new Uint16Array(n) } } : {} });
1515
+ }
1516
+ });
1517
+ forEachStud(insts, H, (j, owner, w, _kind, col, si) => {
1518
+ const sm = sMesh[studCl[j]];
1519
+ if (!sm)
1520
+ return;
1521
+ const im = sm.im;
1522
+ const slot = studSlot[j];
1523
+ if (sm.slots) {
1524
+ const t = sm.slots;
1525
+ t.own[slot] = steps[owner];
1526
+ t.hide[slot] = cull.studHideStep[j];
1527
+ t.inst[slot] = owner;
1528
+ t.stud[slot] = si;
1529
+ t.col[slot] = colorIndex(col);
1530
+ }
1531
+ writeM4(im.instanceMatrix.array, slot * 16, w);
1532
+ color(col).toArray(im.instanceColor.array, slot * 3);
1533
+ });
1534
+ return sMesh;
1105
1535
  }
1106
1536
  function buildLdrScene(insts, pack, P, color, mark) {
1107
1537
  const { header: H, arr } = pack;
1108
- const root = new THREE.Group;
1538
+ const Q = P.quant !== false;
1539
+ const LOD = P.lod !== false;
1540
+ const root = new LdrLodRoot;
1541
+ root.enabled = LOD;
1542
+ root.px = P.lodPx ?? 0.5;
1543
+ root.edgePpl = (P.lodEdgePx ?? 0.5) / 20;
1544
+ const trackVp = (r) => {
1545
+ const rt = r.getRenderTarget();
1546
+ const h = rt ? rt.height : r.getContext().drawingBufferHeight;
1547
+ if (h)
1548
+ root.vpH = h;
1549
+ };
1109
1550
  const matF = makeMaterial(P.side === "front" ? THREE.FrontSide : THREE.DoubleSide), matB = makeMaterial(P.side === "front" ? THREE.BackSide : THREE.DoubleSide);
1110
1551
  const matD = makeMaterial(THREE.DoubleSide);
1111
1552
  let nUncert = 0;
1112
- 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]);
1113
1553
  const byPart = new Map;
1114
1554
  insts.forEach((it, i) => {
1115
1555
  if (H.parts[it.part]) {
1116
- const k = it.part + (det(it.m) < 0 ? "#m" : "");
1556
+ const k = it.part + (det12(it.m) < 0 ? "#m" : "");
1117
1557
  (byPart.get(k) || byPart.set(k, []).get(k)).push(i);
1118
1558
  }
1119
1559
  });
1120
- const cull = computeCulling(insts, H);
1560
+ const steps = P.steps;
1561
+ let cull = cullCore(insts, H, steps);
1121
1562
  mark("culling");
1563
+ const stepBatches = [];
1564
+ const instM = steps ? new Float64Array(insts.length * 12) : null;
1565
+ const instPP = [];
1566
+ const palette = [];
1567
+ const palIdx = new Map;
1568
+ const colorIndex = (c) => {
1569
+ let i = palIdx.get(c);
1570
+ if (i === undefined) {
1571
+ i = palette.length;
1572
+ palette.push(color(c));
1573
+ palIdx.set(c, i);
1574
+ }
1575
+ return i;
1576
+ };
1577
+ const instCol = steps ? new Uint16Array(insts.length) : null;
1578
+ if (instM)
1579
+ insts.forEach((it, i) => {
1580
+ for (let k = 0;k < 12; k++)
1581
+ instM[i * 12 + k] = it.m[k];
1582
+ instPP.push(prepPart(H, it.part));
1583
+ instCol[i] = colorIndex(it.color);
1584
+ });
1122
1585
  const edgeObjs = [];
1123
1586
  const condObjs = [];
1124
1587
  const wantCond = P.cond !== false && (P.edges === "idx" || P.edges === true);
1125
1588
  let condMat = null;
1126
1589
  const cmat = () => condMat ??= condLineMat();
1590
+ let lineMatShared = null;
1591
+ const lmat = () => lineMatShared ??= sharedLineMat();
1127
1592
  const condCache = new Map;
1128
- const addCond = (entry, im, which = "body") => {
1593
+ const addCond = (entry, im, cl, which = "body") => {
1129
1594
  if (!wantCond)
1130
1595
  return null;
1131
1596
  const ck = which === "under" ? entry.condlu : entry.condl;
@@ -1142,12 +1607,40 @@ function buildLdrScene(insts, pack, P, color, mark) {
1142
1607
  const l = condEdges(cb, im, cmat());
1143
1608
  edgeObjs.push(l);
1144
1609
  condObjs.push(l);
1610
+ cl.cond.push(l);
1611
+ root.add(l);
1612
+ return l;
1613
+ };
1614
+ const lineCache = new Map;
1615
+ const addLines = (ref, im, cl) => {
1616
+ let w = lineCache.get(ref);
1617
+ if (!w) {
1618
+ const r = weldLines(arr(ref));
1619
+ lineCache.set(ref, w = { P: new THREE.BufferAttribute(r.P, 3), I: new THREE.BufferAttribute(r.I, 1) });
1620
+ }
1621
+ const g = new THREE.InstancedBufferGeometry;
1622
+ g.setAttribute("position", w.P);
1623
+ g.setIndex(w.I);
1624
+ g.setAttribute("instanceMatrix", im.instanceMatrix);
1625
+ g.instanceCount = im.count;
1626
+ const l = new THREE.LineSegments(g, lmat());
1627
+ l.frustumCulled = false;
1628
+ edgeObjs.push(l);
1629
+ cl.lines.push(l);
1145
1630
  root.add(l);
1146
1631
  return l;
1147
1632
  };
1148
- const stats = { studsTotal: cull.studs.length, studsHidden: cull.studs.filter((s) => s.hidden).length, undersTotal: 0, undersHidden: 0 };
1633
+ const stats = { studsTotal: cull.nStuds, studsHidden: cull.studsHidden, undersTotal: 0, undersHidden: 0 };
1149
1634
  const toggles = [];
1150
1635
  const studMeshes = [];
1636
+ const countTo = (objs) => ({ set count(n) {
1637
+ for (const o of objs()) {
1638
+ if (o.isInstancedMesh)
1639
+ o.count = n;
1640
+ else
1641
+ o.geometry.instanceCount = n;
1642
+ }
1643
+ } });
1151
1644
  const wantTex = P.tex !== false && !!H.textures;
1152
1645
  const texCache = new Map;
1153
1646
  const texMats = new Map;
@@ -1169,160 +1662,256 @@ function buildLdrScene(insts, pack, P, color, mark) {
1169
1662
  return m;
1170
1663
  };
1171
1664
  let texBatches = 0;
1172
- for (const [pk, ids] of byPart) {
1665
+ const newCluster = (s) => {
1666
+ const c = { s, lv: [], lines: [], cond: [], cur: 0, edgeOk: true };
1667
+ root.clusters.push(c);
1668
+ return c;
1669
+ };
1670
+ const lodLevels = (b, base, mat) => !LOD || P.lodMesh === false || !b?.lod ? [] : b.lod.map((l) => {
1671
+ const im = new THREE.InstancedMesh(geom(l, arr, Q), mat, 0);
1672
+ im.instanceMatrix = base.instanceMatrix;
1673
+ im.count = base.count;
1674
+ im.instanceColor = base.instanceColor;
1675
+ im.boundingSphere = base.boundingSphere;
1676
+ im.visible = false;
1677
+ im.onBeforeRender = trackVp;
1678
+ im.userData.ldrLod = true;
1679
+ root.add(im);
1680
+ return { objs: [im], err: l.err };
1681
+ });
1682
+ const tris = (b) => b ? b.tris ?? 0 : 0;
1683
+ const kFor = (t) => LOD ? Math.max(256, Math.min(16384, Math.round(262144 / Math.max(1, t)))) : Infinity;
1684
+ for (const [pk, idsAll] of byPart) {
1173
1685
  const part = pk.replace(/#m$/, "");
1174
1686
  const p = H.parts[part];
1175
1687
  const mat = P.uncertDouble !== false && p.uncert ? (nUncert++, matD) : pk.endsWith("#m") ? matB : matF;
1176
1688
  const textured = wantTex && !!p.tex;
1177
- const body = geom(textured ? p.tbody : p.body, arr);
1178
- let bodyIm = null;
1179
- const place = (im) => {
1180
- ids.forEach((id, k) => {
1181
- im.setMatrixAt(k, toM4(insts[id].m));
1182
- im.setColorAt(k, color(insts[id].color));
1183
- });
1184
- im.computeBoundingSphere();
1185
- root.add(im);
1186
- };
1187
- if (body) {
1188
- const im = bodyIm = new THREE.InstancedMesh(body, mat, ids.length);
1189
- place(im);
1190
- }
1191
- if (textured)
1192
- for (const tb of p.tex) {
1193
- const g = geom(tb, arr);
1194
- const im = new THREE.InstancedMesh(g, texMat(tb.t, mat, !!tb.wrapU), ids.length);
1195
- texBatches++;
1196
- if (bodyIm) {
1197
- im.instanceMatrix = bodyIm.instanceMatrix;
1198
- im.instanceColor = bodyIm.instanceColor;
1199
- im.boundingSphere = bodyIm.boundingSphere;
1200
- root.add(im);
1201
- } else {
1202
- place(im);
1203
- bodyIm = im;
1204
- }
1689
+ const bodyB = textured ? p.tbody : p.body;
1690
+ const body = geom(bodyB, arr, Q);
1691
+ const groups = gridClusters(idsAll.length, (i) => insts[idsAll[i]].m[0], (i) => insts[idsAll[i]].m[2], textured ? Infinity : kFor(tris(p.body) + tris(p.under)));
1692
+ for (const g of groups) {
1693
+ const ids = Array.from(g, (i) => idsAll[i]);
1694
+ let bodyIm = null;
1695
+ const lv0 = [];
1696
+ const place = (im) => {
1697
+ fillInstances(im, ids, insts, color);
1698
+ im.computeBoundingSphere();
1699
+ root.add(im);
1700
+ };
1701
+ if (body) {
1702
+ const im = bodyIm = newIM(body, mat, ids.length);
1703
+ place(im);
1704
+ lv0.push(im);
1205
1705
  }
1206
- if (P.edges === "idx" && p.edges && bodyIm) {
1207
- const l = sharedLines(arr(p.edges), bodyIm);
1208
- edgeObjs.push(l);
1209
- root.add(l);
1210
- }
1211
- if (bodyIm)
1212
- addCond(p, bodyIm);
1213
- const under = geom(p.under, arr);
1214
- if (under) {
1215
- const ord = [...ids.filter((i) => !cull.underHidden[i]), ...ids.filter((i) => cull.underHidden[i])];
1216
- const vis = ids.filter((i) => !cull.underHidden[i]).length;
1217
- stats.undersTotal += ids.length;
1218
- stats.undersHidden += ids.length - vis;
1219
- const im = new THREE.InstancedMesh(under, mat, ids.length);
1220
- ord.forEach((id, k) => {
1221
- im.setMatrixAt(k, toM4(insts[id].m));
1222
- im.setColorAt(k, color(insts[id].color));
1223
- });
1224
- im.computeBoundingSphere();
1225
- im.count = P.cullUnder ? vis : ids.length;
1226
- root.add(im);
1227
- toggles.push({ mesh: im, all: ids.length, visible: vis, flag: "under" });
1228
- {
1229
- const cl = addCond(p, im, "under");
1230
- if (cl) {
1231
- toggles.push({ mesh: { set count(n) {
1232
- cl.geometry.instanceCount = n;
1233
- } }, all: ids.length, visible: vis, flag: "under" });
1234
- cl.geometry.instanceCount = im.count;
1706
+ if (textured)
1707
+ for (const tb of p.tex) {
1708
+ const gg = geom(tb, arr, Q);
1709
+ const im = newIM(gg, texMat(tb.t, mat, !!tb.wrapU), ids.length);
1710
+ texBatches++;
1711
+ lv0.push(im);
1712
+ if (bodyIm) {
1713
+ im.instanceMatrix = bodyIm.instanceMatrix;
1714
+ im.instanceColor = bodyIm.instanceColor;
1715
+ im.boundingSphere = bodyIm.boundingSphere;
1716
+ root.add(im);
1717
+ } else {
1718
+ place(im);
1719
+ bodyIm = im;
1720
+ }
1721
+ }
1722
+ if (bodyIm) {
1723
+ const cl = newCluster(bodyIm.boundingSphere);
1724
+ lv0.forEach((o) => o.onBeforeRender = trackVp);
1725
+ cl.lv.push({ objs: lv0, err: 0 }, ...textured ? [] : lodLevels(p.body, bodyIm, mat));
1726
+ if (P.edges === "idx" && p.edges)
1727
+ addLines(p.edges, bodyIm, cl);
1728
+ addCond(p, bodyIm, cl);
1729
+ if (steps)
1730
+ stepBatches.push({ im: bodyIm, objs: [...cl.lv.flatMap((l) => l.objs), ...cl.lines, ...cl.cond], own: Int32Array.from(ids, (id) => steps[id]), hide: null, flag: "body", inst: Int32Array.from(ids), stud: null, col: Uint16Array.from(ids, (id) => instCol[id]) });
1731
+ if (P.edges === true && p.edges) {
1732
+ const mats = new Float32Array(ids.length * 16);
1733
+ ids.forEach((id, k) => writeM4(mats, k * 16, insts[id].m));
1734
+ {
1735
+ const l = instancedLines(arr(p.edges), mats, ids.length);
1736
+ edgeObjs.push(l);
1737
+ cl.lines.push(l);
1738
+ root.add(l);
1739
+ }
1235
1740
  }
1236
1741
  }
1237
- }
1238
- if (P.edges === true && p.edges) {
1239
- const mats = new Float32Array(ids.length * 16);
1240
- ids.forEach((id, k) => toM4(insts[id].m).toArray(mats, k * 16));
1241
- {
1242
- const l = instancedLines(arr(p.edges), mats, ids.length);
1243
- edgeObjs.push(l);
1244
- root.add(l);
1742
+ const under = geom(p.under, arr, Q);
1743
+ if (under) {
1744
+ const ord = [...ids.filter((i) => !cull.underHidden[i]), ...ids.filter((i) => cull.underHidden[i])];
1745
+ const vis = ids.length - ids.reduce((a, i) => a + cull.underHidden[i], 0);
1746
+ stats.undersTotal += ids.length;
1747
+ stats.undersHidden += ids.length - vis;
1748
+ const im = newIM(under, mat, ids.length);
1749
+ fillInstances(im, ord, insts, color);
1750
+ im.onBeforeRender = trackVp;
1751
+ im.computeBoundingSphere();
1752
+ im.count = P.cullUnder ? vis : ids.length;
1753
+ root.add(im);
1754
+ const cl = newCluster(im.boundingSphere);
1755
+ cl.lv.push({ objs: [im], err: 0 }, ...lodLevels(p.under, im, mat));
1756
+ addCond(p, im, cl, "under");
1757
+ const objs = [...cl.lv.flatMap((l) => l.objs), ...cl.cond];
1758
+ objs.forEach((o) => {
1759
+ if (!o.isInstancedMesh)
1760
+ o.geometry.instanceCount = im.count;
1761
+ });
1762
+ toggles.push({ mesh: countTo(() => objs), all: ids.length, visible: vis, flag: "under" });
1763
+ if (steps)
1764
+ stepBatches.push({ im, objs, own: Int32Array.from(ord, (id) => steps[id]), hide: Int32Array.from(ord, (id) => cull.underHideStep[id]), flag: "under", inst: Int32Array.from(ord), stud: null, col: Uint16Array.from(ord, (id) => instCol[id]) });
1245
1765
  }
1246
1766
  }
1247
1767
  }
1248
- const byKind = new Map;
1249
- for (const s of cull.studs) {
1250
- const k = s.kind + (det(s.w) < 0 ? "#m" : "");
1251
- (byKind.get(k) || byKind.set(k, []).get(k)).push(s);
1252
- }
1253
- for (const [kk, list] of byKind) {
1254
- const kind = kk.replace(/#m$/, "");
1255
- const mat = kk.endsWith("#m") ? matB : matF;
1768
+ const sMesh = buildStudMeshes(insts, H, cull, arr, Q, LOD, color, matF, matB, steps, steps && colorIndex);
1769
+ const sKind = sMesh.map((x) => x?.kind ?? "");
1770
+ const sVis = sMesh.map((x) => x?.vis ?? 0);
1771
+ const STUD_ERR = 12;
1772
+ sMesh.forEach((sm, ci) => {
1773
+ if (!sm)
1774
+ return;
1775
+ const im = sm.im;
1776
+ const kind = sKind[ci];
1256
1777
  const k = H.studKinds[kind];
1257
- const g = geom(k.body, arr);
1258
- if (!g)
1259
- continue;
1260
- const ord = [...list.filter((s) => !s.hidden), ...list.filter((s) => s.hidden)];
1261
- const vis = list.filter((s) => !s.hidden).length;
1262
- const im = new THREE.InstancedMesh(g, mat, ord.length);
1263
- ord.forEach((s, i) => {
1264
- im.setMatrixAt(i, toM4(s.w));
1265
- im.setColorAt(i, color(s.color));
1266
- });
1778
+ const vis = sVis[ci], all = im.instanceMatrix.count;
1267
1779
  im.computeBoundingSphere();
1268
- im.count = P.cullStuds ? vis : ord.length;
1780
+ im.count = P.cullStuds ? vis : all;
1781
+ im.onBeforeRender = trackVp;
1782
+ im.userData.ldrStud = true;
1269
1783
  root.add(im);
1270
- toggles.push({ mesh: im, all: ord.length, visible: vis, flag: "studs" });
1271
1784
  studMeshes.push({ im, kind });
1272
- if (P.edges === "idx" && k.edges) {
1273
- const l = sharedLines(arr(k.edges), im);
1274
- edgeObjs.push(l);
1275
- root.add(l);
1276
- toggles.push({ mesh: { set count(n) {
1277
- l.geometry.instanceCount = n;
1278
- } }, all: ord.length, visible: vis, flag: "studs" });
1279
- l.geometry.instanceCount = im.count;
1280
- }
1281
- {
1282
- const cl = P.condStuds === false ? null : addCond(k, im);
1283
- if (cl) {
1284
- toggles.push({ mesh: { set count(n) {
1285
- cl.geometry.instanceCount = n;
1286
- } }, all: ord.length, visible: vis, flag: "studs" });
1287
- cl.geometry.instanceCount = im.count;
1288
- }
1289
- }
1785
+ const cl = newCluster(im.boundingSphere);
1786
+ cl.lv.push({ objs: [im], err: 0 });
1787
+ if (LOD && P.lodStuds !== false)
1788
+ cl.lv.push({ objs: [], err: STUD_ERR });
1789
+ if (P.edges === "idx" && k.edges)
1790
+ addLines(k.edges, im, cl);
1791
+ if (P.condStuds !== false)
1792
+ addCond(k, im, cl);
1290
1793
  if (P.edges === true && k.edges) {
1291
- const mats = new Float32Array(ord.length * 16);
1292
- ord.forEach((s, i) => toM4(s.w).toArray(mats, i * 16));
1293
- const l = instancedLines(arr(k.edges), mats, P.cullStuds ? vis : ord.length);
1794
+ const l = instancedLines(arr(k.edges), im.instanceMatrix.array.slice(), im.count);
1294
1795
  edgeObjs.push(l);
1796
+ cl.lines.push(l);
1295
1797
  root.add(l);
1296
- toggles.push({ mesh: { set count(n) {
1297
- l.geometry.instanceCount = n;
1298
- } }, all: ord.length, visible: vis, flag: "studs" });
1299
1798
  }
1300
- }
1799
+ const objs = [im, ...cl.lines, ...cl.cond];
1800
+ objs.forEach((o) => {
1801
+ if (!o.isInstancedMesh)
1802
+ o.geometry.instanceCount = im.count;
1803
+ });
1804
+ toggles.push({ mesh: countTo(() => objs), all, visible: vis, flag: "studs" });
1805
+ if (sm.slots)
1806
+ stepBatches.push({ im, objs, flag: "studs", ...sm.slots });
1807
+ });
1301
1808
  stats.uncertParts = Object.values(H.parts).filter((x) => x.uncert).length;
1302
1809
  stats.uncertBatches = nUncert;
1303
1810
  let condOn = true, edgesOn = true;
1811
+ root.apply = (c) => {
1812
+ c.lv.forEach((l, i) => {
1813
+ for (const o of l.objs)
1814
+ o.visible = i === c.cur;
1815
+ });
1816
+ const lineVis = edgesOn && c.edgeOk && (c.cur === 0 || !LOD || c.lv[c.cur].objs.length > 0);
1817
+ for (const o of c.lines)
1818
+ o.visible = lineVis;
1819
+ for (const o of c.cond)
1820
+ o.visible = lineVis && condOn;
1821
+ };
1822
+ const refresh = () => root.clusters.forEach(root.apply);
1304
1823
  const setEdges = (v) => {
1305
1824
  edgesOn = v;
1306
- edgeObjs.forEach((o) => o.visible = v && (condOn || !condObjs.includes(o)));
1825
+ refresh();
1307
1826
  };
1308
1827
  const setCond = (v) => {
1309
1828
  condOn = v;
1310
- condObjs.forEach((o) => o.visible = v && edgesOn);
1829
+ refresh();
1311
1830
  };
1831
+ refresh();
1312
1832
  stats.condBatches = condObjs.length;
1313
1833
  stats.texBatches = texBatches;
1314
1834
  stats.textures = texCache.size;
1835
+ stats.clusters = root.clusters.length;
1315
1836
  const texturesReady = Promise.all(pending).then(() => {});
1837
+ let stepS = STEP_NEVER - 1, cullS = !!P.cullStuds, cullU = !!P.cullUnder;
1838
+ const applySteps = () => {
1839
+ const st = { studsTotal: 0, studsHidden: 0, undersTotal: 0, undersHidden: 0 };
1840
+ const S = stepS;
1841
+ const wm = new Float64Array(12);
1842
+ for (const b of stepBatches) {
1843
+ const MA = b.im.instanceMatrix.array, CA = b.im.instanceColor.array;
1844
+ const useHide = b.flag === "studs" ? cullS : b.flag === "under" ? cullU : false;
1845
+ const { own, hide, inst, stud, col } = b;
1846
+ let w = 0;
1847
+ for (let i = 0;i < own.length; i++) {
1848
+ if (own[i] > S)
1849
+ continue;
1850
+ const hid = !!hide && hide[i] <= S;
1851
+ if (b.flag === "studs") {
1852
+ st.studsTotal++;
1853
+ if (hid)
1854
+ st.studsHidden++;
1855
+ } else if (b.flag === "under") {
1856
+ st.undersTotal++;
1857
+ if (hid)
1858
+ st.undersHidden++;
1859
+ }
1860
+ if (useHide && hid)
1861
+ continue;
1862
+ const n = inst[i];
1863
+ if (stud) {
1864
+ mul12To(instM, instPP[n].studM, stud[i] * 12, wm, n * 12);
1865
+ writeM4(MA, w * 16, wm);
1866
+ } else
1867
+ writeM4(MA, w * 16, instM, n * 12);
1868
+ palette[col[i]].toArray(CA, w * 3);
1869
+ w++;
1870
+ }
1871
+ b.im.instanceMatrix.needsUpdate = true;
1872
+ b.im.instanceColor.needsUpdate = true;
1873
+ for (const o of b.objs) {
1874
+ if (o.isInstancedMesh)
1875
+ o.count = w;
1876
+ else
1877
+ o.geometry.instanceCount = w;
1878
+ }
1879
+ }
1880
+ return st;
1881
+ };
1316
1882
  const setCull = (studs, under) => {
1883
+ cullS = studs;
1884
+ cullU = under;
1885
+ if (steps) {
1886
+ applySteps();
1887
+ return;
1888
+ }
1317
1889
  for (const t of toggles)
1318
1890
  t.mesh.count = (t.flag === "studs" ? studs : under) ? t.visible : t.all;
1319
1891
  };
1892
+ const setStep = (s) => {
1893
+ if (!steps)
1894
+ throw new Error("buildLdrScene: setStep needs P.steps");
1895
+ stepS = s == null ? STEP_NEVER - 1 : s;
1896
+ return applySteps();
1897
+ };
1320
1898
  const box = new THREE.Box3;
1321
1899
  for (const it of insts) {
1322
- const p = H.parts[it.part];
1323
- if (!p)
1900
+ if (!H.parts[it.part])
1324
1901
  continue;
1325
- box.expandByPoint(new THREE.Vector3(it.m[0], it.m[1], it.m[2]));
1902
+ const m = it.m;
1903
+ if (m[0] < box.min.x)
1904
+ box.min.x = m[0];
1905
+ if (m[1] < box.min.y)
1906
+ box.min.y = m[1];
1907
+ if (m[2] < box.min.z)
1908
+ box.min.z = m[2];
1909
+ if (m[0] > box.max.x)
1910
+ box.max.x = m[0];
1911
+ if (m[1] > box.max.y)
1912
+ box.max.y = m[1];
1913
+ if (m[2] > box.max.z)
1914
+ box.max.z = m[2];
1326
1915
  }
1327
1916
  const setSide = (front) => {
1328
1917
  matF.side = front ? THREE.FrontSide : THREE.DoubleSide;
@@ -1339,13 +1928,26 @@ function buildLdrScene(insts, pack, P, color, mark) {
1339
1928
  for (const s of studMeshes) {
1340
1929
  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/"));
1341
1930
  if (k2)
1342
- s.im.geometry = geom(alt.header.studKinds[k2].body, alt.arr);
1931
+ s.im.geometry = geom(alt.header.studKinds[k2].body, alt.arr, Q);
1343
1932
  }
1344
1933
  return () => studMeshes.forEach((s, i) => s.im.geometry = saved[i]);
1345
1934
  };
1346
- return { root, stats, setCull, setSide, setEdges, setCond, swapStuds, box, texturesReady };
1935
+ const setLod = (on) => {
1936
+ root.enabled = on && LOD;
1937
+ if (!root.enabled) {
1938
+ for (const c of root.clusters) {
1939
+ c.cur = 0;
1940
+ c.edgeOk = true;
1941
+ }
1942
+ refresh();
1943
+ }
1944
+ };
1945
+ insts = null;
1946
+ cull = null;
1947
+ byPart.clear();
1948
+ return { root, stats, setCull, setSide, setEdges, setCond, swapStuds, setLod, setStep, box, texturesReady };
1347
1949
  }
1348
- // src/scene.ts
1950
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/scene.ts
1349
1951
  import * as THREE2 from "three";
1350
1952
  function colorFn(src, pack, fallback) {
1351
1953
  const table = new Map;
@@ -1430,11 +2032,11 @@ function createLdrObject(instances, pack, options = {}) {
1430
2032
  }
1431
2033
  cur = null;
1432
2034
  };
1433
- const build = (step) => {
2035
+ const multiStep = uniq.length > 1;
2036
+ const drawnAt = (step) => all.filter((i, k) => (step == null || stepOf[k] <= step) && pack.header.parts[i.part]).length;
2037
+ const build = () => {
1434
2038
  const t0 = performance.now();
1435
- disposeCur();
1436
- const insts = step == null ? all : all.filter((_, k) => stepOf[k] <= step);
1437
- cur = buildLdrScene(insts, pack, { pack: "", cullStuds: cs, cullUnder: cu, edges: edges ? "idx" : false, side, cond, condStuds: o.conditionalStudLines !== false, tex: o.textures !== false }, color, () => {});
2039
+ cur = buildLdrScene(all, pack, { pack: "", cullStuds: cs, cullUnder: cu, edges: edges ? "idx" : false, side, cond, condStuds: o.conditionalStudLines !== false, tex: o.textures !== false, lod: o.lod !== false, lodPx: o.lodPx, lodEdgePx: o.lodEdgePx, steps: multiStep ? stepOf : undefined }, color, () => {});
1438
2040
  pairs.clear();
1439
2041
  if (o.material === "plastic")
1440
2042
  cur.root.traverse((x) => {
@@ -1449,17 +2051,25 @@ function createLdrObject(instances, pack, options = {}) {
1449
2051
  if (!cond)
1450
2052
  cur.setCond(false);
1451
2053
  object.add(cur.root);
1452
- 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 });
2054
+ Object.assign(stats, { drawn: drawnAt(null), 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, clusters: cur.stats.clusters ?? 0, buildMs: performance.now() - t0 });
1453
2055
  api.texturesReady = cur.texturesReady;
1454
2056
  };
2057
+ const setStep = (step) => {
2058
+ if (!multiStep)
2059
+ return;
2060
+ const t0 = performance.now();
2061
+ Object.assign(stats, cur.setStep(step), { drawn: drawnAt(step), buildMs: performance.now() - t0 });
2062
+ };
1455
2063
  const api = { texturesReady: Promise.resolve() };
1456
- build(o.step ?? null);
2064
+ build();
2065
+ if (o.step != null)
2066
+ setStep(o.step);
1457
2067
  return Object.assign(api, {
1458
2068
  object,
1459
2069
  stats,
1460
2070
  stepCount: uniq.length,
1461
2071
  stepViews,
1462
- setStep: (s) => build(s),
2072
+ setStep,
1463
2073
  setEdges: (v) => {
1464
2074
  edges = v;
1465
2075
  cur.setEdges(v);
@@ -1473,6 +2083,7 @@ function createLdrObject(instances, pack, options = {}) {
1473
2083
  cu = b;
1474
2084
  cur.setCull(a, b);
1475
2085
  },
2086
+ setLod: (v) => cur.setLod(v),
1476
2087
  setSide: (s) => {
1477
2088
  side = s;
1478
2089
  cur.setSide(s === "front");
@@ -1485,7 +2096,7 @@ function createLdrObject(instances, pack, options = {}) {
1485
2096
  object.updateMatrixWorld(true);
1486
2097
  target.makeEmpty();
1487
2098
  object.traverse((x) => {
1488
- if (x.isInstancedMesh && x.count) {
2099
+ if (x.isInstancedMesh && x.count && !x.userData.ldrLod) {
1489
2100
  x.computeBoundingBox();
1490
2101
  const b = x.boundingBox.clone().applyMatrix4(x.matrixWorld);
1491
2102
  target.union(b);
@@ -1496,7 +2107,7 @@ function createLdrObject(instances, pack, options = {}) {
1496
2107
  dispose: disposeCur
1497
2108
  });
1498
2109
  }
1499
- // src/pack-io.ts
2110
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/pack-io.ts
1500
2111
  import { decompress } from "fzstd";
1501
2112
  async function decompressPack(data) {
1502
2113
  const u = data instanceof Uint8Array ? data : new Uint8Array(data);
@@ -1507,7 +2118,147 @@ async function decompressPack(data) {
1507
2118
  return new Response(new Blob([u]).stream().pipeThrough(new DecompressionStream("gzip"))).arrayBuffer();
1508
2119
  return own(u);
1509
2120
  }
1510
- // src/node/library.ts
2121
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/cdn.ts
2122
+ import { decompress as decompress2 } from "fzstd";
2123
+ var DEFAULT_PARTS_CDN = "https://ldr-demo.vercel.app/cdn/v1/";
2124
+ var indexCache = new Map;
2125
+ var shardCache = new Map;
2126
+ function loadPartsIndex(cdn = DEFAULT_PARTS_CDN, f = fetch) {
2127
+ let p = indexCache.get(cdn);
2128
+ if (!p) {
2129
+ p = f(cdn + "parts.json").then((r) => {
2130
+ if (!r.ok)
2131
+ throw new Error(`ldr: parts CDN index ${r.status} (${cdn}parts.json)`);
2132
+ return r.json();
2133
+ });
2134
+ p.catch(() => indexCache.delete(cdn));
2135
+ indexCache.set(cdn, p);
2136
+ }
2137
+ return p;
2138
+ }
2139
+ var isRange = (v) => Array.isArray(v) && v.length === 3 && typeof v[0] === "number" && typeof v[1] === "number" && (v[2] === "f32" || v[2] === "u16" || v[2] === "u8" || v[2] === "u32");
2140
+ var rebase = (v, d) => isRange(v) ? [v[0] + d, v[1], v[2]] : Array.isArray(v) ? v.map((x) => rebase(x, d)) : v && typeof v === "object" ? Object.fromEntries(Object.entries(v).map(([k, x]) => [k, rebase(x, d)])) : v;
2141
+ function mergePacks(packs, extra = {}) {
2142
+ const payloads = [];
2143
+ let off = 0;
2144
+ const parts = {}, studKinds = {};
2145
+ for (const p of packs) {
2146
+ const buf = p.buffer;
2147
+ if (!buf)
2148
+ throw new Error("ldr: mergePacks needs packs from decodePack/loadPack");
2149
+ const hl = new DataView(buf).getUint32(0, true);
2150
+ const pay = new Uint8Array(buf, 4 + hl);
2151
+ const d = off;
2152
+ payloads.push(pay);
2153
+ off += pay.byteLength;
2154
+ const pad = (4 - off % 4) % 4;
2155
+ if (pad) {
2156
+ payloads.push(new Uint8Array(pad));
2157
+ off += pad;
2158
+ }
2159
+ for (const [k, v] of Object.entries(p.header.parts ?? {}))
2160
+ if (!(k in parts))
2161
+ parts[k] = rebase(v, d);
2162
+ for (const [k, v] of Object.entries(p.header.studKinds ?? {}))
2163
+ if (!(k in studKinds))
2164
+ studKinds[k] = rebase(v, d);
2165
+ }
2166
+ const h0 = packs[0]?.header ?? { version: 1, weld: true };
2167
+ const header = new TextEncoder().encode(JSON.stringify({ ...h0, parts, studKinds, ...extra }));
2168
+ const hpad = (4 - (4 + header.length) % 4) % 4;
2169
+ const base = 4 + header.length + hpad;
2170
+ const out = new Uint8Array(base + off);
2171
+ new DataView(out.buffer).setUint32(0, header.length + hpad, true);
2172
+ out.set(header, 4);
2173
+ out.fill(32, 4 + header.length, base);
2174
+ let o = base;
2175
+ for (const p of payloads) {
2176
+ out.set(p, o);
2177
+ o += p.byteLength;
2178
+ }
2179
+ return decodePack(out.buffer);
2180
+ }
2181
+ async function loadParts(names, o = {}) {
2182
+ const cdn = o.cdn ?? DEFAULT_PARTS_CDN, f = o.fetch ?? fetch, gap = o.gap ?? 65536;
2183
+ const idx = await loadPartsIndex(cdn, f);
2184
+ const want = [...new Set([...names].map(norm))];
2185
+ const missing = [];
2186
+ const todo = [];
2187
+ for (const n of want) {
2188
+ if (shardCache.has(cdn + n))
2189
+ continue;
2190
+ const e = idx.parts[n];
2191
+ if (e)
2192
+ todo.push({ name: n, off: e[0], len: e[1], file: e[2] ?? 0 });
2193
+ else
2194
+ missing.push(n);
2195
+ }
2196
+ todo.sort((a, b) => a.file - b.file || a.off - b.off);
2197
+ const groups = [];
2198
+ for (const t of todo) {
2199
+ const g = groups[groups.length - 1];
2200
+ if (g && g.file === t.file && t.off - g.end <= gap) {
2201
+ g.end = Math.max(g.end, t.off + t.len);
2202
+ g.items.push(t);
2203
+ } else
2204
+ groups.push({ file: t.file, start: t.off, end: t.off + t.len, items: [t] });
2205
+ }
2206
+ let bytes = 0;
2207
+ let next = 0;
2208
+ const worker = async () => {
2209
+ while (next < groups.length) {
2210
+ const g = groups[next++];
2211
+ const r = await f(new URL(idx.archives[g.file], cdn).href, { headers: { Range: `bytes=${g.start}-${g.end - 1}` } });
2212
+ if (!r.ok)
2213
+ throw new Error(`ldr: parts CDN ${r.status}`);
2214
+ let buf = new Uint8Array(await r.arrayBuffer());
2215
+ if (r.status === 200)
2216
+ buf = buf.subarray(g.start, g.end);
2217
+ bytes += buf.byteLength;
2218
+ for (const t of g.items) {
2219
+ const raw = decompress2(buf.subarray(t.off - g.start, t.off - g.start + t.len));
2220
+ const ab = raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength);
2221
+ shardCache.set(cdn + t.name, decodePack(ab));
2222
+ }
2223
+ }
2224
+ };
2225
+ await Promise.all(Array.from({ length: Math.min(o.concurrency ?? 6, groups.length) }, worker));
2226
+ const shards = want.filter((n) => shardCache.has(cdn + n)).map((n) => shardCache.get(cdn + n));
2227
+ const pack = shards.length ? mergePacks(shards, { colors: idx.colors }) : decodePack(emptyPack(idx.colors));
2228
+ return { pack, missing, requests: groups.length, bytes };
2229
+ }
2230
+ function emptyPack(colors) {
2231
+ const h = new TextEncoder().encode(JSON.stringify({ version: 1, weld: true, parts: {}, studKinds: {}, colors }));
2232
+ const pad = (4 - (4 + h.length) % 4) % 4;
2233
+ const b = new Uint8Array(4 + h.length + pad).fill(32);
2234
+ new DataView(b.buffer).setUint32(0, h.length + pad, true);
2235
+ b.set(h, 4);
2236
+ return b.buffer;
2237
+ }
2238
+ async function loadModel(source, o = {}) {
2239
+ const t0 = performance.now();
2240
+ const f = o.fetch ?? fetch;
2241
+ const idxP = loadPartsIndex(o.cdn ?? DEFAULT_PARTS_CDN, f);
2242
+ const text = o.text ? source : await f(source).then((r) => {
2243
+ if (!r.ok)
2244
+ throw new Error(`ldr: ${r.status} fetching ${source}`);
2245
+ return r.text();
2246
+ });
2247
+ const t1 = performance.now();
2248
+ const name = o.name ?? (o.text ? "main.ldr" : decodeURIComponent(source.split(/[?#]/)[0].split("/").pop() || "main.ldr"));
2249
+ const insts = parseModel(text, name, { looseGeometry: false });
2250
+ const t2 = performance.now();
2251
+ await idxP;
2252
+ const { pack, missing, requests, bytes } = await loadParts(insts.filter((i) => !i.embedded).map((i) => i.part), o);
2253
+ const t3 = performance.now();
2254
+ const obj = createLdrObject(insts, pack, o);
2255
+ for (const m of missing)
2256
+ if (!obj.stats.missing.includes(m))
2257
+ obj.stats.missing.push(m);
2258
+ const t4 = performance.now();
2259
+ return Object.assign(obj, { pack, instances: insts, requests, bytes, timings: { fetchModel: t1 - t0, parse: t2 - t1, parts: t3 - t2, build: t4 - t3, total: t4 - t0 } });
2260
+ }
2261
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/node/library.ts
1511
2262
  import { existsSync, readFileSync } from "fs";
1512
2263
  import { join } from "path";
1513
2264
  var xf = (m, x, y, z) => [m[3] * x + m[4] * y + m[5] * z + m[0], m[6] * x + m[7] * y + m[8] * z + m[1], m[9] * x + m[10] * y + m[11] * z + m[2]];
@@ -1759,11 +2510,11 @@ class Library {
1759
2510
  return insts;
1760
2511
  }
1761
2512
  }
1762
- // src/node/build-pack.ts
2513
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/node/build-pack.ts
1763
2514
  import fs3 from "node:fs";
1764
2515
  import path3 from "node:path";
1765
2516
 
1766
- // src/node/resolver.ts
2517
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/node/resolver.ts
1767
2518
  import fs from "node:fs";
1768
2519
  import path from "node:path";
1769
2520
  var TOP_STUD_RE = /^(stud|studa|stud2|stud2a|stud6|stud6a|stud10|stud15|stud2s|stud2s2|stud-logo\d*|stud2-logo\d*)\.dat$/i;
@@ -2035,9 +2786,118 @@ function collectDeps(lib, refs) {
2035
2786
  return out;
2036
2787
  }
2037
2788
 
2038
- // src/node/compile.ts
2789
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/node/compile.ts
2039
2790
  import fs2 from "node:fs";
2040
2791
  import path2 from "node:path";
2792
+
2793
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/node/lod.ts
2794
+ import { MeshoptSimplifier, MeshoptEncoder } from "meshoptimizer";
2795
+ await Promise.all([MeshoptSimplifier.ready, MeshoptEncoder.ready]);
2796
+ function reorderMesh(P, N, C, I) {
2797
+ const idx = Uint32Array.from(I);
2798
+ const n = P.length / 3;
2799
+ const [remap] = MeshoptEncoder.reorderMesh(idx, true, false);
2800
+ const P2 = new Float32Array(P.length), N2 = new Float32Array(N.length), C2 = new Float32Array(C.length);
2801
+ for (let v = 0;v < n; v++) {
2802
+ const r = remap[v];
2803
+ if (r === 4294967295)
2804
+ continue;
2805
+ P2[r * 3] = P[v * 3];
2806
+ P2[r * 3 + 1] = P[v * 3 + 1];
2807
+ P2[r * 3 + 2] = P[v * 3 + 2];
2808
+ N2[r * 3] = N[v * 3];
2809
+ N2[r * 3 + 1] = N[v * 3 + 1];
2810
+ N2[r * 3 + 2] = N[v * 3 + 2];
2811
+ for (let k = 0;k < 4; k++)
2812
+ C2[r * 4 + k] = C[v * 4 + k];
2813
+ }
2814
+ return { P: P2, N: N2, C: C2, I: idx };
2815
+ }
2816
+ var TARGETS = [0.25, 0.08];
2817
+ var MIN_TRIS = 64;
2818
+ function flatMesh(tris) {
2819
+ const map = new Map;
2820
+ const P = [], N = [], C = [], I = [];
2821
+ for (const { p, i: idx, col } of tris)
2822
+ for (let t = 0;t < idx.length; t += 3) {
2823
+ const a = idx[t] * 3, b = idx[t + 1] * 3, c = idx[t + 2] * 3;
2824
+ const ux = p[b] - p[a], uy = p[b + 1] - p[a + 1], uz = p[b + 2] - p[a + 2], vx = p[c] - p[a], vy = p[c + 1] - p[a + 1], vz = p[c + 2] - p[a + 2];
2825
+ let nx = uy * vz - uz * vy, ny = uz * vx - ux * vz, nz = ux * vy - uy * vx;
2826
+ const l = Math.hypot(nx, ny, nz);
2827
+ if (!(l > 0.000000001))
2828
+ continue;
2829
+ nx /= l;
2830
+ ny /= l;
2831
+ nz /= l;
2832
+ for (const o of [a, b, c]) {
2833
+ const k = `${p[o].toFixed(3)},${p[o + 1].toFixed(3)},${p[o + 2].toFixed(3)},${nx.toFixed(3)},${ny.toFixed(3)},${nz.toFixed(3)},${col.join()}`;
2834
+ let j = map.get(k);
2835
+ if (j === undefined) {
2836
+ j = P.length / 3;
2837
+ map.set(k, j);
2838
+ P.push(p[o], p[o + 1], p[o + 2]);
2839
+ N.push(nx, ny, nz);
2840
+ C.push(...col);
2841
+ }
2842
+ I.push(j);
2843
+ }
2844
+ }
2845
+ return { P: new Float32Array(P), N: new Float32Array(N), C: new Float32Array(C), I: new Uint32Array(I) };
2846
+ }
2847
+ function lodChain(P, C, I) {
2848
+ const nTris = I.length / 3;
2849
+ if (nTris < MIN_TRIS)
2850
+ return [];
2851
+ const groups = new Map;
2852
+ for (let t = 0;t < I.length; t += 3) {
2853
+ const v = I[t] * 4;
2854
+ const col = [C[v], C[v + 1], C[v + 2], C[v + 3]];
2855
+ const k = col.join();
2856
+ let g = groups.get(k);
2857
+ if (!g)
2858
+ groups.set(k, g = { col, tri: [] });
2859
+ g.tri.push(I[t], I[t + 1], I[t + 2]);
2860
+ }
2861
+ const welded = [...groups.values()].map(({ col, tri }) => {
2862
+ const map = new Map;
2863
+ const pos = [];
2864
+ const idx = new Uint32Array(tri.length);
2865
+ for (let i = 0;i < tri.length; i++) {
2866
+ const o = tri[i] * 3;
2867
+ const k = `${P[o].toFixed(3)},${P[o + 1].toFixed(3)},${P[o + 2].toFixed(3)}`;
2868
+ let j = map.get(k);
2869
+ if (j === undefined) {
2870
+ j = pos.length / 3;
2871
+ map.set(k, j);
2872
+ pos.push(P[o], P[o + 1], P[o + 2]);
2873
+ }
2874
+ idx[i] = j;
2875
+ }
2876
+ return { col, p: new Float32Array(pos), i: idx };
2877
+ });
2878
+ const out = [];
2879
+ let prevTris = nTris;
2880
+ for (let li = 0;li < TARGETS.length; li++) {
2881
+ let err = 0;
2882
+ const parts = [];
2883
+ for (const g of welded) {
2884
+ const target = Math.max(3, Math.floor(g.i.length * TARGETS[li] / 3) * 3);
2885
+ const [idx, e] = MeshoptSimplifier.simplify(g.i, g.p, 3, target, 1e9, li ? ["ErrorAbsolute", "Prune"] : ["ErrorAbsolute"]);
2886
+ err = Math.max(err, e);
2887
+ if (idx.length)
2888
+ parts.push({ p: g.p, i: idx, col: g.col });
2889
+ }
2890
+ const m = flatMesh(parts);
2891
+ const t = m.I.length / 3;
2892
+ if (t > prevTris * 0.7 || !t)
2893
+ continue;
2894
+ out.push({ ...m, err: +Math.max(err, 0.001).toFixed(3) });
2895
+ prevTris = t;
2896
+ }
2897
+ return out;
2898
+ }
2899
+
2900
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/node/compile.ts
2041
2901
  function loadColors(root) {
2042
2902
  const map = new Map;
2043
2903
  for (const l of fs2.readFileSync(path2.join(root, "LDConfig.ldr"), "latin1").split(/\r?\n/)) {
@@ -2454,7 +3314,8 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
2454
3314
  return r;
2455
3315
  };
2456
3316
  let lastPos = [];
2457
- const bucket = (b) => {
3317
+ let anyLod = false;
3318
+ const bucket = (b, lod = false) => {
2458
3319
  const n = b.pos.length / 3;
2459
3320
  lastPos = [];
2460
3321
  if (!n)
@@ -2493,9 +3354,20 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
2493
3354
  }
2494
3355
  I.push(j);
2495
3356
  }
2496
- const vcount = P.length / 3;
2497
- lastPos = P;
2498
- return { pos: put(new Float32Array(P)), nrm: put(new Float32Array(N)), col: put(new Float32Array(C)), idx: put(vcount < 65536 ? new Uint16Array(I) : new Uint32Array(I)), tris: n / 3 };
3357
+ let m = { P, N, C, I };
3358
+ if (options.reorder && I.length >= 3)
3359
+ m = reorderMesh(P, N, C, I);
3360
+ const vcount = m.P.length / 3;
3361
+ lastPos = m.P;
3362
+ const out = { pos: put(Float32Array.from(m.P)), nrm: put(Float32Array.from(m.N)), col: put(Float32Array.from(m.C)), idx: put(vcount < 65536 ? Uint16Array.from(m.I) : Uint32Array.from(m.I)), tris: n / 3 };
3363
+ if (lod && options.lod) {
3364
+ const chain = lodChain(P, C, I).map((l) => options.reorder ? { ...l, ...reorderMesh(l.P, l.N, l.C, l.I) } : l);
3365
+ if (chain.length) {
3366
+ anyLod = true;
3367
+ out.lod = chain.map((l) => ({ pos: put(l.P), col: put(Uint8Array.from(l.C, (x) => Math.round(x * 255))), idx: put(l.P.length / 3 < 65536 ? new Uint16Array(l.I) : l.I), tris: l.I.length / 3, err: l.err }));
3368
+ }
3369
+ }
3370
+ return out;
2499
3371
  };
2500
3372
  let anyCond = false;
2501
3373
  const condl = (a, pools) => {
@@ -2586,18 +3458,18 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
2586
3458
  });
2587
3459
  return { tbody: bucket(c.tbody), tex };
2588
3460
  };
2589
- const enc = (c) => {
2590
- const body = bucket(c.body), bodyP = lastPos, under = bucket(c.under), underP = lastPos;
3461
+ const enc = (c, lod = false) => {
3462
+ const body = bucket(c.body, lod), bodyP = lastPos, under = bucket(c.under, lod), underP = lastPos;
2591
3463
  const edges = c.edges.length ? put(new Float32Array(c.edges)) : null;
2592
3464
  const condlB = condl(c.cond, [bodyP, underP, c.edges]), condlU = condl(c.condUnder, [bodyP, underP, c.edges]);
2593
3465
  return { body, under, edges, cond: c.condLines, condl: condlB, condlu: condlU, studs: c.studs.map((s) => [s.kind, s.color, ...s.m.map((x) => +x.toFixed(4))]), bbox: c.bbox, fullBottom: c.fullBottom, uncert: !!c.uncertified, mask: c.bottomMask ? [c.bottomMask.nx, c.bottomMask.nz, c.bottomMask.bits.join("")] : null, ...encTex(c) };
2594
3466
  };
2595
- const encParts = Object.fromEntries([...parts].map(([k, c]) => [k, enc(c)])), encKinds = Object.fromEntries([...studKinds].map(([k, c]) => [k, enc(c)]));
3467
+ const encParts = Object.fromEntries([...parts].map(([k, c]) => [k, enc(c, true)])), encKinds = Object.fromEntries([...studKinds].map(([k, c]) => [k, enc(c)]));
2596
3468
  const textures = texNames.map((name) => {
2597
3469
  const b = options.texture?.(name);
2598
3470
  return b ? { name, mime: "image/png", data: put(b) } : { name, missing: true };
2599
3471
  });
2600
- const features = [...anyCond ? ["cond"] : [], ...texNames.length ? ["tex"] : []];
3472
+ const features = [...anyCond ? ["cond"] : [], ...texNames.length ? ["tex"] : [], ...anyLod ? ["lod"] : []];
2601
3473
  const header = { version: features.length ? 2 : 1, ...features.length ? { features } : {}, weld, parts: encParts, studKinds: encKinds, ...texNames.length ? { textures } : {}, ...extra };
2602
3474
  const hj = new TextEncoder().encode(JSON.stringify(header));
2603
3475
  const hpad = (4 - (4 + hj.length) % 4) % 4;
@@ -2608,7 +3480,7 @@ function encodePack(parts, studKinds, weld, extra, options = {}) {
2608
3480
  return Buffer.concat([head, ...chunks.map((c) => new Uint8Array(c.buffer, c.byteOffset, c.byteLength))]);
2609
3481
  }
2610
3482
 
2611
- // src/node/build-pack.ts
3483
+ // ../../../../../../../tmp/ldr-build-qIdKSv/src/node/build-pack.ts
2612
3484
  function buildPack(o) {
2613
3485
  const lib = new Library2(o.ldrawRoot);
2614
3486
  const refs = new Map;
@@ -2668,10 +3540,11 @@ function buildPack(o) {
2668
3540
  return b ? new Uint8Array(Buffer.from(b, "base64")) : comp.texture(n);
2669
3541
  };
2670
3542
  const warnings = [...lib.conflicts].map((n) => `${n}: defined differently by two models; the first definition is used`);
2671
- return { data: encodePack(parts, kinds, comp.opts.weld, extra, { texture }), parts: parts.size, studKinds: kinds.size, missing: [...missing], ...warnings.length ? { warnings } : {} };
3543
+ return { data: encodePack(parts, kinds, comp.opts.weld, extra, { texture, lod: o.lod !== false, reorder: !!o.reorder }), parts: parts.size, studKinds: kinds.size, missing: [...missing], ...warnings.length ? { warnings } : {} };
2672
3544
  }
2673
3545
  export {
2674
3546
  Compiler,
3547
+ DEFAULT_PARTS_CDN,
2675
3548
  I as IDENTITY,
2676
3549
  Library,
2677
3550
  Library2 as ResolverLibrary,
@@ -2685,6 +3558,7 @@ export {
2685
3558
  createInstancedConditionalEdges,
2686
3559
  createInstancedEdges,
2687
3560
  createLdrObject,
3561
+ cullCore,
2688
3562
  decodePack,
2689
3563
  decompressPack,
2690
3564
  encodePack,
@@ -2692,16 +3566,21 @@ export {
2692
3566
  fileStepViews,
2693
3567
  flatten,
2694
3568
  flatten2 as flattenWithLibrary,
3569
+ forEachStud,
2695
3570
  hash32,
2696
3571
  invert,
2697
3572
  isDirectColor,
2698
3573
  loadColors,
3574
+ loadModel,
2699
3575
  loadPack,
3576
+ loadParts,
3577
+ loadPartsIndex,
2700
3578
  looseFile,
2701
3579
  loosePartName,
2702
3580
  looseSource,
2703
3581
  looseSteps,
2704
3582
  makeMaterial,
3583
+ mergePacks,
2705
3584
  mul,
2706
3585
  norm,
2707
3586
  parse,