ldr 0.4.1 → 0.5.1

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-pIke9r/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-pIke9r/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-pIke9r/src/render.ts
772
772
  import * as THREE from "three";
773
773
  async function loadPack(url) {
774
774
  const res = await fetch(url);
@@ -796,29 +796,71 @@ 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) {
815
856
  const m = new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 0.45, side });
816
857
  m.onBeforeCompile = (s) => {
817
858
  s.vertexShader = `attribute vec4 ldrColor;
818
- ` + s.vertexShader.replace("#include <color_vertex>", `#ifdef USE_INSTANCING_COLOR
819
- vColor = vec4(ldrColor.a > 0.5 ? pow(ldrColor.rgb, vec3(2.2)) : instanceColor.rgb, 1.0); // baked LDConfig colours are sRGB
859
+ ` + s.vertexShader.replace("#include <color_vertex>", `#include <color_vertex>
860
+ #ifdef USE_INSTANCING_COLOR
861
+ vColor.rgb = ldrColor.a > 0.5 ? pow(ldrColor.rgb, vec3(2.2)) : instanceColor.rgb; // baked LDConfig colours are sRGB
820
862
  #else
821
- vColor = vec4(pow(ldrColor.rgb, vec3(2.2)), 1.0);
863
+ vColor.rgb = pow(ldrColor.rgb, vec3(2.2));
822
864
  #endif`);
823
865
  };
824
866
  m.customProgramCacheKey = () => "ldrColor";
@@ -1012,120 +1054,544 @@ function instancedLines(pos, mats, count) {
1012
1054
  l.frustumCulled = false;
1013
1055
  return l;
1014
1056
  }
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)];
1057
+ function writeM4(t, o, m, mo = 0) {
1058
+ t[o] = m[mo + 3];
1059
+ t[o + 1] = m[mo + 6];
1060
+ t[o + 2] = m[mo + 9];
1061
+ t[o + 3] = 0;
1062
+ t[o + 4] = m[mo + 4];
1063
+ t[o + 5] = m[mo + 7];
1064
+ t[o + 6] = m[mo + 10];
1065
+ t[o + 7] = 0;
1066
+ t[o + 8] = m[mo + 5];
1067
+ t[o + 9] = m[mo + 8];
1068
+ t[o + 10] = m[mo + 11];
1069
+ t[o + 11] = 0;
1070
+ t[o + 12] = m[mo];
1071
+ t[o + 13] = m[mo + 1];
1072
+ t[o + 14] = m[mo + 2];
1073
+ t[o + 15] = 1;
1074
+ }
1075
+ function newIM(g, mat, n) {
1076
+ const im = new THREE.InstancedMesh(g, mat, 0);
1077
+ im.instanceMatrix = new THREE.InstancedBufferAttribute(new Float32Array(n * 16), 16);
1078
+ im.count = n;
1079
+ return im;
1080
+ }
1081
+ function fillInstances(im, ids, insts, color) {
1082
+ const M = im.instanceMatrix.array;
1083
+ const C = new Float32Array(ids.length * 3);
1084
+ for (let k = 0;k < ids.length; k++) {
1085
+ const it = insts[ids[k]];
1086
+ writeM4(M, k * 16, it.m);
1087
+ color(it.color).toArray(C, k * 3);
1088
+ }
1089
+ im.instanceColor = new THREE.InstancedBufferAttribute(C, 3);
1090
+ }
1091
+ 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;
1092
+ var prepCache = new WeakMap;
1093
+ function prepPart(H, part) {
1094
+ let c = prepCache.get(H);
1095
+ if (!c)
1096
+ prepCache.set(H, c = new Map);
1097
+ let r = c.get(part);
1098
+ if (r !== undefined)
1099
+ return r;
1100
+ const p = H.parts[part];
1101
+ if (!p) {
1102
+ c.set(part, null);
1103
+ return null;
1104
+ }
1105
+ const n = p.studs.length;
1106
+ const studM = new Float64Array(n * 12);
1107
+ const kind = [], color = [];
1108
+ p.studs.forEach((s, i) => {
1109
+ kind.push(s[0]);
1110
+ color.push(s[1]);
1111
+ for (let k = 0;k < 12; k++)
1112
+ studM[i * 12 + k] = s[2 + k];
1113
+ });
1114
+ let mask = null, nx = 0, nz = 0;
1115
+ if (p.mask) {
1116
+ nx = p.mask[0];
1117
+ nz = p.mask[1];
1118
+ const bits = p.mask[2];
1119
+ mask = new Uint8Array(bits.length);
1120
+ for (let i = 0;i < bits.length; i++)
1121
+ mask[i] = bits.charCodeAt(i) === 49 ? 1 : 0;
1122
+ }
1123
+ c.set(part, r = { nStuds: n, studM, kind, color, mask, nx, nz });
1124
+ return r;
1125
+ }
1126
+ function mul12To(a, b, bo, w, ao = 0) {
1127
+ const b0 = b[bo], b1 = b[bo + 1], b2 = b[bo + 2];
1128
+ w[0] = a[ao] + a[ao + 3] * b0 + a[ao + 4] * b1 + a[ao + 5] * b2;
1129
+ w[1] = a[ao + 1] + a[ao + 6] * b0 + a[ao + 7] * b1 + a[ao + 8] * b2;
1130
+ w[2] = a[ao + 2] + a[ao + 9] * b0 + a[ao + 10] * b1 + a[ao + 11] * b2;
1131
+ for (let i = 0;i < 3; i++)
1132
+ for (let j = 0;j < 3; j++)
1133
+ 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];
1134
+ }
1135
+ 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]);
1136
+ 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);
1137
+ var topKey = (x, y, z) => {
1138
+ const rx = Math.round(x), ry = Math.round(y), rz = Math.round(z);
1139
+ return rx < -131072 || rx >= 131072 || ry < -65536 || ry >= 65536 || rz < -131072 || rz >= 131072 ? -1 : (rx + 131072) * 34359738368 + (ry + 65536) * 262144 + (rz + 131072);
1019
1140
  };
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;
1141
+
1142
+ class NumTable {
1143
+ keys;
1144
+ vals;
1145
+ mask;
1146
+ size = 0;
1147
+ constructor(cap = 1024) {
1148
+ let n = 1024;
1149
+ while (n < cap * 2)
1150
+ n *= 2;
1151
+ this.keys = new Float64Array(n).fill(-1);
1152
+ this.vals = new Int32Array(n);
1153
+ this.mask = n - 1;
1154
+ }
1155
+ slot(k) {
1156
+ const lo = k >>> 0, hi = k / 4294967296 >>> 0;
1157
+ let h = Math.imul(lo ^ Math.imul(hi, 2654435761), 2246822507);
1158
+ h ^= h >>> 15;
1159
+ return h & this.mask;
1160
+ }
1161
+ get(k) {
1162
+ const K = this.keys;
1163
+ let i = this.slot(k);
1164
+ for (;; ) {
1165
+ const x = K[i];
1166
+ if (x === k)
1167
+ return this.vals[i];
1168
+ if (x === -1)
1169
+ return -1;
1170
+ i = i + 1 & this.mask;
1171
+ }
1172
+ }
1173
+ put(k, v) {
1174
+ if ((this.size + 1) * 2 > this.keys.length)
1175
+ this.grow();
1176
+ const K = this.keys;
1177
+ let i = this.slot(k);
1178
+ for (;; ) {
1179
+ const x = K[i];
1180
+ if (x === k) {
1181
+ const o = this.vals[i];
1182
+ this.vals[i] = v;
1183
+ return o;
1184
+ }
1185
+ if (x === -1) {
1186
+ K[i] = k;
1187
+ this.vals[i] = v;
1188
+ this.size++;
1189
+ return -1;
1190
+ }
1191
+ i = i + 1 & this.mask;
1192
+ }
1193
+ }
1194
+ grow() {
1195
+ const ok = this.keys, ov = this.vals;
1196
+ const n = ok.length * 2;
1197
+ this.keys = new Float64Array(n).fill(-1);
1198
+ this.vals = new Int32Array(n);
1199
+ this.mask = n - 1;
1200
+ this.size = 0;
1201
+ for (let i = 0;i < ok.length; i++)
1202
+ if (ok[i] !== -1)
1203
+ this.put(ok[i], ov[i]);
1204
+ }
1205
+ }
1206
+ var STEP_NEVER = 2147483647;
1207
+ function forEachStud(insts, H, fn) {
1208
+ const w = new Float64Array(12);
1209
+ let j = 0;
1210
+ for (let idx = 0;idx < insts.length; idx++) {
1211
+ const it = insts[idx];
1212
+ const pp = prepPart(H, it.part);
1213
+ if (!pp)
1214
+ continue;
1215
+ for (let s = 0;s < pp.nStuds; s++) {
1216
+ mul12To(it.m, pp.studM, s * 12, w);
1217
+ const c = pp.color[s];
1218
+ fn(j++, idx, w, pp.kind[s], c === "16" ? it.color : c, s);
1219
+ }
1220
+ }
1221
+ }
1222
+ function cullCore(insts, H, steps) {
1022
1223
  const CELL = 40;
1023
- const rects = new Map;
1024
- const lk = (y) => Math.round(y * 2) / 2;
1025
- insts.forEach((it, idx) => {
1224
+ const rx0 = [], rx1 = [], rz0 = [], rz1 = [], rInst = [], rPrep = [], rBx = [], rBz = [];
1225
+ const cells = new NumTable(insts.length * 2);
1226
+ const nextE = [], rectE = [];
1227
+ for (let idx = 0;idx < insts.length; idx++) {
1228
+ const it = insts[idx];
1229
+ const m = it.m;
1026
1230
  const p = H.parts[it.part];
1027
- if (!p || !p.mask || !upright(it.m))
1028
- return;
1231
+ if (!p || !p.mask || !upright(m))
1232
+ continue;
1029
1233
  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]);
1234
+ let x0 = Infinity, x1 = -Infinity, z0 = Infinity, z1 = -Infinity;
1235
+ for (let cxi = 0;cxi < 2; cxi++)
1236
+ for (let czi = 0;czi < 2; czi++) {
1237
+ const x = cxi ? b[3] : b[0], z = czi ? b[5] : b[2];
1238
+ const wx = m[0] + m[3] * x + m[5] * z, wz = m[2] + m[9] * x + m[11] * z;
1239
+ if (wx < x0)
1240
+ x0 = wx;
1241
+ if (wx > x1)
1242
+ x1 = wx;
1243
+ if (wz < z0)
1244
+ z0 = wz;
1245
+ if (wz > z1)
1246
+ z1 = wz;
1036
1247
  }
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);
1248
+ const ri = rInst.length;
1249
+ rx0.push(x0);
1250
+ rx1.push(x1);
1251
+ rz0.push(z0);
1252
+ rz1.push(z1);
1253
+ rInst.push(idx);
1254
+ rPrep.push(prepPart(H, it.part));
1255
+ rBx.push(b[0]);
1256
+ rBz.push(b[2]);
1257
+ const lv2 = Math.round((m[1] + b[4]) * 2);
1258
+ for (let cx = Math.floor(x0 / CELL);cx <= Math.floor(x1 / CELL); cx++)
1259
+ for (let cz = Math.floor(z0 / CELL);cz <= Math.floor(z1 / CELL); cz++) {
1260
+ const k = cellKey(lv2, cx, cz);
1261
+ if (k < 0)
1262
+ continue;
1263
+ const e = rectE.length;
1264
+ rectE.push(ri);
1265
+ nextE.push(cells.put(k, e));
1043
1266
  }
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))
1267
+ }
1268
+ const SX = [0, 5, -5, 5, -5], SZ = [0, 5, 5, -5, -5];
1269
+ const coverStep = (x, y, z, self) => {
1270
+ const k = cellKey(Math.round(y * 2), Math.floor(x / CELL), Math.floor(z / CELL));
1271
+ if (k < 0)
1272
+ return STEP_NEVER;
1273
+ let best = STEP_NEVER;
1274
+ for (let e = cells.get(k);e >= 0; e = nextE[e]) {
1275
+ const ri = rectE[e];
1276
+ 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
1277
  continue;
1052
- const it = insts[r[4]];
1053
- const p = H.parts[it.part];
1054
- const m = it.m;
1278
+ const m = insts[rInst[ri]].m;
1279
+ const pp = rPrep[ri];
1280
+ const bx = rBx[ri], bz = rBz[ri];
1055
1281
  const dx = x - m[0], dy = y - m[1], dz = z - m[2];
1056
1282
  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;
1283
+ const { nx, nz, mask: bits } = pp;
1058
1284
  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") {
1285
+ for (let s = 0;s < 5; s++) {
1286
+ const ca = Math.floor((lx + SX[s] - bx) / 20), cb = Math.floor((lz + SZ[s] - bz) / 20);
1287
+ if (ca < 0 || cb < 0 || ca >= nx || cb >= nz || bits[ca * nz + cb] !== 1) {
1062
1288
  ok = false;
1063
1289
  break;
1064
1290
  }
1065
1291
  }
1066
- if (ok)
1067
- return true;
1292
+ if (ok) {
1293
+ if (!steps)
1294
+ return 0;
1295
+ const st = steps[rInst[ri]];
1296
+ if (st < best)
1297
+ best = st;
1298
+ }
1068
1299
  }
1069
- return false;
1300
+ return best;
1070
1301
  };
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)
1302
+ let nStuds = 0;
1303
+ for (const it of insts) {
1304
+ const pp = prepPart(H, it.part);
1305
+ if (pp)
1306
+ nStuds += pp.nStuds;
1307
+ }
1308
+ const studHidden = new Uint8Array(nStuds), studUp = new Uint8Array(nStuds);
1309
+ const tops = new NumTable(nStuds);
1310
+ let studsHidden = 0;
1311
+ const studHideStep = steps ? new Int32Array(nStuds).fill(STEP_NEVER) : undefined;
1312
+ forEachStud(insts, H, (j, idx, w) => {
1313
+ if (!upright(w))
1077
1314
  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]));
