@adatechnology/game-vehicles 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,6 +2,9 @@ var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
 
5
+ // src/kit.ts
6
+ import { shapeGeometries } from "@adatechnology/game-shapes-three";
7
+
5
8
  // src/locales/en.json
6
9
  var en_default = {
7
10
  plate: {
@@ -935,46 +938,79 @@ function installGeometry(kit) {
935
938
  }
936
939
  const p = geo.attributes.position;
937
940
  const n = p.count;
938
- const FN = [];
939
- const map = {};
940
- const A = new V3();
941
- const B = new V3();
942
- const C = new V3();
943
- const e1 = new V3();
944
- const e2 = new V3();
941
+ const faces = n / 3;
942
+ const FN = new Float64Array(faces * 3);
943
+ const map = /* @__PURE__ */ new Map();
944
+ const RANGE = 65536;
945
945
  function key(i) {
946
- return Math.round(p.getX(i) * 2e3) + "," + Math.round(p.getY(i) * 2e3) + "," + Math.round(p.getZ(i) * 2e3);
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;
947
951
  }
948
952
  const keys = new Array(n);
949
- for (let f = 0; f < n / 3; f++) {
950
- A.fromBufferAttribute(p, 3 * f);
951
- B.fromBufferAttribute(p, 3 * f + 1);
952
- C.fromBufferAttribute(p, 3 * f + 2);
953
- const nn = new V3().crossVectors(e1.subVectors(C, B), e2.subVectors(A, B));
954
- if (nn.lengthSq() > 1e-20) nn.normalize();
955
- FN.push(nn);
953
+ 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);
963
+ const e1x = cx - bx;
964
+ const e1y = cy - by;
965
+ const e1z = cz - bz;
966
+ const e2x = ax - bx;
967
+ const e2y = ay - by;
968
+ const e2z = az - bz;
969
+ let nx = e1y * e2z - e1z * e2y;
970
+ let ny = e1z * e2x - e1x * e2z;
971
+ let nz = e1x * e2y - e1y * e2x;
972
+ if (nx * nx + ny * ny + nz * nz > 1e-20) {
973
+ const inv = 1 / (Math.sqrt(nx * nx + ny * ny + nz * nz) || 1);
974
+ nx *= inv;
975
+ ny *= inv;
976
+ nz *= inv;
977
+ }
978
+ FN[3 * f] = nx;
979
+ FN[3 * f + 1] = ny;
980
+ FN[3 * f + 2] = nz;
956
981
  for (let k = 0; k < 3; k++) {
957
982
  const kk = key(3 * f + k);
958
983
  keys[3 * f + k] = kk;
959
- (map[kk] || (map[kk] = [])).push(f);
984
+ const list = map.get(kk);
985
+ if (list) list.push(f);
986
+ else map.set(kk, [f]);
960
987
  }
961
988
  }
962
989
  const cs = Math.cos(deg * PI / 180);
963
990
  const out = new Float32Array(n * 3);
964
- const s = new V3();
965
991
  for (let i = 0; i < n; i++) {
966
- const fi = i / 3 | 0;
967
- const list = map[keys[i]];
968
- s.set(0, 0, 0);
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;
969
997
  for (let j = 0; j < list.length; j++) {
970
- const g = FN[list[j]];
971
- if (g.dot(FN[fi]) >= cs) s.add(g);
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];
1003
+ }
1004
+ }
1005
+ if (sx * sx + sy * sy + sz * sz < 1e-20) {
1006
+ sx = FN[fi];
1007
+ sy = FN[fi + 1];
1008
+ sz = FN[fi + 2];
972
1009
  }
973
- if (s.lengthSq() < 1e-20) s.copy(FN[fi]);
974
- s.normalize();
975
- out[3 * i] = s.x;
976
- out[3 * i + 1] = s.y;
977
- out[3 * i + 2] = s.z;
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;
978
1014
  }
979
1015
  geo.setAttribute("normal", new THREE.BufferAttribute(out, 3));
980
1016
  geo.userData.crease = true;
