@adatechnology/game-vehicles 0.4.5 → 0.4.7

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
@@ -661,7 +661,7 @@ function installMaterials(kit) {
661
661
  const I = kit._;
662
662
  const document2 = kit.document;
663
663
  const RUST = new THREE.Color("#5e3220");
664
- const cache = {};
664
+ const cache2 = {};
665
665
  let envTex = null;
666
666
  function studioEnvironment() {
667
667
  if (envTex) return envTex;
@@ -706,7 +706,7 @@ function installMaterials(kit) {
706
706
  const key = "s" + c.getHexString() + JSON.stringify(o, function(k, v) {
707
707
  return v && v.isColor ? "#" + v.getHexString() : v && v.isTexture ? v.uuid : v;
708
708
  });
709
- if (!cache[key]) {
709
+ if (!cache2[key]) {
710
710
  const o2 = Object.assign({}, o);
711
711
  delete o2.noEnv;
712
712
  const mt = new THREE.MeshStandardMaterial(Object.assign({ color: c, roughness: 0.8, metalness: 0, flatShading: false }, o2));
@@ -714,9 +714,9 @@ function installMaterials(kit) {
714
714
  mt.envMap = studioEnvironment();
715
715
  if (o.envMapIntensity == null) mt.envMapIntensity = 0.55 + (mt.metalness - 0.6) * 1.6;
716
716
  }
717
- cache[key] = mt;
717
+ cache2[key] = mt;
718
718
  }
719
- return cache[key];
719
+ return cache2[key];
720
720
  }
721
721
  function makeFlatMaterial(color, o) {
722
722
  return makeMaterial(color, Object.assign({ flatShading: true, roughness: 0.92 }, o || {}));
@@ -831,7 +831,131 @@ function installMaterials(kit) {
831
831
  return t;
832
832
  }
833
833
  kit.textTexture = textTexture;
834
- Object.assign(I, { RUST, cache, studioEnvironment, makeMaterial, makeFlatMaterial, DOUBLE_SIDE, toColor, MATERIALS, paintColor, texCache, textTexture });
834
+ Object.assign(I, { RUST, cache: cache2, studioEnvironment, makeMaterial, makeFlatMaterial, DOUBLE_SIDE, toColor, MATERIALS, paintColor, texCache, textTexture });
835
+ }
836
+
837
+ // src/tri-index.ts
838
+ var TRI_BLOCK = 32;
839
+ var cache = /* @__PURE__ */ new WeakMap();
840
+ function trianglesOf(geo) {
841
+ return Math.floor((geo.index ? geo.index.count : geo.attributes.position.count) / 3);
842
+ }
843
+ function supports(geo) {
844
+ const pos = geo.attributes && geo.attributes.position;
845
+ return !!pos && !pos.isInterleavedBufferAttribute && pos.itemSize === 3;
846
+ }
847
+ function isCurrent(geo, ti) {
848
+ const pos = geo.attributes.position;
849
+ return ti.position === pos && ti.positionVersion === pos.version && ti.index === (geo.index || null) && ti.indexVersion === (geo.index ? geo.index.version : 0) && ti.triangles === trianglesOf(geo);
850
+ }
851
+ function blockCount(triangles) {
852
+ return Math.ceil(triangles / TRI_BLOCK);
853
+ }
854
+ function boxBlock(ti, block) {
855
+ const B = ti.boxes;
856
+ const t0 = block * TRI_BLOCK;
857
+ const t1 = Math.min(ti.triangles, t0 + TRI_BLOCK);
858
+ const o = block * 6;
859
+ for (let k = 0; k < 3; k++) {
860
+ ti.blocks[o + k] = Infinity;
861
+ ti.blocks[o + 3 + k] = -Infinity;
862
+ }
863
+ for (let t = t0; t < t1; t++) {
864
+ const j = t * 6;
865
+ for (let k = 0; k < 3; k++) {
866
+ if (B[j + k] < ti.blocks[o + k]) ti.blocks[o + k] = B[j + k];
867
+ if (B[j + 3 + k] > ti.blocks[o + 3 + k]) ti.blocks[o + 3 + k] = B[j + 3 + k];
868
+ }
869
+ }
870
+ }
871
+ function boxTriangle(geo, ti, t) {
872
+ const P = geo.attributes.position.array;
873
+ const ix = geo.index ? geo.index.array : null;
874
+ const a = (ix ? ix[t * 3] : t * 3) * 3;
875
+ const b = (ix ? ix[t * 3 + 1] : t * 3 + 1) * 3;
876
+ const c = (ix ? ix[t * 3 + 2] : t * 3 + 2) * 3;
877
+ const j = t * 6;
878
+ for (let k = 0; k < 3; k++) {
879
+ ti.boxes[j + k] = Math.min(P[a + k], P[b + k], P[c + k]);
880
+ ti.boxes[j + 3 + k] = Math.max(P[a + k], P[b + k], P[c + k]);
881
+ }
882
+ }
883
+ function advance(geo, ti, deadline) {
884
+ const total = blockCount(ti.triangles);
885
+ while (ti.builtBlocks < total) {
886
+ const block = ti.builtBlocks;
887
+ const t1 = Math.min(ti.triangles, (block + 1) * TRI_BLOCK);
888
+ for (let t = block * TRI_BLOCK; t < t1; t++) boxTriangle(geo, ti, t);
889
+ boxBlock(ti, block);
890
+ ti.builtBlocks++;
891
+ if (deadline !== Infinity && performance.now() >= deadline) break;
892
+ }
893
+ return ti.builtBlocks === total;
894
+ }
895
+ function start(geo) {
896
+ const pos = geo.attributes.position;
897
+ const triangles = trianglesOf(geo);
898
+ const ti = {
899
+ position: pos,
900
+ positionVersion: pos.version,
901
+ index: geo.index || null,
902
+ indexVersion: geo.index ? geo.index.version : 0,
903
+ triangles,
904
+ boxes: new Float64Array(triangles * 6),
905
+ blocks: new Float64Array(blockCount(triangles) * 6),
906
+ builtBlocks: 0
907
+ };
908
+ cache.set(geo, ti);
909
+ return ti;
910
+ }
911
+ function triIndexOf(geo) {
912
+ if (!supports(geo)) return void 0;
913
+ let ti = cache.get(geo);
914
+ if (!ti || !isCurrent(geo, ti)) ti = start(geo);
915
+ advance(geo, ti, Infinity);
916
+ return ti;
917
+ }
918
+ function warmTriIndex(geo, deadline) {
919
+ if (!supports(geo)) return true;
920
+ let ti = cache.get(geo);
921
+ if (!ti || !isCurrent(geo, ti)) ti = start(geo);
922
+ return advance(geo, ti, deadline);
923
+ }
924
+ function storeTriIndex(geo, boxes) {
925
+ const ti = start(geo);
926
+ ti.boxes = boxes;
927
+ const total = blockCount(ti.triangles);
928
+ for (let b = 0; b < total; b++) boxBlock(ti, b);
929
+ ti.builtBlocks = total;
930
+ }
931
+ function liveTriIndex(geo) {
932
+ if (geo.index || !supports(geo)) return void 0;
933
+ const ti = cache.get(geo);
934
+ return ti && isCurrent(geo, ti) && ti.builtBlocks === blockCount(ti.triangles) ? ti : void 0;
935
+ }
936
+ function touchTriIndex(geo, ti, vertices) {
937
+ if (ti.positionVersion + 1 !== geo.attributes.position.version) return;
938
+ const blocks = /* @__PURE__ */ new Set();
939
+ for (const v of vertices) {
940
+ const t = Math.floor(v / 3);
941
+ if (t >= ti.triangles) continue;
942
+ boxTriangle(geo, ti, t);
943
+ blocks.add(Math.floor(t / TRI_BLOCK));
944
+ }
945
+ blocks.forEach((b) => boxBlock(ti, b));
946
+ ti.positionVersion = geo.attributes.position.version;
947
+ }
948
+ function boundsFromIndex(geo) {
949
+ if (geo.index || !supports(geo)) return void 0;
950
+ const ti = cache.get(geo);
951
+ if (!ti || !isCurrent(geo, ti) || ti.builtBlocks !== blockCount(ti.triangles) || !ti.triangles || geo.attributes.position.count !== ti.triangles * 3) return void 0;
952
+ const box = new Float64Array([Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity]);
953
+ for (let b = 0, n = blockCount(ti.triangles); b < n; b++)
954
+ for (let k = 0; k < 3; k++) {
955
+ box[k] = Math.min(box[k], ti.blocks[b * 6 + k]);
956
+ box[3 + k] = Math.max(box[3 + k], ti.blocks[b * 6 + 3 + k]);
957
+ }
958
+ return { box, version: ti.positionVersion };
835
959
  }
836
960
 
837
961
  // src/geometry.ts
@@ -928,7 +1052,8 @@ function installGeometry(kit) {
928
1052
  g.userData.crease = true;
929
1053
  return g;
930
1054
  }
931
- function crease(geo, deg) {
1055
+ const latestCrease = /* @__PURE__ */ new WeakMap();
1056
+ function creaseTask(geo, deg) {
932
1057
  if (geo.index) {
933
1058
  const ni = geo.toNonIndexed();
934
1059
  Object.keys(ni.attributes).forEach(function(k) {
@@ -936,30 +1061,22 @@ function installGeometry(kit) {
936
1061
  });
937
1062
  geo.setIndex(null);
938
1063
  }
939
- const p = geo.attributes.position;
940
- const n = p.count;
1064
+ const P = geo.attributes.position.array;
1065
+ const n = geo.attributes.position.count;
941
1066
  const faces = n / 3;
942
1067
  const FN = new Float64Array(faces * 3);
943
- const map = /* @__PURE__ */ new Map();
944
- const RANGE = 65536;
945
- function key(i) {
946
- const x = Math.round(p.getX(i) * 2e3);
947
- const y = Math.round(p.getY(i) * 2e3);
948
- const z = Math.round(p.getZ(i) * 2e3);
949
- if (Math.abs(x) < RANGE && Math.abs(y) < RANGE && Math.abs(z) < RANGE) return ((x + RANGE) * 131072 + (y + RANGE)) * 131072 + (z + RANGE);
950
- return x + "," + y + "," + z;
951
- }
952
- const keys = new Array(n);
1068
+ const out = new Float32Array(n * 3);
953
1069
  for (let f = 0; f < faces; f++) {
954
- const ax = p.getX(3 * f);
955
- const ay = p.getY(3 * f);
956
- const az = p.getZ(3 * f);
957
- const bx = p.getX(3 * f + 1);
958
- const by = p.getY(3 * f + 1);
959
- const bz = p.getZ(3 * f + 1);
960
- const cx = p.getX(3 * f + 2);
961
- const cy = p.getY(3 * f + 2);
962
- const cz = p.getZ(3 * f + 2);
1070
+ const o = 9 * f;
1071
+ const ax = P[o];
1072
+ const ay = P[o + 1];
1073
+ const az = P[o + 2];
1074
+ const bx = P[o + 3];
1075
+ const by = P[o + 4];
1076
+ const bz = P[o + 5];
1077
+ const cx = P[o + 6];
1078
+ const cy = P[o + 7];
1079
+ const cz = P[o + 8];
963
1080
  const e1x = cx - bx;
964
1081
  const e1y = cy - by;
965
1082
  const e1z = cz - bz;
@@ -979,41 +1096,109 @@ function installGeometry(kit) {
979
1096
  FN[3 * f + 1] = ny;
980
1097
  FN[3 * f + 2] = nz;
981
1098
  for (let k = 0; k < 3; k++) {
982
- const kk = key(3 * f + k);
983
- keys[3 * f + k] = kk;
984
- const list = map.get(kk);
985
- if (list) list.push(f);
986
- else map.set(kk, [f]);
1099
+ out[9 * f + 3 * k] = nx;
1100
+ out[9 * f + 3 * k + 1] = ny;
1101
+ out[9 * f + 3 * k + 2] = nz;
1102
+ }
1103
+ }
1104
+ const normal = new THREE.BufferAttribute(out, 3);
1105
+ geo.setAttribute("normal", normal);
1106
+ const token = {};
1107
+ latestCrease.set(geo, token);
1108
+ let size = 16;
1109
+ while (size < n * 2) size *= 2;
1110
+ const mask = size - 1;
1111
+ const slot = new Int32Array(size).fill(-1);
1112
+ const gx = new Int32Array(n);
1113
+ const gy = new Int32Array(n);
1114
+ const gz = new Int32Array(n);
1115
+ const groupOf = new Int32Array(n);
1116
+ let groups = 0;
1117
+ let start2;
1118
+ let members;
1119
+ const cs = Math.cos(deg * PI / 180);
1120
+ let phase = 0;
1121
+ let at = 0;
1122
+ function hashSlice(deadline) {
1123
+ for (; at < n; at++) {
1124
+ if ((at & 1023) === 0 && performance.now() > deadline) return false;
1125
+ const x = Math.round(P[3 * at] * 2e3) | 0;
1126
+ const y = Math.round(P[3 * at + 1] * 2e3) | 0;
1127
+ const z = Math.round(P[3 * at + 2] * 2e3) | 0;
1128
+ let h = (Math.imul(x, 73856093) ^ Math.imul(y, 19349663) ^ Math.imul(z, 83492791)) & mask;
1129
+ let g = slot[h];
1130
+ while (g >= 0 && (gx[g] !== x || gy[g] !== y || gz[g] !== z)) {
1131
+ h = h + 1 & mask;
1132
+ g = slot[h];
1133
+ }
1134
+ if (g < 0) {
1135
+ g = groups++;
1136
+ slot[h] = g;
1137
+ gx[g] = x;
1138
+ gy[g] = y;
1139
+ gz[g] = z;
1140
+ }
1141
+ groupOf[at] = g;
987
1142
  }
1143
+ return true;
988
1144
  }
989
- const cs = Math.cos(deg * PI / 180);
990
- const out = new Float32Array(n * 3);
991
- for (let i = 0; i < n; i++) {
992
- const fi = (i / 3 | 0) * 3;
993
- const list = map.get(keys[i]);
994
- let sx = 0;
995
- let sy = 0;
996
- let sz = 0;
997
- for (let j = 0; j < list.length; j++) {
998
- const g = list[j] * 3;
999
- if (FN[g] * FN[fi] + FN[g + 1] * FN[fi + 1] + FN[g + 2] * FN[fi + 2] >= cs) {
1000
- sx += FN[g];
1001
- sy += FN[g + 1];
1002
- sz += FN[g + 2];
1145
+ function gather() {
1146
+ start2 = new Int32Array(groups + 1);
1147
+ for (let i = 0; i < n; i++) start2[groupOf[i] + 1]++;
1148
+ for (let g = 0; g < groups; g++) start2[g + 1] += start2[g];
1149
+ const fill = start2.slice(0, groups);
1150
+ members = new Int32Array(n);
1151
+ for (let i = 0; i < n; i++) members[fill[groupOf[i]]++] = i / 3 | 0;
1152
+ }
1153
+ function smoothSlice(deadline) {
1154
+ for (; at < n; at++) {
1155
+ if ((at & 1023) === 0 && performance.now() > deadline) return false;
1156
+ const fi = (at / 3 | 0) * 3;
1157
+ const g = groupOf[at];
1158
+ let sx = 0;
1159
+ let sy = 0;
1160
+ let sz = 0;
1161
+ for (let j = start2[g], end = start2[g + 1]; j < end; j++) {
1162
+ const o = members[j] * 3;
1163
+ if (FN[o] * FN[fi] + FN[o + 1] * FN[fi + 1] + FN[o + 2] * FN[fi + 2] >= cs) {
1164
+ sx += FN[o];
1165
+ sy += FN[o + 1];
1166
+ sz += FN[o + 2];
1167
+ }
1003
1168
  }
1169
+ if (sx * sx + sy * sy + sz * sz < 1e-20) {
1170
+ sx = FN[fi];
1171
+ sy = FN[fi + 1];
1172
+ sz = FN[fi + 2];
1173
+ }
1174
+ const inv = 1 / (Math.sqrt(sx * sx + sy * sy + sz * sz) || 1);
1175
+ out[3 * at] = sx * inv;
1176
+ out[3 * at + 1] = sy * inv;
1177
+ out[3 * at + 2] = sz * inv;
1004
1178
  }
1005
- if (sx * sx + sy * sy + sz * sz < 1e-20) {
1006
- sx = FN[fi];
1007
- sy = FN[fi + 1];
1008
- sz = FN[fi + 2];
1179
+ return true;
1180
+ }
1181
+ function step(deadline) {
1182
+ if (latestCrease.get(geo) !== token) return true;
1183
+ if (phase === 0) {
1184
+ if (!hashSlice(deadline)) return false;
1185
+ gather();
1186
+ phase = 1;
1187
+ at = 0;
1188
+ }
1189
+ if (phase === 1) {
1190
+ const done = smoothSlice(deadline);
1191
+ if (deadline !== Infinity) normal.needsUpdate = true;
1192
+ if (!done) return false;
1193
+ phase = 2;
1194
+ geo.userData.crease = true;
1009
1195
  }
1010
- const inv = 1 / (Math.sqrt(sx * sx + sy * sy + sz * sz) || 1);
1011
- out[3 * i] = sx * inv;
1012
- out[3 * i + 1] = sy * inv;
1013
- out[3 * i + 2] = sz * inv;
1196
+ return true;
1014
1197
  }
1015
- geo.setAttribute("normal", new THREE.BufferAttribute(out, 3));
1016
- geo.userData.crease = true;
1198
+ return { geo, step };
1199
+ }
1200
+ function crease(geo, deg) {
1201
+ creaseTask(geo, deg).step(Infinity);
1017
1202
  return geo;
1018
1203
  }
1019
1204
  function pathShape(items) {
@@ -1160,28 +1345,28 @@ function installGeometry(kit) {
1160
1345
  return g;
1161
1346
  }
1162
1347
  function cutGeometry(geo, tests) {
1163
- const src = geo.index ? geo.toNonIndexed() : geo;
1348
+ const idx = geo.index ? geo.index.array : null;
1164
1349
  const names = ["position"].concat(
1165
1350
  ["color", "uv"].filter(function(n2) {
1166
- return !!src.attributes[n2];
1351
+ return !!geo.attributes[n2];
1167
1352
  })
1168
1353
  );
1169
1354
  const sizes = names.map(function(n2) {
1170
- return src.attributes[n2].itemSize;
1355
+ return geo.attributes[n2].itemSize;
1171
1356
  });
1172
1357
  const arrs = names.map(function(n2) {
1173
- return src.attributes[n2].array;
1358
+ return geo.attributes[n2].array;
1174
1359
  });
1175
- const nv = src.attributes.position.count;
1176
- let tris = [];
1177
- for (let f = 0; f < nv; f += 3) {
1360
+ const nv = idx ? idx.length : geo.attributes.position.count;
1361
+ function readTri(f) {
1178
1362
  const t = [];
1179
1363
  for (let k = 0; k < 3; k++) {
1364
+ const vi = idx ? idx[f + k] : f + k;
1180
1365
  const v = [];
1181
- for (let a = 0; a < names.length; a++) for (let q = 0; q < sizes[a]; q++) v.push(arrs[a][(f + k) * sizes[a] + q]);
1366
+ for (let a = 0; a < names.length; a++) for (let q = 0; q < sizes[a]; q++) v.push(arrs[a][vi * sizes[a] + q]);
1182
1367
  t.push(v);
1183
1368
  }
1184
- tris.push(t);
1369
+ return t;
1185
1370
  }
1186
1371
  const cut = {};
1187
1372
  const n = new V3();
@@ -1207,60 +1392,177 @@ function installGeometry(kit) {
1207
1392
  function fan(poly, arr) {
1208
1393
  for (let i = 1; i < poly.length - 1; i++) arr.push([poly[0], poly[i], poly[i + 1]]);
1209
1394
  }
1210
- tests.forEach(function(T) {
1211
- const next = [];
1395
+ function testTri(T, t, next, rem) {
1396
+ const A = t[0];
1397
+ const B = t[1];
1398
+ const C = t[2];
1399
+ e1.set(B[0] - A[0], B[1] - A[1], B[2] - A[2]);
1400
+ e2.set(C[0] - A[0], C[1] - A[1], C[2] - A[2]);
1401
+ n.crossVectors(e1, e2);
1402
+ const len = n.length();
1403
+ if (len < 1e-12) {
1404
+ next.push(t);
1405
+ return;
1406
+ }
1407
+ n.multiplyScalar(1 / len);
1408
+ ce.set((A[0] + B[0] + C[0]) / 3, (A[1] + B[1] + C[1]) / 3, (A[2] + B[2] + C[2]) / 3);
1409
+ if (T.sel && !T.sel(n, ce)) {
1410
+ next.push(t);
1411
+ return;
1412
+ }
1413
+ const d = [T.sdf(A[0], A[1], A[2]), T.sdf(B[0], B[1], B[2]), T.sdf(C[0], C[1], C[2])];
1414
+ const ins = (d[0] < 0 ? 1 : 0) + (d[1] < 0 ? 1 : 0) + (d[2] < 0 ? 1 : 0);
1415
+ if (ins === 0) next.push(t);
1416
+ else if (ins === 3) {
1417
+ if (rem) rem.push(t);
1418
+ } else {
1419
+ fan(clip(t, d, 1), next);
1420
+ if (rem) fan(clip(t, d, -1), rem);
1421
+ }
1422
+ }
1423
+ const segments = [];
1424
+ let total = 0;
1425
+ const ti = tests.length === 1 && tests[0].bounds ? triIndexOf(geo) : void 0;
1426
+ if (ti) {
1427
+ const T = tests[0];
1428
+ const lo = T.bounds.lo;
1429
+ const hi = T.bounds.hi;
1212
1430
  const rem = T.tag ? cut[T.tag] = cut[T.tag] || [] : null;
1213
- tris.forEach(function(t) {
1214
- const A = t[0];
1215
- const B = t[1];
1216
- const C = t[2];
1217
- e1.set(B[0] - A[0], B[1] - A[1], B[2] - A[2]);
1218
- e2.set(C[0] - A[0], C[1] - A[1], C[2] - A[2]);
1219
- n.crossVectors(e1, e2);
1220
- const len = n.length();
1221
- if (len < 1e-12) {
1222
- next.push(t);
1223
- return;
1224
- }
1225
- n.multiplyScalar(1 / len);
1226
- ce.set((A[0] + B[0] + C[0]) / 3, (A[1] + B[1] + C[1]) / 3, (A[2] + B[2] + C[2]) / 3);
1227
- if (T.sel && !T.sel(n, ce)) {
1228
- next.push(t);
1229
- return;
1431
+ const boxes = ti.boxes;
1432
+ const blocks = ti.blocks;
1433
+ let runFrom = -1;
1434
+ const closeRun = function(end) {
1435
+ if (runFrom < 0) return;
1436
+ segments.push([runFrom, end]);
1437
+ total += end - runFrom;
1438
+ runFrom = -1;
1439
+ };
1440
+ for (let blk = 0, t0 = 0; t0 < ti.triangles; blk++, t0 += TRI_BLOCK) {
1441
+ const j = blk * 6;
1442
+ if (blocks[j + 3] < lo[0] || blocks[j] > hi[0] || blocks[j + 4] < lo[1] || blocks[j + 1] > hi[1] || blocks[j + 5] < lo[2] || blocks[j + 2] > hi[2]) {
1443
+ if (runFrom < 0) runFrom = t0;
1444
+ continue;
1230
1445
  }
1231
- const d = [T.sdf(A[0], A[1], A[2]), T.sdf(B[0], B[1], B[2]), T.sdf(C[0], C[1], C[2])];
1232
- const ins = (d[0] < 0 ? 1 : 0) + (d[1] < 0 ? 1 : 0) + (d[2] < 0 ? 1 : 0);
1233
- if (ins === 0) next.push(t);
1234
- else if (ins === 3) {
1235
- if (rem) rem.push(t);
1236
- } else {
1237
- fan(clip(t, d, 1), next);
1238
- if (rem) fan(clip(t, d, -1), rem);
1446
+ const t1 = Math.min(ti.triangles, t0 + TRI_BLOCK);
1447
+ for (let t = t0; t < t1; t++) {
1448
+ const q = t * 6;
1449
+ if (boxes[q + 3] < lo[0] || boxes[q] > hi[0] || boxes[q + 4] < lo[1] || boxes[q + 1] > hi[1] || boxes[q + 5] < lo[2] || boxes[q + 2] > hi[2]) {
1450
+ if (runFrom < 0) runFrom = t;
1451
+ continue;
1452
+ }
1453
+ closeRun(t);
1454
+ const next = [];
1455
+ testTri(T, readTri(t * 3), next, rem);
1456
+ if (next.length) {
1457
+ segments.push(next);
1458
+ total += next.length;
1459
+ }
1239
1460
  }
1461
+ }
1462
+ closeRun(ti.triangles);
1463
+ } else {
1464
+ let tris = [];
1465
+ for (let f = 0; f < nv; f += 3) tris.push(readTri(f));
1466
+ tests.forEach(function(T) {
1467
+ const next = [];
1468
+ const rem = T.tag ? cut[T.tag] = cut[T.tag] || [] : null;
1469
+ tris.forEach(function(t) {
1470
+ testTri(T, t, next, rem);
1471
+ });
1472
+ tris = next;
1240
1473
  });
1241
- tris = next;
1242
- });
1243
- function build(list) {
1474
+ segments.push(tris);
1475
+ total = tris.length;
1476
+ }
1477
+ function fill(segs, count, boxesOut2) {
1244
1478
  const g = new THREE.BufferGeometry();
1245
- let off = 0;
1246
- names.forEach(function(nm, a) {
1247
- const arr = new Float32Array(list.length * 3 * sizes[a]);
1248
- let w = 0;
1249
- list.forEach(function(t) {
1250
- for (let k = 0; k < 3; k++) for (let q = 0; q < sizes[a]; q++) arr[w++] = t[k][off + q];
1479
+ const dst = names.map(function(_nm, a) {
1480
+ return new Float32Array(count * 3 * sizes[a]);
1481
+ });
1482
+ const w = names.map(function() {
1483
+ return 0;
1484
+ });
1485
+ let wb = 0;
1486
+ segs.forEach(function(seg) {
1487
+ if (typeof seg[0] === "number") {
1488
+ for (let a = 0; a < names.length; a++) {
1489
+ const size = sizes[a];
1490
+ if (!idx) {
1491
+ dst[a].set(arrs[a].subarray(seg[0] * 3 * size, seg[1] * 3 * size), w[a]);
1492
+ w[a] += (seg[1] - seg[0]) * 3 * size;
1493
+ continue;
1494
+ }
1495
+ for (let f = seg[0] * 3; f < seg[1] * 3; f++) for (let q = 0; q < size; q++) dst[a][w[a]++] = arrs[a][idx[f] * size + q];
1496
+ }
1497
+ if (boxesOut2 && ti) {
1498
+ boxesOut2.set(ti.boxes.subarray(seg[0] * 6, seg[1] * 6), wb);
1499
+ wb += (seg[1] - seg[0]) * 6;
1500
+ }
1501
+ return;
1502
+ }
1503
+ seg.forEach(function(t) {
1504
+ if (boxesOut2) {
1505
+ for (let k = 0; k < 3; k++) {
1506
+ boxesOut2[wb + k] = Math.fround(Math.min(t[0][k], t[1][k], t[2][k]));
1507
+ boxesOut2[wb + 3 + k] = Math.fround(Math.max(t[0][k], t[1][k], t[2][k]));
1508
+ }
1509
+ wb += 6;
1510
+ }
1511
+ let off = 0;
1512
+ for (let a = 0; a < names.length; a++) {
1513
+ for (let k = 0; k < 3; k++) for (let q = 0; q < sizes[a]; q++) dst[a][w[a]++] = t[k][off + q];
1514
+ off += sizes[a];
1515
+ }
1251
1516
  });
1252
- g.setAttribute(nm, new THREE.BufferAttribute(arr, sizes[a]));
1253
- off += sizes[a];
1517
+ });
1518
+ names.forEach(function(nm, a) {
1519
+ g.setAttribute(nm, new THREE.BufferAttribute(dst[a], sizes[a]));
1254
1520
  });
1255
1521
  g.userData.crease = true;
1256
1522
  return g;
1257
1523
  }
1258
- const out = { geo: build(tris), cut: {} };
1524
+ const boxesOut = ti ? new Float64Array(total * 6) : void 0;
1525
+ const out = { geo: fill(segments, total, boxesOut), cut: {}, tasks: {} };
1526
+ if (boxesOut) storeTriIndex(out.geo, boxesOut);
1259
1527
  Object.keys(cut).forEach(function(k) {
1260
- out.cut[k] = crease(build(cut[k]), 42);
1528
+ const piece = fill([cut[k]], cut[k].length);
1529
+ const mode = tests.find(function(T) {
1530
+ return T.tag === k;
1531
+ }).pieces;
1532
+ if (mode === "none") out.cut[k] = piece;
1533
+ else if (mode === "later") {
1534
+ out.cut[k] = piece;
1535
+ out.tasks[k] = creaseTask(piece, 42);
1536
+ } else out.cut[k] = crease(piece, 42);
1261
1537
  });
1262
1538
  return out;
1263
1539
  }
1540
+ function refreshBounds(geo) {
1541
+ const found = boundsFromIndex(geo);
1542
+ if (!found) {
1543
+ geo.computeBoundingSphere();
1544
+ geo.computeBoundingBox();
1545
+ return;
1546
+ }
1547
+ if (geo.boundingBox && geo.boundingSphere && boundsDone.get(geo) === found.version) return;
1548
+ const b = found.box;
1549
+ if (!geo.boundingBox) geo.boundingBox = new THREE.Box3();
1550
+ geo.boundingBox.min.set(b[0], b[1], b[2]);
1551
+ geo.boundingBox.max.set(b[3], b[4], b[5]);
1552
+ if (!geo.boundingSphere) geo.boundingSphere = new THREE.Sphere();
1553
+ const center = geo.boundingBox.getCenter(geo.boundingSphere.center);
1554
+ const P = geo.attributes.position.array;
1555
+ let maxRadiusSq = 0;
1556
+ for (let i = 0; i < P.length; i += 3) {
1557
+ const dx = center.x - P[i];
1558
+ const dy = center.y - P[i + 1];
1559
+ const dz = center.z - P[i + 2];
1560
+ maxRadiusSq = Math.max(maxRadiusSq, dx * dx + dy * dy + dz * dz);
1561
+ }
1562
+ geo.boundingSphere.radius = Math.sqrt(maxRadiusSq);
1563
+ boundsDone.set(geo, found.version);
1564
+ }
1565
+ const boundsDone = /* @__PURE__ */ new WeakMap();
1264
1566
  function mergeGeometries(list) {
1265
1567
  const pos = [];
1266
1568
  const nor = [];
@@ -1391,7 +1693,7 @@ function installGeometry(kit) {
1391
1693
  }
1392
1694
  };
1393
1695
  };
1394
- Object.assign(I, { roundedBoxGeometry, taper, subdivide, crease, pathShape, tumble, loftGeometry, hullGeometry, cutGeometry, mergeGeometries, uvProject });
1696
+ Object.assign(I, { roundedBoxGeometry, taper, subdivide, crease, creaseTask, refreshBounds, pathShape, tumble, loftGeometry, hullGeometry, cutGeometry, mergeGeometries, uvProject });
1395
1697
  }
1396
1698
 
1397
1699
  // src/context.ts
@@ -4745,8 +5047,15 @@ function installGlassDamage(kit) {
4745
5047
  x.arc(cx, cy, 26 * s, 0, PI * 2);
4746
5048
  x.fill();
4747
5049
  }
5050
+ const NO_CANVAS = new Proxy(function() {
5051
+ }, { get: () => NO_CANVAS, apply: () => NO_CANVAS, set: () => true });
4748
5052
  function crackGlass(c, msh, p, force, sc) {
4749
5053
  const G = msh.userData.glass;
5054
+ if (G.cracksFreed) {
5055
+ drawWeb(NO_CANVAS, 0, 0, sc != null ? sc : 0.6 + force * 0.5, Math.max(0.4, G.aspect || 1), c.rand);
5056
+ G.hits++;
5057
+ return;
5058
+ }
4750
5059
  if (!G.cv) {
4751
5060
  G.cv = document2.createElement("canvas");
4752
5061
  G.cv.width = G.cv.height = 256;
@@ -4829,6 +5138,14 @@ function installGlassDamage(kit) {
4829
5138
  const G = msh.userData.glass;
4830
5139
  if (G.broken) return;
4831
5140
  G.broken = true;
5141
+ const freeCracks = function() {
5142
+ if (!G.tex) return;
5143
+ if (msh.material !== MATERIALS.glassEdge && msh.material?.map === G.tex) msh.material.dispose();
5144
+ G.tex.dispose();
5145
+ G.tex = null;
5146
+ G.cv = null;
5147
+ G.cracksFreed = true;
5148
+ };
4832
5149
  const cubesAt = [];
4833
5150
  if (G.outline && G.T !== void 0) {
4834
5151
  const out = G.outline;
@@ -4862,6 +5179,7 @@ function installGlassDamage(kit) {
4862
5179
  geo.computeVertexNormals();
4863
5180
  msh.geometry.dispose();
4864
5181
  msh.geometry = geo;
5182
+ freeCracks();
4865
5183
  msh.material = MATERIALS.glassEdge;
4866
5184
  let minx = 1e9;
4867
5185
  let maxx = -1e9;
@@ -4885,8 +5203,12 @@ function installGlassDamage(kit) {
4885
5203
  if (rim) {
4886
5204
  msh.geometry.dispose();
4887
5205
  msh.geometry = rim;
5206
+ freeCracks();
4888
5207
  msh.material = MATERIALS.glassEdge;
4889
- } else msh.visible = false;
5208
+ } else {
5209
+ freeCracks();
5210
+ msh.visible = false;
5211
+ }
4890
5212
  for (let j = 0; j < 36; j++)
4891
5213
  cubesAt.push(
4892
5214
  new V3(
@@ -6721,6 +7043,8 @@ function installZoneDecals(kit) {
6721
7043
  const pos = g && g.attributes && g.attributes.position;
6722
7044
  return o.raycast === THREE.Mesh.prototype.raycast && !o.isSkinnedMesh && !Array.isArray(o.material) && pos && !pos.isInterleavedBufferAttribute && pos.itemSize === 3 && !g.groups.length && !(g.morphAttributes && g.morphAttributes.position) && (g.index ? g.index.count : pos.count) / 3 >= PROXY_MIN_TRIANGLES;
6723
7045
  }
7046
+ let _keep = new Uint32Array(0);
7047
+ let _box = new Float32Array(0);
6724
7048
  function proxyIn(o, Mw, R) {
6725
7049
  _pm.copy(o.matrixWorld).invert().multiply(Mw);
6726
7050
  const lo = [Infinity, Infinity, Infinity];
@@ -6733,39 +7057,48 @@ function installZoneDecals(kit) {
6733
7057
  }
6734
7058
  }
6735
7059
  const g = o.geometry;
6736
- const P = g.attributes.position.array;
7060
+ const ti = triIndexOf(g);
7061
+ if (!ti) return null;
6737
7062
  const ix = g.index ? g.index.array : null;
6738
- const n = ix ? ix.length : P.length / 3;
6739
- const keep = new Uint32Array(n - n % 3);
7063
+ const boxes = ti.boxes;
7064
+ const blocks = ti.blocks;
7065
+ if (_keep.length < ti.triangles * 3) {
7066
+ _keep = new Uint32Array(ti.triangles * 3);
7067
+ _box = new Float32Array(ti.triangles * 6);
7068
+ }
7069
+ const keep = _keep;
7070
+ const box = _box;
6740
7071
  let nk = 0;
6741
- for (let t = 0; t + 2 < n; t += 3) {
6742
- const a = (ix ? ix[t] : t) * 3;
6743
- const b = (ix ? ix[t + 1] : t + 1) * 3;
6744
- const d = (ix ? ix[t + 2] : t + 2) * 3;
6745
- let out = false;
6746
- for (let k = 0; k < 3 && !out; k++) {
6747
- const u = P[a + k];
6748
- const v = P[b + k];
6749
- const w = P[d + k];
6750
- out = Math.max(u, v, w) < lo[k] || Math.min(u, v, w) > hi[k];
6751
- }
6752
- if (!out) {
6753
- keep[nk++] = ix ? ix[t] : t;
6754
- keep[nk++] = ix ? ix[t + 1] : t + 1;
6755
- keep[nk++] = ix ? ix[t + 2] : t + 2;
7072
+ let nt = 0;
7073
+ for (let blk = 0, t0 = 0; t0 < ti.triangles; blk++, t0 += TRI_BLOCK) {
7074
+ const j = blk * 6;
7075
+ if (blocks[j + 3] < lo[0] || blocks[j] > hi[0] || blocks[j + 4] < lo[1] || blocks[j + 1] > hi[1] || blocks[j + 5] < lo[2] || blocks[j + 2] > hi[2]) continue;
7076
+ const t1 = Math.min(ti.triangles, t0 + TRI_BLOCK);
7077
+ for (let t = t0; t < t1; t++) {
7078
+ const q = t * 6;
7079
+ if (boxes[q + 3] < lo[0] || boxes[q] > hi[0] || boxes[q + 4] < lo[1] || boxes[q + 1] > hi[1] || boxes[q + 5] < lo[2] || boxes[q + 2] > hi[2]) continue;
7080
+ keep[nk++] = ix ? ix[t * 3] : t * 3;
7081
+ keep[nk++] = ix ? ix[t * 3 + 1] : t * 3 + 1;
7082
+ keep[nk++] = ix ? ix[t * 3 + 2] : t * 3 + 2;
7083
+ for (let k = 0; k < 6; k++) box[nt * 6 + k] = boxes[q + k];
7084
+ nt++;
6756
7085
  }
6757
7086
  }
6758
7087
  if (!nk) return null;
6759
7088
  const kept = keep.slice(0, nk);
6760
- const box = new Float32Array(kept.length / 3 * 6);
6761
- for (let t = 0, j = 0; t < kept.length; t += 3, j += 6) {
6762
- const a = kept[t] * 3;
6763
- const b = kept[t + 1] * 3;
6764
- const d = kept[t + 2] * 3;
7089
+ const keptBox = box.slice(0, nt * 6);
7090
+ const keptBlocks = new Float32Array(Math.ceil(nt / TRI_BLOCK) * 6);
7091
+ for (let blk = 0; blk * TRI_BLOCK < nt; blk++) {
7092
+ const b = blk * 6;
6765
7093
  for (let k = 0; k < 3; k++) {
6766
- box[j + k] = Math.min(P[a + k], P[b + k], P[d + k]);
6767
- box[j + 3 + k] = Math.max(P[a + k], P[b + k], P[d + k]);
7094
+ keptBlocks[b + k] = Infinity;
7095
+ keptBlocks[b + 3 + k] = -Infinity;
6768
7096
  }
7097
+ for (let t = blk * TRI_BLOCK, t1 = Math.min(nt, t + TRI_BLOCK); t < t1; t++)
7098
+ for (let k = 0; k < 3; k++) {
7099
+ if (keptBox[t * 6 + k] < keptBlocks[b + k]) keptBlocks[b + k] = keptBox[t * 6 + k];
7100
+ if (keptBox[t * 6 + 3 + k] > keptBlocks[b + 3 + k]) keptBlocks[b + 3 + k] = keptBox[t * 6 + 3 + k];
7101
+ }
6769
7102
  }
6770
7103
  const pg = new THREE.BufferGeometry();
6771
7104
  pg.setAttribute("position", g.attributes.position);
@@ -6776,7 +7109,8 @@ function installZoneDecals(kit) {
6776
7109
  proxy.layers.mask = o.layers.mask;
6777
7110
  proxy.proxyOf = o;
6778
7111
  proxy.kept = kept;
6779
- proxy.keptBox = box;
7112
+ proxy.keptBox = keptBox;
7113
+ proxy.keptBlocks = keptBlocks;
6780
7114
  return proxy;
6781
7115
  }
6782
7116
  function narrowTo(rc, proxy) {
@@ -6796,13 +7130,21 @@ function installZoneDecals(kit) {
6796
7130
  const hi2 = Math.max(z0, _pv.z) + 1e-4;
6797
7131
  const K = proxy.kept;
6798
7132
  const X = proxy.keptBox;
7133
+ const KB = proxy.keptBlocks;
7134
+ const nt = K.length / 3;
6799
7135
  const dst = proxy.geometry.index.array;
6800
7136
  let m = 0;
6801
- for (let t = 0, j = 0; t < K.length; t += 3, j += 6) {
6802
- if (X[j + 3] < lo0 || X[j] > hi0 || X[j + 4] < lo1 || X[j + 1] > hi1 || X[j + 5] < lo2 || X[j + 2] > hi2) continue;
6803
- dst[m++] = K[t];
6804
- dst[m++] = K[t + 1];
6805
- dst[m++] = K[t + 2];
7137
+ for (let blk = 0, t0 = 0; t0 < nt; blk++, t0 += TRI_BLOCK) {
7138
+ const b = blk * 6;
7139
+ if (KB[b + 3] < lo0 || KB[b] > hi0 || KB[b + 4] < lo1 || KB[b + 1] > hi1 || KB[b + 5] < lo2 || KB[b + 2] > hi2) continue;
7140
+ const t1 = Math.min(nt, t0 + TRI_BLOCK);
7141
+ for (let t = t0; t < t1; t++) {
7142
+ const j = t * 6;
7143
+ if (X[j + 3] < lo0 || X[j] > hi0 || X[j + 4] < lo1 || X[j + 1] > hi1 || X[j + 5] < lo2 || X[j + 2] > hi2) continue;
7144
+ dst[m++] = K[t * 3];
7145
+ dst[m++] = K[t * 3 + 1];
7146
+ dst[m++] = K[t * 3 + 2];
7147
+ }
6806
7148
  }
6807
7149
  proxy.geometry.setDrawRange(0, m);
6808
7150
  }
@@ -7220,6 +7562,8 @@ function installZoneDent(kit) {
7220
7562
  irel.copy(rel).invert();
7221
7563
  const pos = g.attributes.position;
7222
7564
  let changed = false;
7565
+ const liveIndex = liveTriIndex(g);
7566
+ const movedVertices = [];
7223
7567
  const near = boxInFrame(B, irel, v);
7224
7568
  for (let i = 0; i < pos.count; i++) {
7225
7569
  v.fromBufferAttribute(pos, i);
@@ -7231,6 +7575,7 @@ function installZoneDent(kit) {
7231
7575
  if (opt.extra) opt.extra(v, f);
7232
7576
  v.applyMatrix4(irel);
7233
7577
  pos.setXYZ(i, v.x, v.y, v.z);
7578
+ movedVertices.push(i);
7234
7579
  changed = true;
7235
7580
  }
7236
7581
  if (opt.extraAll) {
@@ -7239,12 +7584,14 @@ function installZoneDent(kit) {
7239
7584
  if (opt.extraAll(v)) {
7240
7585
  v.applyMatrix4(irel);
7241
7586
  pos.setXYZ(j, v.x, v.y, v.z);
7587
+ movedVertices.push(j);
7242
7588
  changed = true;
7243
7589
  }
7244
7590
  }
7245
7591
  }
7246
7592
  if (changed) {
7247
7593
  pos.needsUpdate = true;
7594
+ if (liveIndex) touchTriIndex(g, liveIndex, movedVertices);
7248
7595
  dirty.add(msh);
7249
7596
  }
7250
7597
  if (changed || boundsDue) {
@@ -7275,6 +7622,26 @@ function installZoneDent(kit) {
7275
7622
  });
7276
7623
  return { field, moved, ax, a, cu, cv, hu, hv, outer };
7277
7624
  }
7625
+ function panelBoundsIn(rel, rect, B, a, ax) {
7626
+ const det = rel.determinant();
7627
+ if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return void 0;
7628
+ const back = rel.clone().invert();
7629
+ const lo = [Infinity, Infinity, Infinity];
7630
+ const hi = [-Infinity, -Infinity, -Infinity];
7631
+ const q = new V3();
7632
+ for (let i = 0; i < 8; i++) {
7633
+ q.setComponent(ax[0], i & 1 ? rect[1] : rect[0]);
7634
+ q.setComponent(ax[1], i & 2 ? rect[3] : rect[2]);
7635
+ q.setComponent(a, i & 4 ? I.boxHi(B, a) : I.boxLo(B, a));
7636
+ q.applyMatrix4(back);
7637
+ for (let k = 0; k < 3; k++) {
7638
+ lo[k] = Math.min(lo[k], q.getComponent(k));
7639
+ hi[k] = Math.max(hi[k], q.getComponent(k));
7640
+ }
7641
+ }
7642
+ const pad = 1e-3;
7643
+ return { lo: lo.map((x) => x - pad), hi: hi.map((x) => x + pad) };
7644
+ }
7278
7645
  function cutPanel(c, z, rect, keep, dirty, o) {
7279
7646
  o = o || {};
7280
7647
  const B = z.box;
@@ -7307,6 +7674,7 @@ function installZoneDent(kit) {
7307
7674
  }
7308
7675
  const test = {
7309
7676
  tag: "part",
7677
+ pieces: keep ? o.pieceTasks ? "later" : "crease" : "none",
7310
7678
  sel: o.anyN || z.cutAll ? null : function(n) {
7311
7679
  nv.copy(n).applyMatrix3(nm).normalize();
7312
7680
  return nv.dot(outN) > 0.3;
@@ -7325,13 +7693,12 @@ function installZoneDent(kit) {
7325
7693
  return q;
7326
7694
  }
7327
7695
  };
7328
- const r = cutGeometry(g, [test]);
7696
+ const r = cutGeometry(g, [{ ...test, bounds: panelBoundsIn(rel, rect, B, a, ax) }]);
7329
7697
  const cg = r.cut.part;
7330
7698
  if (!cg || !cg.attributes.position.count) return;
7331
7699
  g.dispose();
7332
7700
  msh.geometry = r.geo;
7333
- r.geo.computeBoundingSphere();
7334
- r.geo.computeBoundingBox();
7701
+ I.refreshBounds(r.geo);
7335
7702
  dirty.add(msh);
7336
7703
  if (!keep) {
7337
7704
  cg.dispose();
@@ -7344,6 +7711,7 @@ function installZoneDent(kit) {
7344
7711
  rel.decompose(pm.position, pm.quaternion, pm.scale);
7345
7712
  c.vehicle.add(pm);
7346
7713
  pm.updateMatrixWorld(true);
7714
+ if (o.pieceTasks) o.pieceTasks.set(pm, r.tasks.part);
7347
7715
  pieces.push({ mesh: pm, cut: true });
7348
7716
  });
7349
7717
  return pieces;
@@ -10983,7 +11351,15 @@ var VehicleVisuals = class {
10983
11351
  __publicField(this, "gone", /* @__PURE__ */ new Set());
10984
11352
  /** Shows or hides the plate and the recess of a door or lid by its state (see `showWhenOpen`). */
10985
11353
  __publicField(this, "syncPlates", /* @__PURE__ */ new Map());
11354
+ /** Doors and lids whose own skin was cut from the body and hung on the hinge (true), or that had none to take and keep the plate (false). */
11355
+ __publicField(this, "skins", /* @__PURE__ */ new Map());
10986
11356
  __publicField(this, "lightsOn", true);
11357
+ /** Heavy body meshes whose triangle index is still to build (null once done). */
11358
+ __publicField(this, "warming");
11359
+ /** A panel was torn off since the last `pump`: that frame already paid for the cut, the smoothing starts on the next one. */
11360
+ __publicField(this, "tornThisFrame", false);
11361
+ /** Normal smoothing still running, by mesh: a mesh cut again gets a new task and the old one is dropped. */
11362
+ __publicField(this, "smoothing", /* @__PURE__ */ new Map());
10987
11363
  __publicField(this, "hood");
10988
11364
  this.kit = kit;
10989
11365
  this.c = c;
@@ -10993,6 +11369,8 @@ var VehicleVisuals = class {
10993
11369
  for (const p of model.parts) this.byId.set(p.id, p);
10994
11370
  c.root.updateMatrixWorld(true);
10995
11371
  this.hood = this.nameHood();
11372
+ this.warming = c.deform.filter((msh) => msh.geometry && msh.geometry.attributes.position && msh.geometry.attributes.position.count >= 1500);
11373
+ if (!this.warming.length) this.warming = null;
10996
11374
  for (const p of model.parts) {
10997
11375
  if (p.role === "door" || p.role === "lid") this.makePlate(p);
10998
11376
  else if (p.role === "mirror" && !this.mirrorObjects(p).length) this.makeMirror(p);
@@ -11128,11 +11506,21 @@ var VehicleVisuals = class {
11128
11506
  * keeps the shape of its model instead of flat panels over its curved sides. Follows the hinge's rotation, whoever turns it (the clips of a host, `detach`).
11129
11507
  */
11130
11508
  showWhenOpen(id, piv, panel, recess) {
11509
+ let busy = false;
11131
11510
  const sync = () => {
11511
+ if (busy) return;
11132
11512
  const r = piv.rotation;
11133
11513
  const open = Math.abs(r.x) + Math.abs(r.y) + Math.abs(r.z) > 1e-3 || this.gone.has(id) || piv.parent !== this.c.vehicle;
11134
- panel.visible = open;
11135
- recess.visible = open;
11514
+ if (open && !this.gone.has(id) && piv.parent === this.c.vehicle && !this.skins.has(id)) {
11515
+ busy = true;
11516
+ try {
11517
+ this.skins.set(id, this.takeSkin(id, piv));
11518
+ } finally {
11519
+ busy = false;
11520
+ }
11521
+ }
11522
+ panel.visible = open && !this.skins.get(id);
11523
+ recess.visible = open && !this.skins.get(id);
11136
11524
  };
11137
11525
  const euler = piv.rotation;
11138
11526
  const quaternion = piv.quaternion;
@@ -11149,6 +11537,58 @@ var VehicleVisuals = class {
11149
11537
  this.syncPlates.set(id, sync);
11150
11538
  sync();
11151
11539
  }
11540
+ /**
11541
+ * Cuts the skin of a door or lid zone out of the body (as the damage's hanging door does) and hangs it, with the thin parts on it (trim, handle) and, for a door, the side
11542
+ * window over it, on the hinge. Done with the hinge closed, so every piece keeps its place; answers whether there was skin to take.
11543
+ */
11544
+ takeSkin(id, piv) {
11545
+ const p = this.byId.get(id);
11546
+ const z = p && this.zone(p);
11547
+ if (!p || !z) return false;
11548
+ const I = this.I;
11549
+ const B = z.box;
11550
+ const q = piv.quaternion.clone();
11551
+ piv.quaternion.identity();
11552
+ this.c.root.updateMatrixWorld(true);
11553
+ const inv = I.worldInverseOf(this.c);
11554
+ let objs = [];
11555
+ try {
11556
+ if (p.role === "door") {
11557
+ const dirty = /* @__PURE__ */ new Set();
11558
+ const rect = [B.x0 + 0.03, B.x1 - 0.03, B.y0 + Math.min(0.08, (B.y1 - B.y0) * 0.1), B.y1 - 0.02];
11559
+ const pcs = I.cutPanel(this.c, z, rect, true, dirty).map((x) => x.mesh).filter((m) => !!m);
11560
+ this.mend(dirty);
11561
+ if (pcs.length) objs = [...pcs, ...I.followersIn(this.c, z, inv), ...I.windowsOver(this.c, z, inv)];
11562
+ } else {
11563
+ const pcs = this.cutZone(z);
11564
+ if (pcs.length) objs = [...pcs, ...I.followersIn(this.c, z, inv)];
11565
+ }
11566
+ } catch {
11567
+ objs = [];
11568
+ }
11569
+ const deform = this.c.deform ?? [];
11570
+ for (const o of objs) {
11571
+ if (!o.parent || o === piv) continue;
11572
+ piv.attach(o);
11573
+ o.traverse((x) => {
11574
+ x.userData.noFollow = true;
11575
+ });
11576
+ const di = deform.indexOf(o);
11577
+ if (di >= 0) deform.splice(di, 1);
11578
+ }
11579
+ piv.quaternion.copy(q);
11580
+ this.c.root.updateMatrixWorld(true);
11581
+ return objs.length > 0;
11582
+ }
11583
+ mend(dirty) {
11584
+ dirty.forEach((msh) => {
11585
+ const g = msh.geometry;
11586
+ if (g.userData.crease || !g.index) this.I.crease(g, 42);
11587
+ else g.computeVertexNormals();
11588
+ g.computeBoundingSphere();
11589
+ g.computeBoundingBox();
11590
+ });
11591
+ }
11152
11592
  makeMirror(p) {
11153
11593
  const [cx, cy, cz] = p.box.center;
11154
11594
  const s = cz < 0 ? -1 : 1;
@@ -11286,6 +11726,31 @@ var VehicleVisuals = class {
11286
11726
  this.I.flattenTire(w, Math.min(0.5, 0.12 + 0.38 * melt));
11287
11727
  }
11288
11728
  }
11729
+ /** Smooths the normals of a cut mesh a slice per frame (see `pump`); until then it draws with flat face normals. */
11730
+ smoothLater(msh) {
11731
+ this.smoothing.set(msh, this.I.creaseTask(msh.geometry, 42));
11732
+ this.tornThisFrame = true;
11733
+ }
11734
+ /** Spends at most `budgetMs` finishing the pending normal smoothing. Call once per frame. */
11735
+ pump(budgetMs = 2.5) {
11736
+ if (this.tornThisFrame) {
11737
+ this.tornThisFrame = false;
11738
+ return;
11739
+ }
11740
+ if (!this.smoothing.size && !this.warming) return;
11741
+ const deadline = performance.now() + budgetMs;
11742
+ for (const [msh, task] of this.smoothing) {
11743
+ if (msh.geometry !== task.geo || task.step(deadline)) this.smoothing.delete(msh);
11744
+ if (performance.now() >= deadline) return;
11745
+ }
11746
+ this.warmIndexes(deadline);
11747
+ }
11748
+ /** Measures the triangles of the heavy body meshes once, a slice per frame, so the first panel torn off finds them ready (see `triIndexOf`). */
11749
+ warmIndexes(deadline) {
11750
+ if (!this.warming) return;
11751
+ this.warming = this.warming.filter((msh) => !warmTriIndex(msh.geometry, deadline));
11752
+ if (!this.warming.length) this.warming = null;
11753
+ }
11289
11754
  /** Moves the panel of a lost zone out of the body (the kit's torn-off look). */
11290
11755
  tearOff(p) {
11291
11756
  const z = this.zone(p);
@@ -11298,10 +11763,9 @@ var VehicleVisuals = class {
11298
11763
  }
11299
11764
  dirty.forEach((msh) => {
11300
11765
  const g = msh.geometry;
11301
- if (g.userData.crease || !g.index) this.I.crease(g, 42);
11766
+ if (g.userData.crease || !g.index) this.smoothLater(msh);
11302
11767
  else g.computeVertexNormals();
11303
- g.computeBoundingSphere();
11304
- g.computeBoundingBox();
11768
+ this.I.refreshBounds(g);
11305
11769
  });
11306
11770
  this.gone.add(p.id);
11307
11771
  }
@@ -11444,17 +11908,19 @@ var VehicleVisuals = class {
11444
11908
  const mg = Math.min(0.05, (u1 - u0) * 0.08, (v1 - v0) * 0.08);
11445
11909
  const dirty = /* @__PURE__ */ new Set();
11446
11910
  let pcs = [];
11911
+ const pieceTasks = /* @__PURE__ */ new Map();
11447
11912
  try {
11448
- pcs = I.cutPanel(this.c, z, [u0 + mg, u1 - mg, v0 + mg, v1 - mg], true, dirty);
11913
+ pcs = I.cutPanel(this.c, z, [u0 + mg, u1 - mg, v0 + mg, v1 - mg], true, dirty, { pieceTasks });
11449
11914
  } catch {
11450
11915
  pcs = [];
11451
11916
  }
11917
+ pieceTasks.forEach((task, mesh) => this.smoothing.set(mesh, task));
11918
+ this.tornThisFrame = true;
11452
11919
  dirty.forEach((msh) => {
11453
11920
  const g = msh.geometry;
11454
- if (g.userData.crease || !g.index) I.crease(g, 42);
11921
+ if (g.userData.crease || !g.index) this.smoothLater(msh);
11455
11922
  else g.computeVertexNormals();
11456
- g.computeBoundingSphere();
11457
- g.computeBoundingBox();
11923
+ I.refreshBounds(g);
11458
11924
  });
11459
11925
  return pcs.map((q) => q.mesh).filter((m) => !!m);
11460
11926
  }
@@ -12233,7 +12699,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12233
12699
  specCache = { name: options.name ?? c.spec.name ?? c.key, parts, joints, tags: ["vehicle", kindOf(c)] };
12234
12700
  return specCache;
12235
12701
  };
12236
- const state = () => {
12702
+ const buildState = () => {
12237
12703
  const parts = model.parts.map((d) => {
12238
12704
  const p = ps.get(d.id);
12239
12705
  const marked = p.dents.length > 0 || p.holes.length > 0 || p.cracks.length > 0;
@@ -12270,7 +12736,15 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12270
12736
  }));
12271
12737
  return { id, name: options.name ?? c.spec.name ?? c.key, position: [0, 0, 0], rotation: [0, 0, 0, 1], parts, joints, groups: groupsCache.map((g) => [...g]), destroyed: parts.every((p) => p.status === "broken" || p.status === "melted" || p.status === "ashes") };
12272
12738
  };
12739
+ let stateCache;
12740
+ const state = () => stateCache ?? (stateCache = buildState());
12741
+ const isSimulating = () => {
12742
+ if (leaks.size > 0) return true;
12743
+ for (const p of ps.values()) if (!p.lost && (p.burning || p.temp > AMBIENT)) return true;
12744
+ return false;
12745
+ };
12273
12746
  const hit = (h) => {
12747
+ stateCache = void 0;
12274
12748
  const out = [];
12275
12749
  const hh = h.space === "local" ? { ...h, space: "world", point: toWorld(h.point) } : h;
12276
12750
  if (hh.space === "local" || !hh.point) return out;
@@ -12297,6 +12771,8 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12297
12771
  clock += dt;
12298
12772
  const out = queue;
12299
12773
  queue = [];
12774
+ const wasSimulating = isSimulating();
12775
+ const handledBefore = (blastHandled ? 1 : 0) + (rollHandled ? 2 : 0);
12300
12776
  const hood = c.hood;
12301
12777
  if (hood) {
12302
12778
  const sign = hood.sign || 1;
@@ -12317,12 +12793,15 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12317
12793
  afterBlast(out);
12318
12794
  afterRoll(out);
12319
12795
  applyEffects();
12796
+ vis?.pump();
12797
+ if (wasSimulating || out.length > 0 || handledBefore !== (blastHandled ? 1 : 0) + (rollHandled ? 2 : 0)) stateCache = void 0;
12320
12798
  return out;
12321
12799
  };
12322
12800
  const disassemble = (jointId, tool, dt) => {
12323
12801
  const j = js.find((x) => x.def.id === jointId);
12324
12802
  if (!j || !j.def.tools.includes(tool)) return -1;
12325
12803
  if (j.open) return 1;
12804
+ stateCache = void 0;
12326
12805
  j.progress = Math.min(1, j.progress + dt / j.def.seconds);
12327
12806
  const step = Math.floor(j.progress * 4) / 4;
12328
12807
  if (step > j.reported && j.progress < 1) {
@@ -12338,6 +12817,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12338
12817
  return j.progress;
12339
12818
  };
12340
12819
  const extinguish = (partId) => {
12820
+ stateCache = void 0;
12341
12821
  for (const p of ps.values()) {
12342
12822
  if (partId && p.def.id !== partId) continue;
12343
12823
  if (p.burning) {
@@ -12351,6 +12831,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12351
12831
  }
12352
12832
  };
12353
12833
  const reset = () => {
12834
+ stateCache = void 0;
12354
12835
  const old = c;
12355
12836
  const parent = old.root.parent;
12356
12837
  const next = kit.build(old.key, old.state, old.scenario != null ? { scenario: old.scenario } : void 0);
@@ -12368,6 +12849,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12368
12849
  setup();
12369
12850
  };
12370
12851
  const rollover = (side2 = 1) => {
12852
+ stateCache = void 0;
12371
12853
  if (kit.roll(c, side2)) rolled = true;
12372
12854
  void rolled;
12373
12855
  };