1315
+ studUp[j] = 1;
1316
+ const cs = coverStep(w[0], w[1], w[2], idx);
1317
+ if (cs !== STEP_NEVER) {
1318
+ studHidden[j] = 1;
1319
+ studsHidden++;
1320
+ if (studHideStep)
1321
+ studHideStep[j] = cs;
1085
1322
  }
1323
+ const k = topKey(w[0], w[1], w[2]);
1324
+ if (k < 0)
1325
+ return;
1326
+ if (!steps) {
1327
+ tops.put(k, 1);
1328
+ return;
1329
+ }
1330
+ const st = steps[idx], o = tops.get(k);
1331
+ if (o < 0 || st < o)
1332
+ tops.put(k, st);
1086
1333
  });
1087
- const underHidden = insts.map((it) => {
1334
+ const underHidden = new Uint8Array(insts.length);
1335
+ const underHideStep = steps ? new Int32Array(insts.length).fill(STEP_NEVER) : undefined;
1336
+ for (let idx = 0;idx < insts.length; idx++) {
1337
+ const it = insts[idx];
1088
1338
  const p = H.parts[it.part];
1089
1339
  if (!p || !p.under || !upright(it.m))
1090
- return false;
1340
+ continue;
1091
1341
  const b = p.bbox;
1092
1342
  const nx = Math.round((b[3] - b[0]) / 20), nz = Math.round((b[5] - b[2]) / 20);
1093
1343
  if (!nx || !nz || nx * nz > 4096)
1094
- return false;
1095
- for (let a = 0;a < nx; a++)
1344
+ continue;
1345
+ const m = it.m;
1346
+ let all = true, at = 0;
1347
+ for (let a = 0;a < nx && all; a++)
1096
1348
  for (let c = 0;c < nz; c++) {
1097
1349
  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;
1350
+ const x = m[0] + m[3] * lx + m[5] * lz, z = m[2] + m[9] * lx + m[11] * lz;
1351
+ const k = topKey(x, m[1] + b[4], z);
1352
+ const t = k < 0 ? -1 : tops.get(k);
1353
+ if (t < 0) {
1354
+ all = false;
1355
+ break;
1356
+ }
1357
+ if (t > at)
1358
+ at = t;
1101
1359
  }
1102
- return true;
1360
+ if (all) {
1361
+ underHidden[idx] = 1;
1362
+ if (underHideStep)
1363
+ underHideStep[idx] = at;
1364
+ }
1365
+ }
1366
+ return { nStuds, studHidden, studUp, underHidden, studsHidden, studHideStep, underHideStep };
1367
+ }
1368
+ function computeCulling(insts, H) {
1369
+ const c = cullCore(insts, H);
1370
+ const studs = new Array(c.nStuds);
1371
+ forEachStud(insts, H, (j, _i, w, kind, color) => {
1372
+ studs[j] = { kind, color, w: Array.from(w), hidden: c.studHidden[j] === 1 };
1103
1373
  });
1104
- return { studs, underHidden };
1374
+ return { studs, underHidden: Array.from(c.underHidden, (x) => x === 1) };
1375
+ }
1376
+ function gridClusters(n, x, z, k) {
1377
+ if (n <= k || !isFinite(k)) {
1378
+ const a = new Int32Array(n);
1379
+ for (let i = 0;i < n; i++)
1380
+ a[i] = i;
1381
+ return [a];
1382
+ }
1383
+ let x0 = Infinity, x1 = -Infinity, z0 = Infinity, z1 = -Infinity;
1384
+ for (let i = 0;i < n; i++) {
1385
+ const a = x(i), b = z(i);
1386
+ if (a < x0)
1387
+ x0 = a;
1388
+ if (a > x1)
1389
+ x1 = a;
1390
+ if (b < z0)
1391
+ z0 = b;
1392
+ if (b > z1)
1393
+ z1 = b;
1394
+ }
1395
+ const G = Math.max(1, Math.ceil(Math.sqrt(n / k)));
1396
+ const sx = (x1 - x0) / G || 1, sz = (z1 - z0) / G || 1;
1397
+ const cell = new Int32Array(n), cnt = new Int32Array(G * G);
1398
+ for (let i = 0;i < n; i++) {
1399
+ const c = Math.min(G - 1, Math.floor((x(i) - x0) / sx)) * G + Math.min(G - 1, Math.floor((z(i) - z0) / sz));
1400
+ cell[i] = c;
1401
+ cnt[c]++;
1402
+ }
1403
+ const out = [];
1404
+ const at = new Int32Array(G * G).fill(-1);
1405
+ const fill = new Int32Array(G * G);
1406
+ for (let c = 0;c < G * G; c++)
1407
+ if (cnt[c]) {
1408
+ at[c] = out.length;
1409
+ out.push(new Int32Array(cnt[c]));
1410
+ }
1411
+ for (let i = 0;i < n; i++) {
1412
+ const c = cell[i];
1413
+ out[at[c]][fill[c]++] = i;
1414
+ }
1415
+ return out;
1416
+ }
1417
+
1418
+ class LdrLodRoot extends THREE.LOD {
1419
+ clusters = [];
1420
+ vpH = 1080;
1421
+ px = 0.5;
1422
+ edgePpl = 0.025;
1423
+ enabled = true;
1424
+ apply = () => {};
1425
+ inv = new THREE.Matrix4;
1426
+ cp = new THREE.Vector3;
1427
+ constructor() {
1428
+ super();
1429
+ this.autoUpdate = true;
1430
+ }
1431
+ raycast() {}
1432
+ update(camera) {
1433
+ if (!this.enabled || !this.clusters.length)
1434
+ return;
1435
+ const pm = camera.projectionMatrix.elements;
1436
+ const persp = camera.isPerspectiveCamera || pm[15] === 0;
1437
+ const k = this.vpH / 2 * Math.abs(pm[5]);
1438
+ this.inv.copy(this.matrixWorld).invert();
1439
+ this.cp.setFromMatrixPosition(camera.matrixWorld).applyMatrix4(this.inv);
1440
+ const { x, y, z } = this.cp;
1441
+ const tau = this.px, eppl = this.edgePpl;
1442
+ for (const c of this.clusters) {
1443
+ const cx = c.s.center.x - x, cy = c.s.center.y - y, cz = c.s.center.z - z;
1444
+ const d = Math.sqrt(cx * cx + cy * cy + cz * cz) - c.s.radius;
1445
+ const ppl = persp ? d > 0.000001 ? k / d : Infinity : k;
1446
+ let L = 0;
1447
+ for (let i = c.lv.length - 1;i > 0; i--)
1448
+ if (c.lv[i].err * ppl <= tau) {
1449
+ L = i;
1450
+ break;
1451
+ }
1452
+ const e = ppl >= eppl;
1453
+ if (L !== c.cur || e !== c.edgeOk) {
1454
+ c.cur = L;
1455
+ c.edgeOk = e;
1456
+ this.apply(c);
1457
+ }
1458
+ }
1459
+ }
1460
+ }
1461
+ function buildStudMeshes(insts, H, cull, arr, Q, LOD, color, matF, matB, steps, colorIndex) {
1462
+ const bucketOf = new Map;
1463
+ const bKind = [], bMirror = [];
1464
+ const bAll = [];
1465
+ const studBucket = new Uint16Array(cull.nStuds);
1466
+ const sx = new Float32Array(cull.nStuds), sz = new Float32Array(cull.nStuds);
1467
+ forEachStud(insts, H, (j, _i, w, kind) => {
1468
+ const neg = det12(w) < 0;
1469
+ const k = neg ? kind + "#m" : kind;
1470
+ let b = bucketOf.get(k);
1471
+ if (b === undefined) {
1472
+ b = bKind.length;
1473
+ bucketOf.set(k, b);
1474
+ bKind.push(kind);
1475
+ bMirror.push(neg);
1476
+ bAll.push(0);
1477
+ }
1478
+ studBucket[j] = b;
1479
+ bAll[b]++;
1480
+ sx[j] = w[0];
1481
+ sz[j] = w[2];
1482
+ });
1483
+ const bIds = bAll.map((n) => new Int32Array(n));
1484
+ {
1485
+ const f = bAll.map(() => 0);
1486
+ for (let j = 0;j < cull.nStuds; j++) {
1487
+ const b = studBucket[j];
1488
+ bIds[b][f[b]++] = j;
1489
+ }
1490
+ }
1491
+ const studCl = new Int32Array(cull.nStuds), studSlot = new Int32Array(cull.nStuds);
1492
+ const sMesh = [];
1493
+ bKind.forEach((kind, b) => {
1494
+ const g = geom(H.studKinds[kind].body, arr, Q);
1495
+ const ids = bIds[b];
1496
+ for (const c of gridClusters(ids.length, (i) => sx[ids[i]], (i) => sz[ids[i]], LOD ? 16384 : Infinity)) {
1497
+ const ci = sMesh.length;
1498
+ let v = 0;
1499
+ for (const i of c)
1500
+ if (!cull.studHidden[ids[i]])
1501
+ v++;
1502
+ let vc = 0, hc = v;
1503
+ for (const i of c) {
1504
+ const j = ids[i];
1505
+ studCl[j] = ci;
1506
+ studSlot[j] = cull.studHidden[j] ? hc++ : vc++;
1507
+ }
1508
+ if (!g) {
1509
+ sMesh.push(null);
1510
+ continue;
1511
+ }
1512
+ const im = newIM(g, bMirror[b] ? matB : matF, c.length);
1513
+ im.instanceColor = new THREE.InstancedBufferAttribute(new Float32Array(c.length * 3), 3);
1514
+ const n = c.length;
1515
+ 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) } } : {} });
1516
+ }
1517
+ });
1518
+ forEachStud(insts, H, (j, owner, w, _kind, col, si) => {
1519
+ const sm = sMesh[studCl[j]];
1520
+ if (!sm)
1521
+ return;
1522
+ const im = sm.im;
1523
+ const slot = studSlot[j];
1524
+ if (sm.slots) {
1525
+ const t = sm.slots;
1526
+ t.own[slot] = steps[owner];
1527
+ t.hide[slot] = cull.studHideStep[j];
1528
+ t.inst[slot] = owner;
1529
+ t.stud[slot] = si;
1530
+ t.col[slot] = colorIndex(col);
1531
+ }
1532
+ writeM4(im.instanceMatrix.array, slot * 16, w);
1533
+ color(col).toArray(im.instanceColor.array, slot * 3);
1534
+ });
1535
+ return sMesh;
1105
1536
  }