@@ -6632,6 +6668,153 @@ function installZoneDecals(kit) {
6632
6668
  return hole;
6633
6669
  }
6634
6670
  const _zr = new THREE.Raycaster();
6671
+ const PROXY_MIN_TRIANGLES = 500;
6672
+ const STROKE_REACH = 0.6;
6673
+ const _pv = new V3();
6674
+ const _pm = new THREE.Matrix4();
6675
+ function decalRegion(c, z) {
6676
+ const B = z.box;
6677
+ const lo = [0, 1, 2].map(function(k) {
6678
+ return I.boxLo(B, k) - STROKE_REACH;
6679
+ });
6680
+ const hi = [0, 1, 2].map(function(k) {
6681
+ return I.boxHi(B, k) + STROKE_REACH;
6682
+ });
6683
+ const a = axisOf(z.dir);
6684
+ const dn = z.dir.clone().normalize();
6685
+ const sg = z.dir.getComponent(a) > 0 ? 1 : -1;
6686
+ const outer = sg > 0 ? I.boxLo(B, a) - 0.6 : I.boxHi(B, a) + 0.6;
6687
+ const far = 0.6 + (I.boxHi(B, a) - I.boxLo(B, a)) + 0.05;
6688
+ for (let i = 0; i < 8; i++) {
6689
+ const p = new V3();
6690
+ p.setComponent(a, outer);
6691
+ [0, 1, 2].filter(function(k) {
6692
+ return k !== a;
6693
+ }).forEach(function(k, j) {
6694
+ p.setComponent(k, i >> j & 1 ? I.boxHi(B, k) : I.boxLo(B, k));
6695
+ });
6696
+ for (let e = 0; e < 2; e++) {
6697
+ const q = e ? p.clone().addScaledVector(dn, far) : p;
6698
+ for (let k = 0; k < 3; k++) {
6699
+ lo[k] = Math.min(lo[k], q.getComponent(k) - 0.05);
6700
+ hi[k] = Math.max(hi[k], q.getComponent(k) + 0.05);
6701
+ }
6702
+ }
6703
+ }
6704
+ return { lo, hi };
6705
+ }
6706
+ function regionCands(c, z, cands) {
6707
+ if (!cands.some(heavy)) return cands;
6708
+ const R = decalRegion(c, z);
6709
+ const Mw = c.vehicle.matrixWorld;
6710
+ return cands.map(function(o) {
6711
+ if (!heavy(o)) return o;
6712
+ if (!o.geometry.boundingSphere) o.geometry.computeBoundingSphere();
6713
+ if (!o.geometry.boundingBox) o.geometry.computeBoundingBox();
6714
+ return proxyIn(o, Mw, R);
6715
+ });
6716
+ }
6717
+ function heavy(o) {
6718
+ const g = o.geometry;
6719
+ const pos = g && g.attributes && g.attributes.position;
6720
+ 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;
6721
+ }
6722
+ function proxyIn(o, Mw, R) {
6723
+ _pm.copy(o.matrixWorld).invert().multiply(Mw);
6724
+ const lo = [Infinity, Infinity, Infinity];
6725
+ const hi = [-Infinity, -Infinity, -Infinity];
6726
+ for (let i = 0; i < 8; i++) {
6727
+ _pv.set(i & 1 ? R.hi[0] : R.lo[0], i & 2 ? R.hi[1] : R.lo[1], i & 4 ? R.hi[2] : R.lo[2]).applyMatrix4(_pm);
6728
+ for (let k = 0; k < 3; k++) {
6729
+ lo[k] = Math.min(lo[k], _pv.getComponent(k));
6730
+ hi[k] = Math.max(hi[k], _pv.getComponent(k));
6731
+ }
6732
+ }
6733
+ const g = o.geometry;
6734
+ const P = g.attributes.position.array;
6735
+ const ix = g.index ? g.index.array : null;
6736
+ const n = ix ? ix.length : P.length / 3;
6737
+ const keep = new Uint32Array(n - n % 3);
6738
+ let nk = 0;
6739
+ for (let t = 0; t + 2 < n; t += 3) {
6740
+ const a = (ix ? ix[t] : t) * 3;
6741
+ const b = (ix ? ix[t + 1] : t + 1) * 3;
6742
+ const d = (ix ? ix[t + 2] : t + 2) * 3;
6743
+ let out = false;
6744
+ for (let k = 0; k < 3 && !out; k++) {
6745
+ const u = P[a + k];
6746
+ const v = P[b + k];
6747
+ const w = P[d + k];
6748
+ out = Math.max(u, v, w) < lo[k] || Math.min(u, v, w) > hi[k];
6749
+ }
6750
+ if (!out) {
6751
+ keep[nk++] = ix ? ix[t] : t;
6752
+ keep[nk++] = ix ? ix[t + 1] : t + 1;
6753
+ keep[nk++] = ix ? ix[t + 2] : t + 2;
6754
+ }
6755
+ }
6756
+ if (!nk) return null;
6757
+ const kept = keep.slice(0, nk);
6758
+ const box = new Float32Array(kept.length / 3 * 6);
6759
+ for (let t = 0, j = 0; t < kept.length; t += 3, j += 6) {
6760
+ const a = kept[t] * 3;
6761
+ const b = kept[t + 1] * 3;
6762
+ const d = kept[t + 2] * 3;
6763
+ for (let k = 0; k < 3; k++) {
6764
+ box[j + k] = Math.min(P[a + k], P[b + k], P[d + k]);
6765
+ box[j + 3 + k] = Math.max(P[a + k], P[b + k], P[d + k]);
6766
+ }
6767
+ }
6768
+ const pg = new THREE.BufferGeometry();
6769
+ pg.setAttribute("position", g.attributes.position);
6770
+ pg.setIndex(new THREE.BufferAttribute(new Uint32Array(kept.length), 1));
6771
+ pg.boundingSphere = g.boundingSphere;
6772
+ pg.boundingBox = g.boundingBox;
6773
+ const proxy = new THREE.Mesh(pg, o.material);
6774
+ proxy.layers.mask = o.layers.mask;
6775
+ proxy.proxyOf = o;
6776
+ proxy.kept = kept;
6777
+ proxy.keptBox = box;
6778
+ return proxy;
6779
+ }
6780
+ function narrowTo(rc, proxy) {
6781
+ const o = proxy.proxyOf;
6782
+ const ray = rc.ray;
6783
+ _pm.copy(o.matrixWorld).invert();
6784
+ _pv.copy(ray.origin).applyMatrix4(_pm);
6785
+ const x0 = _pv.x;
6786
+ const y0 = _pv.y;
6787
+ const z0 = _pv.z;
6788
+ _pv.copy(ray.direction).multiplyScalar(rc.far).add(ray.origin).applyMatrix4(_pm);
6789
+ const lo0 = Math.min(x0, _pv.x) - 1e-4;
6790
+ const lo1 = Math.min(y0, _pv.y) - 1e-4;
6791
+ const lo2 = Math.min(z0, _pv.z) - 1e-4;
6792
+ const hi0 = Math.max(x0, _pv.x) + 1e-4;
6793
+ const hi1 = Math.max(y0, _pv.y) + 1e-4;
6794
+ const hi2 = Math.max(z0, _pv.z) + 1e-4;
6795
+ const K = proxy.kept;
6796
+ const X = proxy.keptBox;
6797
+ const dst = proxy.geometry.index.array;
6798
+ let m = 0;
6799
+ for (let t = 0, j = 0; t < K.length; t += 3, j += 6) {
6800
+ 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;
6801
+ dst[m++] = K[t];
6802
+ dst[m++] = K[t + 1];
6803
+ dst[m++] = K[t + 2];
6804
+ }
6805
+ proxy.geometry.setDrawRange(0, m);
6806
+ }
6807
+ function castAt(rc, cands) {
6808
+ for (let i = 0; i < cands.length; i++) {
6809
+ const q = cands[i];
6810
+ if (!q.proxyOf) continue;
6811
+ q.matrixWorld.copy(q.proxyOf.matrixWorld);
6812
+ narrowTo(rc, q);
6813
+ }
6814
+ const hits = rc.intersectObjects(cands, false);
6815
+ for (let i = 0; i < hits.length; i++) if (hits[i].object.proxyOf) hits[i].object = hits[i].object.proxyOf;
6816
+ return hits;
6817
+ }
6635
6818
  function zoneCands(c, z, inv, glassToo) {
6636
6819
  const out = [];
6637
6820
  c.vehicle.traverse(function(o) {
@@ -6684,7 +6867,7 @@ function installZoneDecals(kit) {
6684
6867
  });
6685
6868
  _zr.set(org.clone().applyMatrix4(Mw), d.clone().transformDirection(Mw));
6686
6869
  _zr.far = 0.6 + (I.boxHi(B, a) - I.boxLo(B, a)) + 0.05;
6687
- const hits = _zr.intersectObjects(cands, false);
6870
+ const hits = castAt(_zr, cands);
6688
6871
  for (let i = 0; i < hits.length; i++) {
6689
6872
  const h = hits[i];
6690
6873
  if (!h.face) continue;
@@ -6712,7 +6895,7 @@ function installZoneDecals(kit) {
6712
6895
  const q = s.p.clone().addScaledVector(t, len * 0.5 * k / M);
6713
6896
  _rt.set(q.clone().addScaledVector(n, 0.08).applyMatrix4(Mw), n.clone().negate().transformDirection(Mw));
6714
6897
  _rt.far = 0.2;
6715
- const h = _rt.intersectObjects(cands, false).find(function(x) {
6898
+ const h = castAt(_rt, cands).find(function(x) {
6716
6899
  return x.face;
6717
6900
  });
6718
6901
  if (!h) return null;
@@ -6783,6 +6966,7 @@ function installZoneDecals(kit) {
6783
6966
  const R = c.rand;
6784
6967
  const need = (spec.fine || 0) + (spec.deep || 0) + (spec.chip || 0) + (spec.rust || 0) + (spec.tear || 0) + (spec.ding || 0) + (spec.soot || 0) + (spec.crater || 0);
6785
6968
  if (!need) return [];
6969
+ cands = regionCands(c, z, cands).filter(Boolean);
6786
6970
  const S = zoneSurf(c, z, need + 2, R, cands);
6787
6971
  let k = 0;
6788
6972
  const ang0 = R() * PI;
@@ -6863,8 +7047,25 @@ function installZoneDent(kit) {
6863
7047
  }
6864
7048
  function subdivIn(geo, rel, B, maxLen) {
6865
7049
  const a = geo.attributes.position.array;
6866
- const out = [];
6867
- const v = new V3();
7050
+ let out = new Float32Array(a.length + 4096);
7051
+ let used = 0;
7052
+ const push = function(a0, a1, a2, b0, b1, b2, c0, c1, c2) {
7053
+ if (used + 9 > out.length) {
7054
+ const bigger = new Float32Array(out.length * 2);
7055
+ bigger.set(out);
7056
+ out = bigger;
7057
+ }
7058
+ out[used] = a0;
7059
+ out[used + 1] = a1;
7060
+ out[used + 2] = a2;
7061
+ out[used + 3] = b0;
7062
+ out[used + 4] = b1;
7063
+ out[used + 5] = b2;
7064
+ out[used + 6] = c0;
7065
+ out[used + 7] = c1;
7066
+ out[used + 8] = c2;
7067
+ used += 9;
7068
+ };
6868
7069
  const e = 0.15;
6869
7070
  function d(p, q) {
6870
7071
  return Math.hypot(p[0] - q[0], p[1] - q[1], p[2] - q[2]);
@@ -6878,7 +7079,7 @@ function installZoneDent(kit) {
6878
7079
  const ca = d(C, A);
6879
7080
  const mx = Math.max(ab, bc, ca);
6880
7081
  if (mx <= maxLen || depth > 9) {
6881
- out.push(A[0], A[1], A[2], Bv[0], Bv[1], Bv[2], C[0], C[1], C[2]);
7082
+ push(A[0], A[1], A[2], Bv[0], Bv[1], Bv[2], C[0], C[1], C[2]);
6882
7083
  return;
6883
7084
  }
6884
7085
  if (mx === ab) {
@@ -6895,31 +7096,26 @@ function installZoneDent(kit) {
6895
7096
  tri(F2, Bv, C, depth + 1);
6896
7097
  }
6897
7098
  }
7099
+ const M = rel.elements;
7100
+ const at = function(x, y, z, k) {
7101
+ const w = 1 / (M[3] * x + M[7] * y + M[11] * z + M[15]);
7102
+ return k === 0 ? (M[0] * x + M[4] * y + M[8] * z + M[12]) * w : k === 1 ? (M[1] * x + M[5] * y + M[9] * z + M[13]) * w : (M[2] * x + M[6] * y + M[10] * z + M[14]) * w;
7103
+ };
7104
+ const lo = [B.x0 - e, B.y0 - e, B.z0 - e];
7105
+ const hi = [B.x1 + e, B.y1 + e, B.z1 + e];
6898
7106
  for (let i = 0; i < a.length; i += 9) {
6899
- const A = [a[i], a[i + 1], a[i + 2]];
6900
- const Bv = [a[i + 3], a[i + 4], a[i + 5]];
6901
- const C = [a[i + 6], a[i + 7], a[i + 8]];
6902
- let x0 = 1e9;
6903
- let x1 = -1e9;
6904
- let y0 = 1e9;
6905
- let y1 = -1e9;
6906
- let z0 = 1e9;
6907
- let z1 = -1e9;
6908
- [A, Bv, C].forEach(function(p) {
6909
- v.set(p[0], p[1], p[2]).applyMatrix4(rel);
6910
- x0 = Math.min(x0, v.x);
6911
- x1 = Math.max(x1, v.x);
6912
- y0 = Math.min(y0, v.y);
6913
- y1 = Math.max(y1, v.y);
6914
- z0 = Math.min(z0, v.z);
6915
- z1 = Math.max(z1, v.z);
6916
- });
6917
- if (x1 < B.x0 - e || x0 > B.x1 + e || y1 < B.y0 - e || y0 > B.y1 + e || z1 < B.z0 - e || z0 > B.z1 + e)
6918
- out.push(A[0], A[1], A[2], Bv[0], Bv[1], Bv[2], C[0], C[1], C[2]);
6919
- else tri(A, Bv, C, 0);
7107
+ let outside = false;
7108
+ for (let k = 0; k < 3 && !outside; k++) {
7109
+ const p = at(a[i], a[i + 1], a[i + 2], k);
7110
+ const q = at(a[i + 3], a[i + 4], a[i + 5], k);
7111
+ const r = at(a[i + 6], a[i + 7], a[i + 8], k);
7112
+ outside = Math.max(p, q, r) < lo[k] || Math.min(p, q, r) > hi[k];
7113
+ }
7114
+ if (outside) push(a[i], a[i + 1], a[i + 2], a[i + 3], a[i + 4], a[i + 5], a[i + 6], a[i + 7], a[i + 8]);
7115
+ else tri([a[i], a[i + 1], a[i + 2]], [a[i + 3], a[i + 4], a[i + 5]], [a[i + 6], a[i + 7], a[i + 8]], 0);
6920
7116
  }
6921
7117
  const g = new THREE.BufferGeometry();
6922
- g.setAttribute("position", new THREE.Float32BufferAttribute(out, 3));
7118
+ g.setAttribute("position", new THREE.BufferAttribute(out.slice(0, used), 3));
6923
7119
  g.userData.crease = true;
6924
7120
  g.userData.p0 = null;
6925
7121
  return g;
@@ -6942,6 +7138,17 @@ function installZoneDent(kit) {
6942
7138
  });
6943
7139
  return out;
6944
7140
  }
7141
+ function boxInFrame(B, m, tmp) {
7142
+ const r = [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];
7143
+ for (let i = 0; i < 8; i++) {
7144
+ tmp.set(i & 1 ? B.x1 : B.x0, i & 2 ? B.y1 : B.y0, i & 4 ? B.z1 : B.z0).applyMatrix4(m);
7145
+ for (let k = 0; k < 3; k++) {
7146
+ r[k] = Math.min(r[k], tmp.getComponent(k) - 1e-4);
7147
+ r[k + 3] = Math.max(r[k + 3], tmp.getComponent(k) + 1e-4);
7148
+ }
7149
+ }
7150
+ return r;
7151
+ }
6945
7152
  function zoneDent(c, z, dents, dirty, opt) {
6946
7153
  opt = opt || {};
6947
7154
  const B = z.box;
@@ -6996,6 +7203,7 @@ function installZoneDent(kit) {
6996
7203
  g = msh.geometry = g.clone();
6997
7204
  msh.userData.zCloned = true;
6998
7205
  }
7206
+ let boundsDue = false;
6999
7207
  const nonIdx = !g.index;
7000
7208
  const onlyPos = Object.keys(g.attributes).every(function(k) {
7001
7209
  return k === "position" || k === "normal";
@@ -7004,15 +7212,17 @@ function installZoneDent(kit) {
7004
7212
  const ng = subdivIn(g, rel, B, opt.sub || 0.075);
7005
7213
  g.dispose();
7006
7214
  g = msh.geometry = ng;
7007
- ng.computeBoundingSphere();
7008
- ng.computeBoundingBox();
7215
+ boundsDue = true;
7009
7216
  dirty.add(msh);
7010
7217
  }
7011
7218
  irel.copy(rel).invert();
7012
7219
  const pos = g.attributes.position;
7013
7220
  let changed = false;
7221
+ const near = boxInFrame(B, irel, v);
7014
7222
  for (let i = 0; i < pos.count; i++) {
7015
- v.fromBufferAttribute(pos, i).applyMatrix4(rel);
7223
+ v.fromBufferAttribute(pos, i);
7224
+ if (v.x < near[0] || v.x > near[3] || v.y < near[1] || v.y > near[4] || v.z < near[2] || v.z > near[5]) continue;
7225
+ v.applyMatrix4(rel);
7016
7226
  const f = field(v);
7017
7227
  if (f <= 1e-5) continue;
7018
7228
  v.addScaledVector(d, f);
@@ -7033,9 +7243,11 @@ function installZoneDent(kit) {
7033
7243
  }
7034
7244
  if (changed) {
7035
7245
  pos.needsUpdate = true;
7246
+ dirty.add(msh);
7247
+ }
7248
+ if (changed || boundsDue) {
7036
7249
  g.computeBoundingSphere();
7037
7250
  g.computeBoundingBox();
7038
- dirty.add(msh);
7039
7251
  }
7040
7252
  });
7041
7253
  const dset = new Set(c.deform);
@@ -9915,7 +10127,7 @@ function createVehicleKit(options) {
9915
10127
  const opts = options || {};
9916
10128
  if (!opts.THREE || !opts.THREE.Vector3) throw new Error("createVehicleKit: options.THREE (the three.js namespace) is required");
9917
10129
  const lod = createLod(opts.lod);
9918
- const THREE = wrapThreeForLod(opts.THREE, lod);
10130
+ const THREE = wrapThreeForLod(shapeGeometries(opts.THREE), lod);
9919
10131
  const strings = resolveStrings(opts.locale);
9920
10132
  const t = createTranslate(strings);
9921
10133
  const doc = opts.document || (typeof document !== "undefined" ? document : void 0);
@@ -10138,6 +10350,20 @@ function createVehicleKit(options) {
10138
10350
  else kit.applyArmor(c);
10139
10351
  }
10140
10352
  if (zoneState == null && st === "damaged" && kit.config.damagedScenario) zoneState = kit.zoneScenario(c, kit.config.damagedScenario);
10353
+ else if (zoneState && zoneState.scenario != null) {
10354
+ c.scenario = zoneState.scenario;
10355
+ let fromScenario = { health: {}, flags: {} };
10356
+ try {
10357
+ fromScenario = kit.zoneScenario(c, zoneState.scenario);
10358
+ } catch (e) {
10359
+ ;
10360
+ (c.zoneErrors = c.zoneErrors || []).push("scenario: " + (e && e.message));
10361
+ }
10362
+ zoneState = {
10363
+ health: Object.assign({}, fromScenario.health, zoneState.health),
10364
+ flags: Object.assign({}, fromScenario.flags, zoneState.flags)
10365
+ };
10366
+ }
10141
10367
  if (zoneState) kit.applyZones(c, zoneState);
10142
10368
  else {
10143
10369
  c.zoneState = { health: {}, flags: {} };
@@ -10176,6 +10402,9 @@ function createVehicleKit(options) {
10176
10402
  return kit;
10177
10403
  }
10178
10404
 
10405
+ // src/destructible/impl.ts
10406
+ import { ignitionChance, resolveIgnitionRules, sparkTargetFactor } from "@adatechnology/game-contracts";
10407
+
10179
10408
  // src/destructible/model.ts
10180
10409
  var JOINT_RULES = {
10181
10410
  hinge: { strength: 5e3, tools: ["wrench", "screwdriver"], seconds: 3 },
@@ -11288,6 +11517,9 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
11288
11517
  const headless = !!options.headless;
11289
11518
  const seedBase = strHash(`${c.key}|${id}|${options.seed ?? 0}`);
11290
11519
  let rand = rng(seedBase);
11520
+ const ignitionRules = resolveIgnitionRules(options.ignition);
11521
+ const fuelFraction = clamp(options.fuel ?? 1, 0, 1);
11522
+ let igniteRand = rng(strHash(`${c.key}|${id}|ignition|${options.seed ?? 0}`));
11291
11523
  let clock = 0;
11292
11524
  let model;
11293
11525
  let vis = null;
@@ -11355,7 +11587,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
11355
11587
  dents: [],
11356
11588
  holes: [],
11357
11589
  cracks: [],
11358
- liters: d.contains?.liters,
11590
+ liters: d.contains ? d.role === "fuel_tank" ? d.contains.liters * fuelFraction : d.contains.liters : void 0,
11359
11591
  lost: false,
11360
11592
  status: "intact",
11361
11593
  flat: false,
@@ -11373,6 +11605,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
11373
11605
  wasLit = true;
11374
11606
  deadEngine = false;
11375
11607
  rand = rng(seedBase);
11608
+ igniteRand = rng(strHash(`${c.key}|${id}|ignition|${options.seed ?? 0}`));
11376
11609
  steerSign = rand() < 0.5 ? -1 : 1;
11377
11610
  vis = headless ? null : new VehicleVisuals(kit, c, model);
11378
11611
  recomputeGroups();
@@ -11667,8 +11900,19 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
11667
11900
  if (!p.burning && p.temp > AMBIENT) p.temp = Math.max(AMBIENT, p.temp - dt * 6);
11668
11901
  }
11669
11902
  for (const f of flames) heat(centreOf(f.def), 0.8, 500, dt * 0.5, out, 0);
11903
+ fireNearLeak(flames, dt, out);
11670
11904
  thermal(out);
11671
11905
  };
11906
+ const fireNearLeak = (flames, dt, out) => {
11907
+ const tank = tankPart();
11908
+ const leak = tank ? leaks.get(tank.def.id) : void 0;
11909
+ if (!tank || !leak || tank.lost || tank.burning || exploded || leak.fluid !== "fuel" && leak.fluid !== "diesel") return;
11910
+ if (leak.pud?.fire) return light(tank, out);
11911
+ const { radius, chancePerSecond } = ignitionRules.fireNear;
11912
+ if (chancePerSecond <= 0) return;
11913
+ const near = flames.some((f) => f !== tank && distToBox(tank.def, centreOf(f.def)) - Math.max(...f.def.box.size) / 2 <= radius);
11914
+ if (near && igniteRand() < 1 - Math.exp(-chancePerSecond * dt)) light(tank, out);
11915
+ };
11672
11916
  const normalOf = (d, v) => faceNormal(d, v);
11673
11917
  const ballistic = (h, out) => {
11674
11918
  sync();
@@ -11736,10 +11980,21 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
11736
11980
  let amount = through ? E * 0.6 : E * 0.25;
11737
11981
  if (through && (d.role === "battery" || d.role === "alternator")) amount = Math.max(amount, p.hp * 0.65);
11738
11982
  damage(p, amount, "ballistic", h.source, out, worldPoint);
11739
- if (d.role === "fuel_tank" && E > 1500 && (p.liters ?? 0) > 1) p.temp = Math.max(p.temp, d.ignitionC ?? 260);
11983
+ if (through && d.role === "fuel_tank") rollIgnition(p, h, metal, out);
11740
11984
  }
11741
11985
  }
11742
11986
  };
11987
+ const light = (tank, out) => {
11988
+ tank.temp = Math.max(tank.temp, (tank.def.ignitionC ?? 260) + 1);
11989
+ if ((tank.liters ?? 0) > 0.5) ignite(tank, out);
11990
+ };
11991
+ const rollIgnition = (tank, h, metal, out) => {
11992
+ const d = tank.def;
11993
+ if (!d.contains || tank.burning || (tank.liters ?? 0) <= 0.05) return;
11994
+ const fill = d.contains.liters > 0 ? (tank.liters ?? 0) / d.contains.liters : 0;
11995
+ const chance = ignitionChance(h, { sparkTarget: sparkTargetFactor(d.material, metal, ignitionRules), fill, fluid: d.contains.fluid }, ignitionRules);
11996
+ if (chance > 0 && igniteRand() < chance) light(tank, out);
11997
+ };
11743
11998
  const blunt = (h, out) => {
11744
11999
  sync();
11745
12000
  const M = toFrameMatrix();
@@ -11803,7 +12058,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
11803
12058
  const verdict = kit.blastDamage(c, E / (1 + dist * dist * 4) / 50);
11804
12059
  if (verdict === "explosion") explode(tank, h.source, out);
11805
12060
  else if (verdict === "fire") {
11806
- tank.temp = Math.max(tank.temp, tank.def.ignitionC ?? 260);
12061
+ light(tank, out);
11807
12062
  startLeak(tank, centreOf(tank.def).setY(tank.def.box.center[1] - tank.def.box.size[1] / 2 + 0.05), 12, out, dir);
11808
12063
  }
11809
12064
  }
@@ -12065,7 +12320,7 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12065
12320
  const reset = () => {
12066
12321
  const old = c;
12067
12322
  const parent = old.root.parent;
12068
- const next = kit.build(old.key, old.state);
12323
+ const next = kit.build(old.key, old.state, old.scenario != null ? { scenario: old.scenario } : void 0);
12069
12324
  if (parent) {
12070
12325
  next.root.position.copy(old.root.position);
12071
12326
  next.root.quaternion.copy(old.root.quaternion);
@@ -12122,7 +12377,8 @@ function createVehicleDestructible(kit, vehicleCtx, options = {}) {
12122
12377
  // src/destructible/item.ts
12123
12378
  function buildVehicleItem(kit, key, options = {}) {
12124
12379
  const THREE = kit.THREE;
12125
- const vehicle = options.lod ? kit.build(key, options.state ?? "intact", void 0, { lod: true }) : kit.build(key, options.state ?? "intact");
12380
+ const zoneState = options.scenario != null ? { scenario: options.scenario } : void 0;
12381
+ const vehicle = options.lod ? kit.build(key, options.state ?? "intact", zoneState, { lod: true }) : kit.build(key, options.state ?? "intact", zoneState);
12126
12382
  const m = vehicle.meta;
12127
12383
  const root = new THREE.Group();
12128
12384
  root.name = key;