@adatechnology/game-vehicles 0.4.4 → 0.4.6

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
+ }
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];
1003
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
@@ -6721,6 +7023,8 @@ function installZoneDecals(kit) {
6721
7023
  const pos = g && g.attributes && g.attributes.position;
6722
7024
  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
7025
  }
7026
+ let _keep = new Uint32Array(0);
7027
+ let _box = new Float32Array(0);
6724
7028
  function proxyIn(o, Mw, R) {
6725
7029
  _pm.copy(o.matrixWorld).invert().multiply(Mw);
6726
7030
  const lo = [Infinity, Infinity, Infinity];
@@ -6733,39 +7037,48 @@ function installZoneDecals(kit) {
6733
7037
  }
6734
7038
  }
6735
7039
  const g = o.geometry;
6736
- const P = g.attributes.position.array;
7040
+ const ti = triIndexOf(g);
7041
+ if (!ti) return null;
6737
7042
  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);
7043
+ const boxes = ti.boxes;
7044
+ const blocks = ti.blocks;
7045
+ if (_keep.length < ti.triangles * 3) {
7046
+ _keep = new Uint32Array(ti.triangles * 3);
7047
+ _box = new Float32Array(ti.triangles * 6);
7048
+ }
7049
+ const keep = _keep;
7050
+ const box = _box;
6740
7051
  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;
7052
+ let nt = 0;
7053
+ for (let blk = 0, t0 = 0; t0 < ti.triangles; blk++, t0 += TRI_BLOCK) {
7054
+ const j = blk * 6;
7055
+ 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;
7056
+ const t1 = Math.min(ti.triangles, t0 + TRI_BLOCK);
7057
+ for (let t = t0; t < t1; t++) {
7058
+ const q = t * 6;
7059
+ 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;
7060
+ keep[nk++] = ix ? ix[t * 3] : t * 3;
7061
+ keep[nk++] = ix ? ix[t * 3 + 1] : t * 3 + 1;
7062
+ keep[nk++] = ix ? ix[t * 3 + 2] : t * 3 + 2;
7063
+ for (let k = 0; k < 6; k++) box[nt * 6 + k] = boxes[q + k];
7064
+ nt++;
6756
7065
  }
6757
7066
  }
6758
7067
  if (!nk) return null;
6759
7068
  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;
7069
+ const keptBox = box.slice(0, nt * 6);
7070
+ const keptBlocks = new Float32Array(Math.ceil(nt / TRI_BLOCK) * 6);
7071
+ for (let blk = 0; blk * TRI_BLOCK < nt; blk++) {
7072
+ const b = blk * 6;
6765
7073
  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]);
7074
+ keptBlocks[b + k] = Infinity;
7075
+ keptBlocks[b + 3 + k] = -Infinity;
6768
7076
  }
7077
+ for (let t = blk * TRI_BLOCK, t1 = Math.min(nt, t + TRI_BLOCK); t < t1; t++)
7078
+ for (let k = 0; k < 3; k++) {
7079
+ if (keptBox[t * 6 + k] < keptBlocks[b + k]) keptBlocks[b + k] = keptBox[t * 6 + k];
7080
+ if (keptBox[t * 6 + 3 + k] > keptBlocks[b + 3 + k]) keptBlocks[b + 3 + k] = keptBox[t * 6 + 3 + k];
7081
+ }
6769
7082
  }
6770
7083
  const pg = new THREE.BufferGeometry();
6771
7084
  pg.setAttribute("position", g.attributes.position);
@@ -6776,7 +7089,8 @@ function installZoneDecals(kit) {
6776
7089
  proxy.layers.mask = o.layers.mask;
6777
7090
  proxy.proxyOf = o;
6778
7091
  proxy.kept = kept;
6779
- proxy.keptBox = box;
7092
+ proxy.keptBox = keptBox;
7093
+ proxy.keptBlocks = keptBlocks;
6780
7094
  return proxy;
6781
7095
  }