1106
1537
  function buildLdrScene(insts, pack, P, color, mark) {
1107
1538
  const { header: H, arr } = pack;
1108
- const root = new THREE.Group;
1539
+ const Q = P.quant !== false;
1540
+ const LOD = P.lod !== false;
1541
+ const root = new LdrLodRoot;
1542
+ root.enabled = LOD;
1543
+ root.px = P.lodPx ?? 0.5;
1544
+ root.edgePpl = (P.lodEdgePx ?? 0.5) / 20;
1545
+ const trackVp = (r) => {
1546
+ const rt = r.getRenderTarget();
1547
+ const h = rt ? rt.height : r.getContext().drawingBufferHeight;
1548
+ if (h)
1549
+ root.vpH = h;
1550
+ };
1109
1551
  const matF = makeMaterial(P.side === "front" ? THREE.FrontSide : THREE.DoubleSide), matB = makeMaterial(P.side === "front" ? THREE.BackSide : THREE.DoubleSide);
1110
1552
  const matD = makeMaterial(THREE.DoubleSide);
1111
1553
  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
1554
  const byPart = new Map;
1114
1555
  insts.forEach((it, i) => {
1115
1556
  if (H.parts[it.part]) {
1116
- const k = it.part + (det(it.m) < 0 ? "#m" : "");
1557
+ const k = it.part + (det12(it.m) < 0 ? "#m" : "");
1117
1558
  (byPart.get(k) || byPart.set(k, []).get(k)).push(i);
1118
1559
  }
1119
1560
  });
1120
- const cull = computeCulling(insts, H);
1561
+ const steps = P.steps;
1562
+ let cull = cullCore(insts, H, steps);
1121
1563
  mark("culling");
1564
+ const stepBatches = [];
1565
+ const instM = steps ? new Float64Array(insts.length * 12) : null;
1566
+ const instPP = [];
1567
+ const palette = [];
1568
+ const palIdx = new Map;
1569
+ const colorIndex = (c) => {
1570
+ let i = palIdx.get(c);
1571
+ if (i === undefined) {
1572
+ i = palette.length;
1573
+ palette.push(color(c));
1574
+ palIdx.set(c, i);
1575
+ }
1576
+ return i;
1577
+ };
1578
+ const instCol = steps ? new Uint16Array(insts.length) : null;
1579
+ if (instM)
1580
+ insts.forEach((it, i) => {
1581
+ for (let k = 0;k < 12; k++)
1582
+ instM[i * 12 + k] = it.m[k];
1583
+ instPP.push(prepPart(H, it.part));
1584
+ instCol[i] = colorIndex(it.color);
1585
+ });
1122
1586
  const edgeObjs = [];
1123
1587
  const condObjs = [];
1124
1588
  const wantCond = P.cond !== false && (P.edges === "idx" || P.edges === true);
1125
1589
  let condMat = null;
1126
1590
  const cmat = () => condMat ??= condLineMat();
1591
+ let lineMatShared = null;
1592
+ const lmat = () => lineMatShared ??= sharedLineMat();
1127
1593
  const condCache = new Map;
1128
- const addCond = (entry, im, which = "body") => {
1594
+ const addCond = (entry, im, cl, which = "body") => {
1129
1595
  if (!wantCond)
1130
1596
  return null;
1131
1597
  const ck = which === "under" ? entry.condlu : entry.condl;
@@ -1142,12 +1608,40 @@ function buildLdrScene(insts, pack, P, color, mark) {
1142
1608
  const l = condEdges(cb, im, cmat());
1143
1609
  edgeObjs.push(l);
1144
1610
  condObjs.push(l);
1611
+ cl.cond.push(l);
1145
1612
  root.add(l);
1146
1613
  return l;
1147
1614
  };
1148
- const stats = { studsTotal: cull.studs.length, studsHidden: cull.studs.filter((s) => s.hidden).length, undersTotal: 0, undersHidden: 0 };
1615
+ const lineCache = new Map;
1616
+ const addLines = (ref, im, cl) => {
1617
+ let w = lineCache.get(ref);
1618
+ if (!w) {
1619
+ const r = weldLines(arr(ref));
1620
+ lineCache.set(ref, w = { P: new THREE.BufferAttribute(r.P, 3), I: new THREE.BufferAttribute(r.I, 1) });
1621
+ }
1622
+ const g = new THREE.InstancedBufferGeometry;
1623
+ g.setAttribute("position", w.P);
1624
+ g.setIndex(w.I);
1625
+ g.setAttribute("instanceMatrix", im.instanceMatrix);
1626
+ g.instanceCount = im.count;
1627
+ const l = new THREE.LineSegments(g, lmat());
1628
+ l.frustumCulled = false;
1629
+ edgeObjs.push(l);
1630
+ cl.lines.push(l);
1631
+ root.add(l);
1632
+ return l;
1633
+ };
1634
+ const stats = { studsTotal: cull.nStuds, studsHidden: cull.studsHidden, undersTotal: 0, undersHidden: 0 };
1149
1635
  const toggles = [];
1150
1636
  const studMeshes = [];
1637
+ const countTo = (objs) => ({ set count(n) {
1638
+ for (const o of objs()) {
1639
+ if (o.isInstancedMesh)
1640
+ o.count = n;
1641
+ else
1642
+ o.geometry.instanceCount = n;
1643
+ }
1644
+ } });
1151
1645
  const wantTex = P.tex !== false && !!H.textures;
1152
1646
  const texCache = new Map;
1153
1647
  const texMats = new Map;
@@ -1169,160 +1663,256 @@ function buildLdrScene(insts, pack, P, color, mark) {
1169
1663
  return m;
1170
1664
  };
1171
1665
  let texBatches = 0;
1172
- for (const [pk, ids] of byPart) {
1666
+ const newCluster = (s) => {
1667
+ const c = { s, lv: [], lines: [], cond: [], cur: 0, edgeOk: true };
1668
+ root.clusters.push(c);
1669
+ return c;
1670
+ };
1671
+ const lodLevels = (b, base, mat) => !LOD || P.lodMesh === false || !b?.lod ? [] : b.lod.map((l) => {
1672
+ const im = new THREE.InstancedMesh(geom(l, arr, Q), mat, 0);
1673
+ im.instanceMatrix = base.instanceMatrix;
1674
+ im.count = base.count;
1675
+ im.instanceColor = base.instanceColor;
1676
+ im.boundingSphere = base.boundingSphere;
1677
+ im.visible = false;
1678
+ im.onBeforeRender = trackVp;
1679
+ im.userData.ldrLod = true;
1680
+ root.add(im);
1681
+ return { objs: [im], err: l.err };
1682
+ });
1683
+ const tris = (b) => b ? b.tris ?? 0 : 0;
1684
+ const kFor = (t) => LOD ? Math.max(256, Math.min(16384, Math.round(262144 / Math.max(1, t)))) : Infinity;
1685
+ for (const [pk, idsAll] of byPart) {
1173
1686
  const part = pk.replace(/#m$/, "");
1174
1687
  const p = H.parts[part];
1175
1688
  const mat = P.uncertDouble !== false && p.uncert ? (nUncert++, matD) : pk.endsWith("#m") ? matB : matF;
1176
1689
  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
- }
1690
+ const bodyB = textured ? p.tbody : p.body;
1691
+ const body = geom(bodyB, arr, Q);
1692
+ 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)));
1693
+ for (const g of groups) {
1694
+ const ids = Array.from(g, (i) => idsAll[i]);
1695
+ let bodyIm = null;
1696
+ const lv0 = [];
1697
+ const place = (im) => {
1698
+ fillInstances(im, ids, insts, color);
1699
+ im.computeBoundingSphere();
1700
+ root.add(im);
1701
+ };
1702
+ if (body) {
1703
+ const im = bodyIm = newIM(body, mat, ids.length);
1704
+ place(im);
1705
+ lv0.push(im);
1205
1706
  }
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;
1707
+ if (textured)
1708
+ for (const tb of p.tex) {
1709
+ const gg = geom(tb, arr, Q);
1710
+ const im = newIM(gg, texMat(tb.t, mat, !!tb.wrapU), ids.length);
1711
+ texBatches++;
1712
+ lv0.push(im);
1713
+ if (bodyIm) {
1714
+ im.instanceMatrix = bodyIm.instanceMatrix;
1715
+ im.instanceColor = bodyIm.instanceColor;
1716
+ im.boundingSphere = bodyIm.boundingSphere;
1717
+ root.add(im);
1718
+ } else {
1719
+ place(im);
1720
+ bodyIm = im;
1721
+ }
1722
+ }
1723
+ if (bodyIm) {
1724
+ const cl = newCluster(bodyIm.boundingSphere);
1725
+ lv0.forEach((o) => o.onBeforeRender = trackVp);
1726
+ cl.lv.push({ objs: lv0, err: 0 }, ...textured ? [] : lodLevels(p.body, bodyIm, mat));
1727
+ if (P.edges === "idx" && p.edges)
1728
+ addLines(p.edges, bodyIm, cl);
1729
+ addCond(p, bodyIm, cl);
1730
+ if (steps)
1731
+ 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]) });
1732
+ if (P.edges === true && p.edges) {
1733
+ const mats = new Float32Array(ids.length * 16);
1734
+ ids.forEach((id, k) => writeM4(mats, k * 16, insts[id].m));
1735
+ {
1736
+ const l = instancedLines(arr(p.edges), mats, ids.length);
1737
+ edgeObjs.push(l);
1738
+ cl.lines.push(l);
1739
+ root.add(l);
1740
+ }
1235
1741
  }
