ldr 0.4.1 → 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.
package/dist/index.js CHANGED
@@ -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]);
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;
1036
1246
  }
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);
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));
1043
1265
  }
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))
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))
1077
1313
  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]));
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;
1085
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);
1327
+ return;
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);
1145
1611
  root.add(l);
1146
1612
  return l;
1147
1613
  };
1148
- const stats = { studsTotal: cull.studs.length, studsHidden: cull.studs.filter((s) => s.hidden).length, undersTotal: 0, undersHidden: 0 };
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);
1630
+ root.add(l);
1631
+ return l;
1632
+ };
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,148 @@ 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
  }
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
+ }
1510
2261
  export {
2262
+ DEFAULT_PARTS_CDN,
1511
2263
  I as IDENTITY,
1512
2264
  buildLdrScene,
1513
2265
  colorToken,
@@ -1517,20 +2269,26 @@ export {
1517
2269
  createInstancedConditionalEdges,
1518
2270
  createInstancedEdges,
1519
2271
  createLdrObject,
2272
+ cullCore,
1520
2273
  decodePack,
1521
2274
  decompressPack,
1522
2275
  fileKind,
1523
2276
  fileStepViews,
1524
2277
  flatten,
2278
+ forEachStud,
1525
2279
  hash32,
1526
2280
  invert,
1527
2281
  isDirectColor,
2282
+ loadModel,
1528
2283
  loadPack,
2284
+ loadParts,
2285
+ loadPartsIndex,
1529
2286
  looseFile,
1530
2287
  loosePartName,
1531
2288
  looseSource,
1532
2289
  looseSteps,
1533
2290
  makeMaterial,
2291
+ mergePacks,
1534
2292
  mul,
1535
2293
  norm,
1536
2294
  parse,