6782
7096
  function narrowTo(rc, proxy) {
@@ -6796,13 +7110,21 @@ function installZoneDecals(kit) {
6796
7110
  const hi2 = Math.max(z0, _pv.z) + 1e-4;
6797
7111
  const K = proxy.kept;
6798
7112
  const X = proxy.keptBox;
7113
+ const KB = proxy.keptBlocks;
7114
+ const nt = K.length / 3;
6799
7115
  const dst = proxy.geometry.index.array;
6800
7116
  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];
7117
+ for (let blk = 0, t0 = 0; t0 < nt; blk++, t0 += TRI_BLOCK) {
7118
+ const b = blk * 6;
7119
+ 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;
7120
+ const t1 = Math.min(nt, t0 + TRI_BLOCK);
7121
+ for (let t = t0; t < t1; t++) {
7122
+ const j = t * 6;
7123
+ 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;
7124
+ dst[m++] = K[t * 3];
7125
+ dst[m++] = K[t * 3 + 1];
7126
+ dst[m++] = K[t * 3 + 2];
7127
+ }
6806
7128
  }
6807
7129
  proxy.geometry.setDrawRange(0, m);
6808
7130
  }
@@ -7220,6 +7542,8 @@ function installZoneDent(kit) {
7220
7542
  irel.copy(rel).invert();
7221
7543
  const pos = g.attributes.position;
7222
7544
  let changed = false;
7545
+ const liveIndex = liveTriIndex(g);
7546
+ const movedVertices = [];
7223
7547
  const near = boxInFrame(B, irel, v);
7224
7548
  for (let i = 0; i < pos.count; i++) {
7225
7549
  v.fromBufferAttribute(pos, i);
@@ -7231,6 +7555,7 @@ function installZoneDent(kit) {
7231
7555
  if (opt.extra) opt.extra(v, f);
7232
7556
  v.applyMatrix4(irel);
7233
7557
  pos.setXYZ(i, v.x, v.y, v.z);
7558
+ movedVertices.push(i);
7234
7559
  changed = true;
7235
7560
  }
7236
7561
  if (opt.extraAll) {
@@ -7239,12 +7564,14 @@ function installZoneDent(kit) {
7239
7564
  if (opt.extraAll(v)) {
7240
7565
  v.applyMatrix4(irel);
7241
7566
  pos.setXYZ(j, v.x, v.y, v.z);
7567
+ movedVertices.push(j);
7242
7568
  changed = true;
7243
7569
  }
7244
7570
  }
7245
7571
  }
7246
7572
  if (changed) {
7247
7573
  pos.needsUpdate = true;
7574
+ if (liveIndex) touchTriIndex(g, liveIndex, movedVertices);
7248
7575
  dirty.add(msh);
7249
7576
  }
7250
7577
  if (changed || boundsDue) {
@@ -7275,6 +7602,26 @@ function installZoneDent(kit) {
7275
7602
  });
7276
7603
  return { field, moved, ax, a, cu, cv, hu, hv, outer };
7277
7604
  }
7605
+ function panelBoundsIn(rel, rect, B, a, ax) {
7606
+ const det = rel.determinant();
7607
+ if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return void 0;
7608
+ const back = rel.clone().invert();
7609
+ const lo = [Infinity, Infinity, Infinity];
7610
+ const hi = [-Infinity, -Infinity, -Infinity];
7611
+ const q = new V3();
7612
+ for (let i = 0; i < 8; i++) {
7613
+ q.setComponent(ax[0], i & 1 ? rect[1] : rect[0]);
7614
+ q.setComponent(ax[1], i & 2 ? rect[3] : rect[2]);
7615
+ q.setComponent(a, i & 4 ? I.boxHi(B, a) : I.boxLo(B, a));
7616
+ q.applyMatrix4(back);
7617
+ for (let k = 0; k < 3; k++) {
7618
+ lo[k] = Math.min(lo[k], q.getComponent(k));
7619
+ hi[k] = Math.max(hi[k], q.getComponent(k));
7620
+ }
7621
+ }
7622
+ const pad = 1e-3;
7623
+ return { lo: lo.map((x) => x - pad), hi: hi.map((x) => x + pad) };
7624
+ }
7278
7625
  function cutPanel(c, z, rect, keep, dirty, o) {
7279
7626
  o = o || {};
7280
7627
  const B = z.box;
@@ -7307,6 +7654,7 @@ function installZoneDent(kit) {
7307
7654
  }
7308
7655
  const test = {
7309
7656
  tag: "part",
7657
+ pieces: keep ? o.pieceTasks ? "later" : "crease" : "none",
7310
7658
  sel: o.anyN || z.cutAll ? null : function(n) {
7311
7659
  nv.copy(n).applyMatrix3(nm).normalize();
7312
7660
  return nv.dot(outN) > 0.3;
@@ -7325,13 +7673,12 @@ function installZoneDent(kit) {
7325
7673
  return q;
7326
7674
  }
7327
7675
  };
7328
- const r = cutGeometry(g, [test]);
7676
+ const r = cutGeometry(g, [{ ...test, bounds: panelBoundsIn(rel, rect, B, a, ax) }]);
7329
7677
  const cg = r.cut.part;
7330
7678
  if (!cg || !cg.attributes.position.count) return;
7331
7679
  g.dispose();
7332
7680
  msh.geometry = r.geo;
7333
- r.geo.computeBoundingSphere();
7334
- r.geo.computeBoundingBox();
7681
+ I.refreshBounds(r.geo);
7335
7682
  dirty.add(msh);
7336
7683
  if (!keep) {
7337
7684
  cg.dispose();
@@ -7344,6 +7691,7 @@ function installZoneDent(kit) {
7344
7691
  rel.decompose(pm.position, pm.quaternion, pm.scale);
7345
7692
  c.vehicle.add(pm);
7346
7693
  pm.updateMatrixWorld(true);
7694
+ if (o.pieceTasks) o.pieceTasks.set(pm, r.tasks.part);
7347
7695
  pieces.push({ mesh: pm, cut: true });
7348
7696
  });
7349
7697
  return pieces;
@@ -10983,7 +11331,15 @@ var VehicleVisuals = class {
10983
11331
  __publicField(this, "gone", /* @__PURE__ */ new Set());
10984
11332
  /** Shows or hides the plate and the recess of a door or lid by its state (see `showWhenOpen`). */
10985
11333
  __publicField(this, "syncPlates", /* @__PURE__ */ new Map());
11334
+ /** 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). */
11335
+ __publicField(this, "skins", /* @__PURE__ */ new Map());
10986
11336
  __publicField(this, "lightsOn", true);
11337
+ /** Heavy body meshes whose triangle index is still to build (null once done). */
11338
+ __publicField(this, "warming");
11339
+ /** A panel was torn off since the last `pump`: that frame already paid for the cut, the smoothing starts on the next one. */
11340
+ __publicField(this, "tornThisFrame", false);
11341
+ /** Normal smoothing still running, by mesh: a mesh cut again gets a new task and the old one is dropped. */
11342
+ __publicField(this, "smoothing", /* @__PURE__ */ new Map());
10987
11343
  __publicField(this, "hood");
10988
11344
  this.kit = kit;
10989
11345
  this.c = c;
@@ -10993,6 +11349,8 @@ var VehicleVisuals = class {
10993
11349
  for (const p of model.parts) this.byId.set(p.id, p);
10994
11350
  c.root.updateMatrixWorld(true);
10995
11351
  this.hood = this.nameHood();
11352
+ this.warming = c.deform.filter((msh) => msh.geometry && msh.geometry.attributes.position && msh.geometry.attributes.position.count >= 1500);
11353
+ if (!this.warming.length) this.warming = null;
10996
11354
  for (const p of model.parts) {
10997
11355
  if (p.role === "door" || p.role === "lid") this.makePlate(p);
10998
11356
  else if (p.role === "mirror" && !this.mirrorObjects(p).length) this.makeMirror(p);
@@ -11128,11 +11486,21 @@ var VehicleVisuals = class {
11128
11486
  * 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
11487
  */
11130
11488
  showWhenOpen(id, piv, panel, recess) {
11489
+ let busy = false;
11131
11490
  const sync = () => {
11491
+ if (busy) return;
11132
11492
  const r = piv.rotation;
11133
11493
  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;
11494
+ if (open && !this.gone.has(id) && piv.parent === this.c.vehicle && !this.skins.has(id)) {
11495
+ busy = true;
11496
+ try {
11497
+ this.skins.set(id, this.takeSkin(id, piv));
11498
+ } finally {
11499
+ busy = false;
11500
+ }
11501
+ }
11502
+ panel.visible = open && !this.skins.get(id);
11503
+ recess.visible = open && !this.skins.get(id);
11136
11504
  };
11137
11505
  const euler = piv.rotation;
11138
11506
  const quaternion = piv.quaternion;
@@ -11149,6 +11517,58 @@ var VehicleVisuals = class {
11149
11517
  this.syncPlates.set(id, sync);
11150
11518
  sync();
11151
11519
  }
11520
+ /**
11521
+ * 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
11522
+ * window over it, on the hinge. Done with the hinge closed, so every piece keeps its place; answers whether there was skin to take.
11523
+ */
11524
+ takeSkin(id, piv) {
11525
+ const p = this.byId.get(id);
11526
+ const z = p && this.zone(p);
11527
+ if (!p || !z) return false;
11528
+ const I = this.I;
11529
+ const B = z.box;
11530
+ const q = piv.quaternion.clone();
11531
+ piv.quaternion.identity();
11532
+ this.c.root.updateMatrixWorld(true);
11533
+ const inv = I.worldInverseOf(this.c);
11534
+ let objs = [];
11535
+ try {
11536
+ if (p.role === "door") {
11537
+ const dirty = /* @__PURE__ */ new Set();
11538
+ 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];
11539
+ const pcs = I.cutPanel(this.c, z, rect, true, dirty).map((x) => x.mesh).filter((m) => !!m);
11540
+ this.mend(dirty);
11541
+ if (pcs.length) objs = [...pcs, ...I.followersIn(this.c, z, inv), ...I.windowsOver(this.c, z, inv)];
11542
+ } else {
11543
+ const pcs = this.cutZone(z);
11544
+ if (pcs.length) objs = [...pcs, ...I.followersIn(this.c, z, inv)];
11545
+ }
11546
+ } catch {
11547
+ objs = [];
11548
+ }
11549
+ const deform = this.c.deform ?? [];
11550
+ for (const o of objs) {
11551
+ if (!o.parent || o === piv) continue;
11552
+ piv.attach(o);
11553
+ o.traverse((x) => {
11554
+ x.userData.noFollow = true;
11555
+ });
11556
+ const di = deform.indexOf(o);
11557
+ if (di >= 0) deform.splice(di, 1);
11558
+ }
11559
+ piv.quaternion.copy(q);
11560
+ this.c.root.updateMatrixWorld(true);
11561
+ return objs.length > 0;
11562
+ }
11563
+ mend(dirty) {
11564
+ dirty.forEach((msh) => {
11565
+ const g = msh.geometry;
11566
+ if (g.userData.crease || !g.index) this.I.crease(g, 42);
11567
+ else g.computeVertexNormals();
11568
+ g.computeBoundingSphere();
11569
+ g.computeBoundingBox();
11570
+ });
11571
+ }
11152
11572
  makeMirror(p) {
11153
11573
  const [cx, cy, cz] = p.box.center;
11154
11574
  const s = cz < 0 ? -1 : 1;
@@ -11286,6 +11706,31 @@ var VehicleVisuals = class {
11286
11706
  this.I.flattenTire(w, Math.min(0.5, 0.12 + 0.38 * melt));
11287
11707
  }
11288
11708
  }
11709
+ /** Smooths the normals of a cut mesh a slice per frame (see `pump`); until then it draws with flat face normals. */
11710
+ smoothLater(msh) {
11711
+ this.smoothing.set(msh, this.I.creaseTask(msh.geometry, 42));
11712
+ this.tornThisFrame = true;
11713
+ }
11714
+ /** Spends at most `budgetMs` finishing the pending normal smoothing. Call once per frame. */
11715
+ pump(budgetMs = 2.5) {
11716
+ if (this.tornThisFrame) {
11717
+ this.tornThisFrame = false;
11718
+ return;
11719
+ }
11720
+ if (!this.smoothing.size && !this.warming) return;
11721
+ const deadline = performance.now() + budgetMs;
11722
+ for (const [msh, task] of this.smoothing) {
11723
+ if (msh.geometry !== task.geo || task.step(deadline)) this.smoothing.delete(msh);
11724
+ if (performance.now() >= deadline) return;
11725
+ }
11726
+ this.warmIndexes(deadline);
11727
+ }
11728
+ /** Measures the triangles of the heavy body meshes once, a slice per frame, so the first panel torn off finds them ready (see `triIndexOf`). */
11729
+ warmIndexes(deadline) {
11730
+ if (!this.warming) return;
11731
+ this.warming = this.warming.filter((msh) => !warmTriIndex(msh.geometry, deadline));
11732
+ if (!this.warming.length) this.warming = null;
11733
+ }
11289
11734
  /** Moves the panel of a lost zone out of the body (the kit's torn-off look). */
11290
11735
  tearOff(p) {
11291
11736
  const z = this.zone(p);
@@ -11298,10 +11743,9 @@ var VehicleVisuals = class {
11298
11743
  }
11299
11744
  dirty.forEach((msh) => {
11300
11745
  const g = msh.geometry;
11301
- if (g.userData.crease || !g.index) this.I.crease(g, 42);
11746
+ if (g.userData.crease || !g.index) this.smoothLater(msh);
11302
11747
  else g.computeVertexNormals();
11303
- g.computeBoundingSphere();
11304
- g.computeBoundingBox();
11748
+ this.I.refreshBounds(g);
11305
11749
  });
11306
11750
  this.gone.add(p.id);
11307
11751
  }
@@ -11444,17 +11888,19 @@ var VehicleVisuals = class {
11444
11888
  const mg = Math.min(0.05, (u1 - u0) * 0.08, (v1 - v0) * 0.08);
11445
11889
  const dirty = /* @__PURE__ */ new Set();
11446
11890
  let pcs = [];
11891
+ const pieceTasks = /* @__PURE__ */ new Map();
11447
11892
  try {
11448
- pcs = I.cutPanel(this.c, z, [u0 + mg, u1 - mg, v0 + mg, v1 - mg], true, dirty);
11893
+ pcs = I.cutPanel(this.c, z, [u0 + mg, u1 - mg, v0 + mg, v1 - mg], true, dirty, { pieceTasks });
11449
11894
  } catch {
11450
11895
  pcs = [];
11451
11896
  }
11897
+ pieceTasks.forEach((task, mesh) => this.smoothing.set(mesh, task));
11898
+ this.tornThisFrame = true;
11452
11899
  dirty.forEach((msh) => {
11453
11900
  const g = msh.geometry;
11454
- if (g.userData.crease || !g.index) I.crease(g, 42);
11901
+ if (g.userData.crease || !g.index) this.smoothLater(msh);
11455
11902
  else g.computeVertexNormals();
11456
- g.computeBoundingSphere();
11457
- g.computeBoundingBox();
11903
+ I.refreshBounds(g);
11458
11904
  });
11459
11905
  return pcs.map((q) => q.mesh).filter((m) => !!m);
11460
11906
  }
@@ -12233,7 +12679,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12233
12679
  specCache = { name: options.name ?? c.spec.name ?? c.key, parts, joints, tags: ["vehicle", kindOf(c)] };
12234
12680
  return specCache;
12235
12681
  };
12236
- const state = () => {
12682
+ const buildState = () => {
12237
12683
  const parts = model.parts.map((d) => {
12238
12684
  const p = ps.get(d.id);
12239
12685
  const marked = p.dents.length > 0 || p.holes.length > 0 || p.cracks.length > 0;
@@ -12270,7 +12716,15 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12270
12716
  }));
12271
12717
  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
12718
  };
12719
+ let stateCache;
12720
+ const state = () => stateCache ?? (stateCache = buildState());
12721
+ const isSimulating = () => {
12722
+ if (leaks.size > 0) return true;
12723
+ for (const p of ps.values()) if (!p.lost && (p.burning || p.temp > AMBIENT)) return true;
12724
+ return false;
12725
+ };
12273
12726
  const hit = (h) => {
12727
+ stateCache = void 0;
12274
12728
  const out = [];
12275
12729
  const hh = h.space === "local" ? { ...h, space: "world", point: toWorld(h.point) } : h;
12276
12730
  if (hh.space === "local" || !hh.point) return out;
@@ -12297,6 +12751,8 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12297
12751
  clock += dt;
12298
12752
  const out = queue;
12299
12753
  queue = [];
12754
+ const wasSimulating = isSimulating();
12755
+ const handledBefore = (blastHandled ? 1 : 0) + (rollHandled ? 2 : 0);
12300
12756
  const hood = c.hood;
12301
12757
  if (hood) {
12302
12758
  const sign = hood.sign || 1;
@@ -12317,12 +12773,15 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12317
12773
  afterBlast(out);
12318
12774
  afterRoll(out);
12319
12775
  applyEffects();
12776
+ vis?.pump();
12777
+ if (wasSimulating || out.length > 0 || handledBefore !== (blastHandled ? 1 : 0) + (rollHandled ? 2 : 0)) stateCache = void 0;
12320
12778
  return out;
12321
12779
  };
12322
12780
  const disassemble = (jointId, tool, dt) => {
12323
12781
  const j = js.find((x) => x.def.id === jointId);
12324
12782
  if (!j || !j.def.tools.includes(tool)) return -1;
12325
12783
  if (j.open) return 1;
12784
+ stateCache = void 0;
12326
12785
  j.progress = Math.min(1, j.progress + dt / j.def.seconds);
12327
12786
  const step = Math.floor(j.progress * 4) / 4;
12328
12787
  if (step > j.reported && j.progress < 1) {
@@ -12338,6 +12797,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12338
12797
  return j.progress;
12339
12798
  };
12340
12799
  const extinguish = (partId) => {
12800
+ stateCache = void 0;
12341
12801
  for (const p of ps.values()) {
12342
12802
  if (partId && p.def.id !== partId) continue;
12343
12803
  if (p.burning) {
@@ -12351,6 +12811,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12351
12811
  }
12352
12812
  };
12353
12813
  const reset = () => {
12814
+ stateCache = void 0;
12354
12815
  const old = c;
12355
12816
  const parent = old.root.parent;
12356
12817
  const next = kit.build(old.key, old.state, old.scenario != null ? { scenario: old.scenario } : void 0);
@@ -12368,6 +12829,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12368
12829
  setup();
12369
12830
  };
12370
12831
  const rollover = (side2 = 1) => {
12832
+ stateCache = void 0;
12371
12833
  if (kit.roll(c, side2)) rolled = true;
12372
12834
  void rolled;
12373
12835
  };