1236
1742
  }
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);
1743
+ const under = geom(p.under, arr, Q);
1744
+ if (under) {
1745
+ const ord = [...ids.filter((i) => !cull.underHidden[i]), ...ids.filter((i) => cull.underHidden[i])];
1746
+ const vis = ids.length - ids.reduce((a, i) => a + cull.underHidden[i], 0);
1747
+ stats.undersTotal += ids.length;
1748
+ stats.undersHidden += ids.length - vis;
1749
+ const im = newIM(under, mat, ids.length);
1750
+ fillInstances(im, ord, insts, color);
1751
+ im.onBeforeRender = trackVp;
1752
+ im.computeBoundingSphere();
1753
+ im.count = P.cullUnder ? vis : ids.length;
1754
+ root.add(im);
1755
+ const cl = newCluster(im.boundingSphere);
1756
+ cl.lv.push({ objs: [im], err: 0 }, ...lodLevels(p.under, im, mat));
1757
+ addCond(p, im, cl, "under");
1758
+ const objs = [...cl.lv.flatMap((l) => l.objs), ...cl.cond];
1759
+ objs.forEach((o) => {
1760
+ if (!o.isInstancedMesh)
1761
+ o.geometry.instanceCount = im.count;
1762
+ });
1763
+ toggles.push({ mesh: countTo(() => objs), all: ids.length, visible: vis, flag: "under" });
1764
+ if (steps)
1765
+ 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
1766
  }
1246
1767
  }
1247
1768
  }
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;
1769
+ const sMesh = buildStudMeshes(insts, H, cull, arr, Q, LOD, color, matF, matB, steps, steps && colorIndex);
1770
+ const sKind = sMesh.map((x) => x?.kind ?? "");
1771
+ const sVis = sMesh.map((x) => x?.vis ?? 0);
1772
+ const STUD_ERR = 12;
1773
+ sMesh.forEach((sm, ci) => {
1774
+ if (!sm)
1775
+ return;
1776
+ const im = sm.im;
1777
+ const kind = sKind[ci];
1256
1778
  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
- });
1779
+ const vis = sVis[ci], all = im.instanceMatrix.count;
1267
1780
  im.computeBoundingSphere();
1268
- im.count = P.cullStuds ? vis : ord.length;
1781
+ im.count = P.cullStuds ? vis : all;
1782
+ im.onBeforeRender = trackVp;
1783
+ im.userData.ldrStud = true;
1269
1784
  root.add(im);
1270
- toggles.push({ mesh: im, all: ord.length, visible: vis, flag: "studs" });
1271
1785
  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
- }
1786
+ const cl = newCluster(im.boundingSphere);
1787
+ cl.lv.push({ objs: [im], err: 0 });
1788
+ if (LOD && P.lodStuds !== false)
1789
+ cl.lv.push({ objs: [], err: STUD_ERR });
1790
+ if (P.edges === "idx" && k.edges)
1791
+ addLines(k.edges, im, cl);
1792
+ if (P.condStuds !== false)
1793
+ addCond(k, im, cl);
1290
1794
  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);
1795
+ const l = instancedLines(arr(k.edges), im.instanceMatrix.array.slice(), im.count);
1294
1796
  edgeObjs.push(l);
1797
+ cl.lines.push(l);
1295
1798
  root.add(l);
1296
- toggles.push({ mesh: { set count(n) {
1297
- l.geometry.instanceCount = n;
1298
- } }, all: ord.length, visible: vis, flag: "studs" });
1299
1799
  }
1300
- }
1800
+ const objs = [im, ...cl.lines, ...cl.cond];
1801
+ objs.forEach((o) => {
1802
+ if (!o.isInstancedMesh)
1803
+ o.geometry.instanceCount = im.count;
1804
+ });
1805
+ toggles.push({ mesh: countTo(() => objs), all, visible: vis, flag: "studs" });
1806
+ if (sm.slots)
1807
+ stepBatches.push({ im, objs, flag: "studs", ...sm.slots });
1808
+ });
1301
1809
  stats.uncertParts = Object.values(H.parts).filter((x) => x.uncert).length;
1302
1810
  stats.uncertBatches = nUncert;
1303
1811
  let condOn = true, edgesOn = true;
1812
+ root.apply = (c) => {
1813
+ c.lv.forEach((l, i) => {
1814
+ for (const o of l.objs)
1815
+ o.visible = i === c.cur;
1816
+ });
1817
+ const lineVis = edgesOn && c.edgeOk && (c.cur === 0 || !LOD || c.lv[c.cur].objs.length > 0);
1818
+ for (const o of c.lines)
1819
+ o.visible = lineVis;
1820
+ for (const o of c.cond)
1821
+ o.visible = lineVis && condOn;
1822
+ };
1823
+ const refresh = () => root.clusters.forEach(root.apply);
1304
1824
  const setEdges = (v) => {
1305
1825
  edgesOn = v;
1306
- edgeObjs.forEach((o) => o.visible = v && (condOn || !condObjs.includes(o)));
1826
+ refresh();
1307
1827
  };
1308
1828
  const setCond = (v) => {
1309
1829
  condOn = v;
1310
- condObjs.forEach((o) => o.visible = v && edgesOn);
1830
+ refresh();
1311
1831
  };
1832
+ refresh();
1312
1833
  stats.condBatches = condObjs.length;
1313
1834
  stats.texBatches = texBatches;
1314
1835
  stats.textures = texCache.size;
1836
+ stats.clusters = root.clusters.length;
1315
1837
  const texturesReady = Promise.all(pending).then(() => {});
1838
+ let stepS = STEP_NEVER - 1, cullS = !!P.cullStuds, cullU = !!P.cullUnder;
1839
+ const applySteps = () => {
1840
+ const st = { studsTotal: 0, studsHidden: 0, undersTotal: 0, undersHidden: 0 };
1841
+ const S = stepS;
1842
+ const wm = new Float64Array(12);
1843
+ for (const b of stepBatches) {
1844
+ const MA = b.im.instanceMatrix.array, CA = b.im.instanceColor.array;
1845
+ const useHide = b.flag === "studs" ? cullS : b.flag === "under" ? cullU : false;
1846
+ const { own, hide, inst, stud, col } = b;
1847
+ let w = 0;
1848
+ for (let i = 0;i < own.length; i++) {
1849
+ if (own[i] > S)
1850
+ continue;
1851
+ const hid = !!hide && hide[i] <= S;
1852
+ if (b.flag === "studs") {
1853
+ st.studsTotal++;
1854
+ if (hid)
1855
+ st.studsHidden++;
1856
+ } else if (b.flag === "under") {
1857
+ st.undersTotal++;
1858
+ if (hid)
1859
+ st.undersHidden++;
1860
+ }
1861
+ if (useHide && hid)
1862
+ continue;
1863
+ const n = inst[i];
1864
+ if (stud) {
1865
+ mul12To(instM, instPP[n].studM, stud[i] * 12, wm, n * 12);
1866
+ writeM4(MA, w * 16, wm);
1867
+ } else
1868
+ writeM4(MA, w * 16, instM, n * 12);
1869
+ palette[col[i]].toArray(CA, w * 3);
1870
+ w++;
1871
+ }
1872
+ b.im.instanceMatrix.needsUpdate = true;
1873
+ b.im.instanceColor.needsUpdate = true;
1874
+ for (const o of b.objs) {
1875
+ if (o.isInstancedMesh)
1876
+ o.count = w;
1877
+ else
1878
+ o.geometry.instanceCount = w;
1879
+ }
1880
+ }
1881
+ return st;
1882
+ };
1316
1883
  const setCull = (studs, under) => {
1884
+ cullS = studs;
1885
+ cullU = under;
1886
+ if (steps) {
1887
+ applySteps();
1888
+ return;
1889
+ }
1317
1890
  for (const t of toggles)
1318
1891
  t.mesh.count = (t.flag === "studs" ? studs : under) ? t.visible : t.all;
1319
1892
  };
1893
+ const setStep = (s) => {
1894
+ if (!steps)
1895
+ throw new Error("buildLdrScene: setStep needs P.steps");
1896
+ stepS = s == null ? STEP_NEVER - 1 : s;
1897
+ return applySteps();
1898
+ };
1320
1899
  const box = new THREE.Box3;
1321
1900
  for (const it of insts) {
1322
- const p = H.parts[it.part];
1323
- if (!p)
1901
+ if (!H.parts[it.part])
1324
1902
  continue;
1325
- box.expandByPoint(new THREE.Vector3(it.m[0], it.m[1], it.m[2]));
1903
+ const m = it.m;
1904
+ if (m[0] < box.min.x)
1905
+ box.min.x = m[0];
1906
+ if (m[1] < box.min.y)
1907
+ box.min.y = m[1];
1908
+ if (m[2] < box.min.z)
1909
+ box.min.z = m[2];
1910
+ if (m[0] > box.max.x)
1911
+ box.max.x = m[0];
1912
+ if (m[1] > box.max.y)
1913
+ box.max.y = m[1];
1914
+ if (m[2] > box.max.z)
1915
+ box.max.z = m[2];
1326
1916
  }
1327
1917
  const setSide = (front) => {
1328
1918
  matF.side = front ? THREE.FrontSide : THREE.DoubleSide;
@@ -1339,13 +1929,26 @@ function buildLdrScene(insts, pack, P, color, mark) {
1339
1929
  for (const s of studMeshes) {
1340
1930
  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
1931
  if (k2)
1342
- s.im.geometry = geom(alt.header.studKinds[k2].body, alt.arr);
1932
+ s.im.geometry = geom(alt.header.studKinds[k2].body, alt.arr, Q);
1343
1933
  }
1344
1934
  return () => studMeshes.forEach((s, i) => s.im.geometry = saved[i]);
1345
1935
  };
1346
- return { root, stats, setCull, setSide, setEdges, setCond, swapStuds, box, texturesReady };
1936
+ const setLod = (on) => {
1937
+ root.enabled = on && LOD;
1938
+ if (!root.enabled) {
1939
+ for (const c of root.clusters) {
1940
+ c.cur = 0;
1941
+ c.edgeOk = true;
1942
+ }
1943
+ refresh();
1944
+ }
1945
+ };
1946
+ insts = null;
1947
+ cull = null;
1948
+ byPart.clear();
1949
+ return { root, stats, setCull, setSide, setEdges, setCond, swapStuds, setLod, setStep, box, texturesReady };
1347
1950
  }
1348
- // src/scene.ts
1951
+ // ../../../../../../../tmp/ldr-build-pIke9r/src/scene.ts
1349
1952
  import * as THREE2 from "three";
1350
1953
  function colorFn(src, pack, fallback) {
1351
1954
  const table = new Map;
@@ -1430,11 +2033,11 @@ function createLdrObject(instances, pack, options = {}) {
1430
2033
  }
1431
2034
  cur = null;
1432
2035
  };
1433
- const build = (step) => {
2036
+ const multiStep = uniq.length > 1;
2037
+ const drawnAt = (step) => all.filter((i, k) => (step == null || stepOf[k] <= step) && pack.header.parts[i.part]).length;
2038
+ const build = () => {
1434
2039
  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, () => {});
2040
+ 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
2041
  pairs.clear();
1439
2042
  if (o.material === "plastic")
1440
2043
  cur.root.traverse((x) => {
@@ -1449,17 +2052,25 @@ function createLdrObject(instances, pack, options = {}) {
1449
2052
  if (!cond)
1450
2053
  cur.setCond(false);
1451
2054
  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 });
2055
+ 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
2056
  api.texturesReady = cur.texturesReady;
1454
2057
  };
2058
+ const setStep = (step) => {
2059
+ if (!multiStep)
2060
+ return;
2061
+ const t0 = performance.now();
2062
+ Object.assign(stats, cur.setStep(step), { drawn: drawnAt(step), buildMs: performance.now() - t0 });
2063
+ };
1455
2064
  const api = { texturesReady: Promise.resolve() };
1456
- build(o.step ?? null);
2065
+ build();
2066
+ if (o.step != null)
2067
+ setStep(o.step);
1457
2068
  return Object.assign(api, {
1458
2069
  object,
1459
2070
  stats,
1460
2071
  stepCount: uniq.length,
1461
2072
  stepViews,
1462
- setStep: (s) => build(s),
2073
+ setStep,
1463
2074
  setEdges: (v) => {
1464
2075
  edges = v;
1465
2076
  cur.setEdges(v);
@@ -1473,6 +2084,7 @@ function createLdrObject(instances, pack, options = {}) {
1473
2084
  cu = b;
1474
2085
  cur.setCull(a, b);
1475
2086
  },
2087
+ setLod: (v) => cur.setLod(v),
1476
2088
  setSide: (s) => {
1477
2089
  side = s;
1478
2090
  cur.setSide(s === "front");
@@ -1485,7 +2097,7 @@ function createLdrObject(instances, pack, options = {}) {
1485
2097
  object.updateMatrixWorld(true);
1486
2098
  target.makeEmpty();
1487
2099
  object.traverse((x) => {
1488
- if (x.isInstancedMesh && x.count) {
2100
+ if (x.isInstancedMesh && x.count && !x.userData.ldrLod) {
1489
2101
  x.computeBoundingBox();
1490
2102
  const b = x.boundingBox.clone().applyMatrix4(x.matrixWorld);
1491
2103
  target.union(b);
@@ -1496,7 +2108,7 @@ function createLdrObject(instances, pack, options = {}) {
1496
2108
  dispose: disposeCur
1497
2109
  });
1498
2110
  }
1499
- // src/pack-io.ts
2111
+ // ../../../../../../../tmp/ldr-build-pIke9r/src/pack-io.ts
1500
2112
  import { decompress } from "fzstd";
1501
2113
  async function decompressPack(data) {
1502
2114
  const u = data instanceof Uint8Array ? data : new Uint8Array(data);
@@ -1507,7 +2119,148 @@ async function decompressPack(data) {
1507
2119
  return new Response(new Blob([u]).stream().pipeThrough(new DecompressionStream("gzip"))).arrayBuffer();
1508
2120
  return own(u);
1509
2121
  }
2122
+ // ../../../../../../../tmp/ldr-build-pIke9r/src/cdn.ts
2123
+ import { decompress as decompress2 } from "fzstd";
2124
+ var DEFAULT_PARTS_CDN = "https://ldr-demo.vercel.app/cdn/v1/";
2125
+ var indexCache = new Map;
2126
+ var shardCache = new Map;
2127
+ function loadPartsIndex(cdn = DEFAULT_PARTS_CDN, f = fetch) {
2128
+ let p = indexCache.get(cdn);
2129
+ if (!p) {
2130
+ p = f(cdn + "parts.json").then((r) => {
2131
+ if (!r.ok)
2132
+ throw new Error(`ldr: parts CDN index ${r.status} (${cdn}parts.json)`);
2133
+ return r.json();
2134
+ });
2135
+ p.catch(() => indexCache.delete(cdn));
2136
+ indexCache.set(cdn, p);
2137
+ }
2138
+ return p;
2139
+ }
2140
+ 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");
2141
+ 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;
2142
+ function mergePacks(packs, extra = {}) {
2143
+ const payloads = [];
2144
+ let off = 0;
2145
+ const parts = {}, studKinds = {};
2146
+ for (const p of packs) {
2147
+ const buf = p.buffer;
2148
+ if (!buf)
2149
+ throw new Error("ldr: mergePacks needs packs from decodePack/loadPack");
2150
+ const hl = new DataView(buf).getUint32(0, true);
2151
+ const pay = new Uint8Array(buf, 4 + hl);
2152
+ const d = off;
2153
+ payloads.push(pay);
2154
+ off += pay.byteLength;
2155
+ const pad = (4 - off % 4) % 4;
2156
+ if (pad) {
2157
+ payloads.push(new Uint8Array(pad));
2158
+ off += pad;
2159
+ }
2160
+ for (const [k, v] of Object.entries(p.header.parts ?? {}))
2161
+ if (!(k in parts))
2162
+ parts[k] = rebase(v, d);
2163
+ for (const [k, v] of Object.entries(p.header.studKinds ?? {}))
2164
+ if (!(k in studKinds))
2165
+ studKinds[k] = rebase(v, d);
2166
+ }
2167
+ const h0 = packs[0]?.header ?? { version: 1, weld: true };
2168
+ const header = new TextEncoder().encode(JSON.stringify({ ...h0, parts, studKinds, ...extra }));
2169
+ const hpad = (4 - (4 + header.length) % 4) % 4;
2170
+ const base = 4 + header.length + hpad;
2171
+ const out = new Uint8Array(base + off);
2172
+ new DataView(out.buffer).setUint32(0, header.length + hpad, true);
2173
+ out.set(header, 4);
2174
+ out.fill(32, 4 + header.length, base);
2175
+ let o = base;
2176
+ for (const p of payloads) {
2177
+ out.set(p, o);
2178
+ o += p.byteLength;
2179
+ }
2180
+ return decodePack(out.buffer);
2181
+ }
2182
+ async function loadParts(names, o = {}) {
2183
+ const cdn = o.cdn ?? DEFAULT_PARTS_CDN, f = o.fetch ?? fetch, gap = o.gap ?? 65536;
2184
+ const idx = await loadPartsIndex(cdn, f);
2185
+ const want = [...new Set([...names].map(norm))];
2186
+ const missing = [];
2187
+ const todo = [];
2188
+ for (const n of want) {
2189
+ if (shardCache.has(cdn + n))
2190
+ continue;
2191
+ const e = idx.parts[n];
2192
+ if (e)
2193
+ todo.push({ name: n, off: e[0], len: e[1], file: e[2] ?? 0 });
2194
+ else
2195
+ missing.push(n);
2196
+ }
2197
+ todo.sort((a, b) => a.file - b.file || a.off - b.off);
2198
+ const groups = [];
2199
+ for (const t of todo) {
2200
+ const g = groups[groups.length - 1];
2201
+ if (g && g.file === t.file && t.off - g.end <= gap) {
2202
+ g.end = Math.max(g.end, t.off + t.len);
2203
+ g.items.push(t);
2204
+ } else
2205
+ groups.push({ file: t.file, start: t.off, end: t.off + t.len, items: [t] });
2206
+ }
2207
+ let bytes = 0;
2208
+ let next = 0;
2209
+ const worker = async () => {
2210
+ while (next < groups.length) {
2211
+ const g = groups[next++];
2212
+ const r = await f(new URL(idx.archives[g.file], cdn).href, { headers: { Range: `bytes=${g.start}-${g.end - 1}` } });
2213
+ if (!r.ok)
2214
+ throw new Error(`ldr: parts CDN ${r.status}`);
2215
+ let buf = new Uint8Array(await r.arrayBuffer());
2216
+ if (r.status === 200)
2217
+ buf = buf.subarray(g.start, g.end);
2218
+ bytes += buf.byteLength;
2219
+ for (const t of g.items) {
2220
+ const raw = decompress2(buf.subarray(t.off - g.start, t.off - g.start + t.len));
2221
+ const ab = raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength);
2222
+ shardCache.set(cdn + t.name, decodePack(ab));
2223
+ }
2224
+ }
2225
+ };
2226
+ await Promise.all(Array.from({ length: Math.min(o.concurrency ?? 6, groups.length) }, worker));
2227
+ const shards = want.filter((n) => shardCache.has(cdn + n)).map((n) => shardCache.get(cdn + n));
2228
+ const pack = shards.length ? mergePacks(shards, { colors: idx.colors }) : decodePack(emptyPack(idx.colors));
2229
+ return { pack, missing, requests: groups.length, bytes };
2230
+ }
2231
+ function emptyPack(colors) {
2232
+ const h = new TextEncoder().encode(JSON.stringify({ version: 1, weld: true, parts: {}, studKinds: {}, colors }));
2233
+ const pad = (4 - (4 + h.length) % 4) % 4;
2234
+ const b = new Uint8Array(4 + h.length + pad).fill(32);
2235
+ new DataView(b.buffer).setUint32(0, h.length + pad, true);
2236
+ b.set(h, 4);
2237
+ return b.buffer;
2238
+ }
2239
+ async function loadModel(source, o = {}) {
2240
+ const t0 = performance.now();
2241
+ const f = o.fetch ?? fetch;
2242
+ const idxP = loadPartsIndex(o.cdn ?? DEFAULT_PARTS_CDN, f);
2243
+ const text = o.text ? source : await f(source).then((r) => {
2244
+ if (!r.ok)
2245
+ throw new Error(`ldr: ${r.status} fetching ${source}`);
2246
+ return r.text();
2247
+ });
2248
+ const t1 = performance.now();
2249
+ const name = o.name ?? (o.text ? "main.ldr" : decodeURIComponent(source.split(/[?#]/)[0].split("/").pop() || "main.ldr"));
2250
+ const insts = parseModel(text, name, { looseGeometry: false });
2251
+ const t2 = performance.now();
2252
+ await idxP;
2253
+ const { pack, missing, requests, bytes } = await loadParts(insts.filter((i) => !i.embedded).map((i) => i.part), o);
2254
+ const t3 = performance.now();
2255
+ const obj = createLdrObject(insts, pack, o);
2256
+ for (const m of missing)
2257
+ if (!obj.stats.missing.includes(m))
2258
+ obj.stats.missing.push(m);
2259
+ const t4 = performance.now();
2260
+ 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 } });
2261
+ }
1510
2262
  export {
2263
+ DEFAULT_PARTS_CDN,
1511
2264
  I as IDENTITY,
1512
2265
  buildLdrScene,
1513
2266
  colorToken,
@@ -1517,20 +2270,26 @@ export {
1517
2270
  createInstancedConditionalEdges,
1518
2271
  createInstancedEdges,
1519
2272
  createLdrObject,
2273
+ cullCore,
1520
2274
  decodePack,
1521
2275
  decompressPack,
1522
2276
  fileKind,
1523
2277
  fileStepViews,
1524
2278
  flatten,
2279
+ forEachStud,
1525
2280
  hash32,
1526
2281
  invert,
1527
2282
  isDirectColor,
2283
+ loadModel,
1528
2284
  loadPack,
2285
+ loadParts,
2286
+ loadPartsIndex,
1529
2287
  looseFile,
1530
2288
  loosePartName,
1531
2289
  looseSource,
1532
2290
  looseSteps,
1533
2291
  makeMaterial,
2292
+ mergePacks,
1534
2293
  mul,
1535
2294
  norm,
1536
2295
  parse,