@adatechnology/game-destruction-three 0.17.2 → 0.18.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.
@@ -43,10 +43,12 @@ var GameDestructionThree = (() => {
43
43
  FIRE_FALLOFF: () => FIRE_FALLOFF,
44
44
  FIRE_LOOP: () => FIRE_LOOP,
45
45
  FIRE_SOUNDS: () => FIRE_SOUNDS,
46
+ GRENADE_PIN_PULL: () => GRENADE_PIN_PULL,
46
47
  IMPACT_FAMILY_TAGS: () => IMPACT_FAMILY_TAGS,
47
48
  IMPACT_SOUNDS: () => IMPACT_SOUNDS,
48
49
  IMPACT_SOUND_KEYS: () => IMPACT_SOUND_KEYS,
49
50
  SCENE_SOUNDS: () => SCENE_SOUNDS,
51
+ SMOKE_HISS_LOOP: () => SMOKE_HISS_LOOP,
50
52
  blastFireSampleBank: () => blastFireSampleBank,
51
53
  breakSoundKeys: () => breakSoundKeys,
52
54
  createDestructionRenderer: () => createDestructionRenderer,
@@ -244,7 +246,8 @@ var GameDestructionThree = (() => {
244
246
  var EVENT_BUDGET = { particles: 400, debris: 40, blastLights: 4, fireLights: 2 };
245
247
  var explosion_aliases_default = {
246
248
  carl_gustaf_84mm: "at4_84mm",
247
- vog_25: "m79_40mm"
249
+ vog_25: "m79_40mm",
250
+ semtex: "c4"
248
251
  };
249
252
  var explosions_default = {
250
253
  m67: {
@@ -6221,8 +6224,8 @@ var GameDestructionThree = (() => {
6221
6224
  id: "dust",
6222
6225
  additive: false,
6223
6226
  life: [
6224
- 1,
6225
- 2
6227
+ 2,
6228
+ 5
6226
6229
  ],
6227
6230
  size: [
6228
6231
  0.08,
@@ -6289,8 +6292,8 @@ var GameDestructionThree = (() => {
6289
6292
  id: "chips",
6290
6293
  additive: false,
6291
6294
  life: [
6292
- 0.8,
6293
- 1.5
6295
+ 2,
6296
+ 4
6294
6297
  ],
6295
6298
  size: [
6296
6299
  0.025,
@@ -7737,7 +7740,7 @@ var GameDestructionThree = (() => {
7737
7740
  };
7738
7741
  var EFFECT_STEP_SECONDS = 1 / 50;
7739
7742
 
7740
- // ../game-vfx-three/dist/chunk-CPRCWTSQ.js
7743
+ // ../game-vfx-three/dist/chunk-IOGYK5RO.js
7741
7744
  var GLOW_VERTEX = `attribute vec4 aP;attribute vec4 aC;varying vec2 vU;varying vec4 vC;
7742
7745
  void main(){vec3 R=vec3(viewMatrix[0][0],viewMatrix[1][0],viewMatrix[2][0]);vec3 Up=vec3(viewMatrix[0][1],viewMatrix[1][1],viewMatrix[2][1]);
7743
7746
  vec3 wp=aP.xyz+(R*position.x+Up*position.y)*aP.w;vU=position.xy;vC=aC;gl_Position=projectionMatrix*viewMatrix*vec4(wp,1.);}`;
@@ -7784,10 +7787,15 @@ void main(){
7784
7787
  if(n<diss*1.08-.04)discard;
7785
7788
  float lam=max(dot(N,uLight),0.);
7786
7789
  float band=lam>.55?1.:lam>.15?.68:.42;
7787
- vec3 col=vC*(vec3(.5,.56,.7)*band*1.05+vec3(.2,.19,.18)+uGround*(.4-.4*N.y));
7790
+ // soot: a dark smoke still scatters some light, so it never goes flat black, and its rim takes the sky so its volume reads (only the dark puffs; the rest as the Lab)
7791
+ float dark=1.-smoothstep(.02,.12,dot(vC,vec3(.2126,.7152,.0722)));
7792
+ vec3 alb=max(vC,vec3(.055)*dark);
7793
+ vec3 col=alb*(vec3(.5,.56,.7)*band*1.05+vec3(.2,.19,.18)+uGround*(.4-.4*N.y));
7794
+ float rim=pow(1.-clamp(abs(dot(N,normalize(cameraPosition-vWP))),0.,1.),2.);
7795
+ col+=dark*rim*(uSky*.9+alb);
7788
7796
  vec3 tg=uGlowPos-vWP;float dg=length(tg)+.001;
7789
7797
  float gi=uGlowI/(1.+dg*dg*.05)*(.3+.7*max(dot(N,tg/dg),0.));
7790
- col+=uGlowCol*gi*vC*1.6;
7798
+ col+=uGlowCol*gi*alb*1.6;
7791
7799
  float h=heat*(.72+.56*n)+heat*(-N.y)*.18;
7792
7800
  if(h>.05){
7793
7801
  float hq=floor(h*5.+.5)/5.;
@@ -9939,7 +9947,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9939
9947
  var SLAM_BOUNCE = 0.2;
9940
9948
  var SLAM_FLOOR_NORMAL = 0.7;
9941
9949
  var SUB_DT = 1 / 120;
9942
- var MU = 0.7;
9950
+ var MU_STATIC = 0.7;
9951
+ var MU_DYNAMIC = 0.6;
9952
+ var SLIP_SPEED = 0.02;
9943
9953
  var SLEEP_AFTER = 0.5;
9944
9954
  var CALM_DRIFT = 0.01;
9945
9955
  var CALM_TURN = 0.05;
@@ -9958,6 +9968,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9958
9968
  var EDGE_POINTS = [-0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75];
9959
9969
  var SUPPORT_CHECKS2 = 16;
9960
9970
  var SUPPORT_GAP2 = 0.02;
9971
+ var WAKE_ABOVE_SPEED = 0.05;
9972
+ var WAKE_ABOVE_SPIN = 0.1;
9973
+ var topFace = 1;
9961
9974
  var boxTopAt = (s, x, z) => {
9962
9975
  const e = s.e;
9963
9976
  const dx = x - s.c[0], dz = z - s.c[2];
@@ -9973,11 +9986,24 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9973
9986
  let a = (-h - p) / d, b = (h - p) / d;
9974
9987
  if (a > b) [a, b] = [b, a];
9975
9988
  t0 = Math.max(t0, a);
9976
- t1 = Math.min(t1, b);
9989
+ if (b < t1) {
9990
+ t1 = b;
9991
+ topFace = k;
9992
+ }
9977
9993
  if (t0 > t1) return -Infinity;
9978
9994
  }
9979
9995
  return s.c[1] + t1;
9980
9996
  };
9997
+ var UP16 = [0, 1, 0];
9998
+ var faceNormal = (s, axis2) => {
9999
+ const e = s.e;
10000
+ const sign = e[axis2 * 4 + 1] < 0 ? -1 : 1;
10001
+ return [sign * e[axis2 * 4], sign * e[axis2 * 4 + 1], sign * e[axis2 * 4 + 2]];
10002
+ };
10003
+ var pairFriction = (a, b) => ({
10004
+ static: Math.sqrt((a?.static ?? MU_STATIC) * (b?.static ?? MU_STATIC)),
10005
+ dynamic: Math.sqrt((a?.dynamic ?? MU_DYNAMIC) * (b?.dynamic ?? MU_DYNAMIC))
10006
+ });
9981
10007
  var PieceSystem = class {
9982
10008
  constructor(THREE, o) {
9983
10009
  __publicField(this, "THREE", THREE);
@@ -9991,6 +10017,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9991
10017
  __publicField(this, "ax");
9992
10018
  __publicField(this, "solids", []);
9993
10019
  __publicField(this, "supportTurn", 0);
10020
+ /** The surface normal and the friction of what the last `supportAt` found under the point (the ground and fixed tops are level: up, and no piece). */
10021
+ __publicField(this, "supportNormal", UP16);
10022
+ __publicField(this, "supportFriction", null);
9994
10023
  this.p = new THREE.Vector3();
9995
10024
  this.q = new THREE.Quaternion();
9996
10025
  this.dq = new THREE.Quaternion();
@@ -10096,8 +10125,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10096
10125
  const lz = (e[8] * x + e[9] * y + e[10] * z) * iz;
10097
10126
  return [e[0] * lx + e[4] * ly + e[8] * lz, e[1] * lx + e[5] * ly + e[9] * lz, e[2] * lx + e[6] * ly + e[10] * lz];
10098
10127
  }
10099
- /** The points of the box that touch the ground, in its own frame: the 8 corners, then (with `edges`) `EDGE_POINTS` along each of its 12 edges. */
10128
+ /** The points of the box that touch the ground, in its own frame: the 8 corners, then (with `edges`) `EDGE_POINTS` along each of its 12 edges; or the piece's own `contacts`. */
10100
10129
  samples(b, edges) {
10130
+ if (b.contacts) return b.contacts;
10101
10131
  const [hx, hy, hz2] = b.halfExtents;
10102
10132
  const out = [];
10103
10133
  for (let i = 0; i < 8; i++) out.push([i & 1 ? hx : -hx, i & 2 ? hy : -hy, i & 4 ? hz2 : -hz2]);
@@ -10162,6 +10192,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10162
10192
  }
10163
10193
  if (b.sleeping) continue;
10164
10194
  this.integrate(b, dt);
10195
+ if (b.sleeping) continue;
10196
+ const v = b.velocity;
10197
+ const w = b.angularVelocity;
10198
+ if (Math.hypot(v[0], v[1], v[2]) > WAKE_ABOVE_SPEED || Math.hypot(w[0], w[1], w[2]) > WAKE_ABOVE_SPIN) this.wakeAbove(b);
10165
10199
  }
10166
10200
  this.separate(dt);
10167
10201
  }
@@ -10192,15 +10226,48 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10192
10226
  }
10193
10227
  return false;
10194
10228
  }
10229
+ /** The highest top of a piece (other than `self`) under `(x, z)` that a point at height `y` stands on (at most `step` above it), -Infinity when none; the boxes of the last update. */
10230
+ topAt(x, z, y, step, self) {
10231
+ let top = -Infinity;
10232
+ for (const s of this.solids) {
10233
+ if (s.body.objectId === self || s.body.held || Math.abs(x - s.c[0]) > s.r || Math.abs(z - s.c[2]) > s.r) continue;
10234
+ const t = boxTopAt(s, x, z);
10235
+ if (t > top && t <= y + step) top = t;
10236
+ }
10237
+ return top;
10238
+ }
10239
+ /** What lay asleep on `b` (a box over its top) wakes at once when `b` moves: a can on a table that goes over goes with it. */
10240
+ wakeAbove(b) {
10241
+ const below = this.solids.find((s) => s.body === b);
10242
+ if (!below) return;
10243
+ const top = below.c[1] + below.half[0] * Math.abs(below.e[1]) + below.half[1] * Math.abs(below.e[5]) + below.half[2] * Math.abs(below.e[9]);
10244
+ for (const s of this.solids) {
10245
+ const o = s.body;
10246
+ if (!o.sleeping || o.held || o === b) continue;
10247
+ if (s.c[1] < below.c[1] || s.c[1] - s.r > top + STEP || Math.hypot(s.c[0] - below.c[0], s.c[2] - below.c[2]) > s.r + below.r) continue;
10248
+ o.sleeping = false;
10249
+ o.still = 0;
10250
+ o.calmAt = null;
10251
+ }
10252
+ }
10195
10253
  /** Where a corner of `self` at `(x, y, z)` lands: the ground, the top of another piece or of something fixed under it. */
10196
10254
  supportAt(self, x, y, z, step) {
10197
10255
  let top = this.o.groundAt(x, z);
10256
+ let on = null;
10257
+ let face = 1;
10198
10258
  for (const s of this.solids) {
10199
10259
  if (s.body === self || s.body.held || Math.abs(x - s.c[0]) > s.r || Math.abs(z - s.c[2]) > s.r) continue;
10200
10260
  const t = boxTopAt(s, x, z);
10201
- if (t > top && t <= y + step) top = t;
10261
+ if (t > top && t <= y + step) {
10262
+ top = t;
10263
+ on = s;
10264
+ face = topFace;
10265
+ }
10202
10266
  }
10203
10267
  const fixed = this.o.staticTopAt?.(x, z, y, step) ?? -Infinity;
10268
+ if (fixed >= top) on = null;
10269
+ this.supportNormal = on ? faceNormal(on, face) : UP16;
10270
+ this.supportFriction = on?.body.friction ?? null;
10204
10271
  return Math.max(top, fixed);
10205
10272
  }
10206
10273
  /** Stops a piece that ran into something on its way from `start` to `pos` (`sweep`): moves it back to where it met it and takes the speed into it. */
@@ -10283,7 +10350,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10283
10350
  const e = this.m.elements;
10284
10351
  const rs = [];
10285
10352
  const ids = [];
10353
+ const ns = [];
10354
+ const mus = [];
10286
10355
  let maxPen = 0;
10356
+ let penNormal = UP16;
10287
10357
  const rise = STEP + Math.max(0, -v[1]) * h * 2;
10288
10358
  let side = null;
10289
10359
  let lowest = Infinity;
@@ -10301,6 +10371,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10301
10371
  if (floor2 !== void 0 && floor2 - cy > -SLOP && floor2 < raw - STEP) {
10302
10372
  rs.push(r);
10303
10373
  ids.push(i);
10374
+ ns.push(UP16);
10375
+ mus.push(pairFriction(b.friction, null));
10304
10376
  maxPen = Math.max(maxPen, floor2 - cy);
10305
10377
  }
10306
10378
  continue;
@@ -10321,12 +10393,19 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10321
10393
  }
10322
10394
  continue;
10323
10395
  }
10324
- const gy = i < 8 ? this.supportAt(b, px, cy, pz, rise) : Math.max(raw, this.o.staticTopAt?.(px, pz, cy, rise) ?? -Infinity);
10396
+ this.supportNormal = UP16;
10397
+ this.supportFriction = null;
10398
+ const gy = i < 8 || b.contacts ? this.supportAt(b, px, cy, pz, rise) : Math.max(raw, this.o.staticTopAt?.(px, pz, cy, rise) ?? -Infinity);
10325
10399
  const pen = gy - cy;
10326
10400
  if (pen > -SLOP) {
10327
10401
  rs.push(r);
10328
10402
  ids.push(i);
10329
- if (pen > maxPen) maxPen = pen;
10403
+ ns.push(this.supportNormal);
10404
+ mus.push(pairFriction(b.friction, this.supportFriction));
10405
+ if (pen > maxPen) {
10406
+ maxPen = pen;
10407
+ penNormal = this.supportNormal;
10408
+ }
10330
10409
  lowest = Math.min(lowest, gy);
10331
10410
  }
10332
10411
  }
@@ -10339,7 +10418,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10339
10418
  contactedLast = rs.length > 0;
10340
10419
  if (contactedLast) touched = true;
10341
10420
  if (!contactedLast) continue;
10342
- pos[1] += Math.max(0, maxPen - SLOP * 0.5);
10421
+ const out = Math.max(0, maxPen - SLOP * 0.5) * penNormal[1];
10422
+ pos[0] += penNormal[0] * out;
10423
+ pos[1] += penNormal[1] * out;
10424
+ pos[2] += penNormal[2] * out;
10343
10425
  let impact3 = 0;
10344
10426
  for (const r of rs) impact3 = Math.max(impact3, -(v[1] + w[2] * r[0] - w[0] * r[2]));
10345
10427
  const restitution = impact3 > 2 ? 0.25 : 0;
@@ -10355,76 +10437,72 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10355
10437
  const lz = pos[2] + sz / rs.length;
10356
10438
  landPoint = [lx, pos[1] - Math.max(...rs.map((r) => -r[1])), lz];
10357
10439
  }
10358
- const goal = rs.map((r) => {
10359
- const vy2 = v[1] + (w[2] * r[0] - w[0] * r[2]);
10360
- return vy2 < 0 ? -restitution * vy2 : 0;
10440
+ const pointV = (r) => [v[0] + w[1] * r[2] - w[2] * r[1], v[1] + w[2] * r[0] - w[0] * r[2], v[2] + w[0] * r[1] - w[1] * r[0]];
10441
+ const dot = (a, c2) => a[0] * c2[0] + a[1] * c2[1] + a[2] * c2[2];
10442
+ const goal = rs.map((r, k) => {
10443
+ const vn = dot(pointV(r), ns[k]);
10444
+ return vn < 0 ? -restitution * vn : 0;
10445
+ });
10446
+ const slipping = rs.map((r, k) => {
10447
+ const pv = pointV(r);
10448
+ const vn = dot(pv, ns[k]);
10449
+ return Math.hypot(pv[0] - vn * ns[k][0], pv[1] - vn * ns[k][1], pv[2] - vn * ns[k][2]) > SLIP_SPEED;
10361
10450
  });
10362
10451
  const vx0 = v[0];
10363
10452
  const vz0 = v[2];
10364
- if (!b.warm || b.warm.length !== WARM_SLOTS * 3) b.warm = new Float64Array(WARM_SLOTS * 3);
10453
+ if (!b.warm || b.warm.length !== WARM_SLOTS * 4) b.warm = new Float64Array(WARM_SLOTS * 4);
10365
10454
  const warm = b.warm;
10366
- const accN = ids.map((id) => id < WARM_SLOTS ? warm[id * 3] : 0);
10367
- const accT = ids.map((id) => id < WARM_SLOTS ? [warm[id * 3 + 1], warm[id * 3 + 2]] : [0, 0]);
10368
- for (let k = 0; k < rs.length; k++) {
10369
- const r = rs[k];
10370
- const jn = accN[k];
10371
- const [fx, fz] = accT[k];
10372
- if (jn === 0 && fx === 0 && fz === 0) continue;
10373
- v[0] += fx * invM;
10374
- v[1] += jn * invM;
10375
- v[2] += fz * invM;
10376
- const dw = this.mulInvInertiaWith(b, e, r[1] * fz - r[2] * jn, r[2] * fx - r[0] * fz, r[0] * jn - r[1] * fx);
10455
+ const accN = ids.map((id) => id < WARM_SLOTS ? warm[id * 4] : 0);
10456
+ const accT = ids.map((id) => id < WARM_SLOTS ? [warm[id * 4 + 1], warm[id * 4 + 2], warm[id * 4 + 3]] : [0, 0, 0]);
10457
+ const push = (r, j) => {
10458
+ v[0] += j[0] * invM;
10459
+ v[1] += j[1] * invM;
10460
+ v[2] += j[2] * invM;
10461
+ const dw = this.mulInvInertiaWith(b, e, r[1] * j[2] - r[2] * j[1], r[2] * j[0] - r[0] * j[2], r[0] * j[1] - r[1] * j[0]);
10377
10462
  w[0] += dw[0];
10378
10463
  w[1] += dw[1];
10379
10464
  w[2] += dw[2];
10465
+ };
10466
+ const reach = (r, d) => {
10467
+ const a = this.mulInvInertiaWith(b, e, r[1] * d[2] - r[2] * d[1], r[2] * d[0] - r[0] * d[2], r[0] * d[1] - r[1] * d[0]);
10468
+ return invM + (a[1] * r[2] - a[2] * r[1]) * d[0] + (a[2] * r[0] - a[0] * r[2]) * d[1] + (a[0] * r[1] - a[1] * r[0]) * d[2];
10469
+ };
10470
+ for (let k = 0; k < rs.length; k++) {
10471
+ const n = ns[k];
10472
+ const t = accT[k];
10473
+ if (accN[k] !== 0 || t[0] !== 0 || t[1] !== 0 || t[2] !== 0) push(rs[k], [n[0] * accN[k] + t[0], n[1] * accN[k] + t[1], n[2] * accN[k] + t[2]]);
10380
10474
  }
10381
10475
  for (let it = 0; it < SOLVER_PASSES; it++) {
10382
10476
  for (let k = 0; k < rs.length; k++) {
10383
10477
  const r = rs[k];
10384
- const vy2 = v[1] + (w[2] * r[0] - w[0] * r[2]);
10385
- const a = this.mulInvInertiaWith(b, e, -r[2], 0, r[0]);
10386
- const denom = invM + (a[2] * r[0] - a[0] * r[2]);
10387
- const total = Math.max(0, accN[k] + (goal[k] - vy2) / denom);
10478
+ const n = ns[k];
10479
+ const vn = dot(pointV(r), n);
10480
+ const total = Math.max(0, accN[k] + (goal[k] - vn) / reach(r, n));
10388
10481
  const j = total - accN[k];
10389
10482
  accN[k] = total;
10390
- if (j !== 0) {
10391
- v[1] += j * invM;
10392
- w[0] += a[0] * j;
10393
- w[1] += a[1] * j;
10394
- w[2] += a[2] * j;
10395
- }
10396
- const vx2 = v[0] + (w[1] * r[2] - w[2] * r[1]);
10397
- const vz2 = v[2] + (w[0] * r[1] - w[1] * r[0]);
10398
- const vt = Math.hypot(vx2, vz2);
10399
- if (vt < 1e-6 && accT[k][0] === 0 && accT[k][1] === 0) continue;
10400
- let fx = accT[k][0];
10401
- let fz = accT[k][1];
10402
- if (vt > 1e-6) {
10403
- const tx = vx2 / vt;
10404
- const tz = vz2 / vt;
10405
- const at3 = this.mulInvInertiaWith(b, e, r[1] * tz, r[2] * tx - r[0] * tz, -r[1] * tx);
10406
- const cx = at3[1] * r[2] - at3[2] * r[1];
10407
- const cz = at3[0] * r[1] - at3[1] * r[0];
10408
- const jt = vt / (invM + cx * tx + cz * tz);
10409
- fx -= tx * jt;
10410
- fz -= tz * jt;
10411
- }
10412
- const most = MU * accN[k];
10413
- const f = Math.hypot(fx, fz);
10414
- if (f > most) {
10415
- fx *= most / f;
10416
- fz *= most / f;
10483
+ if (j !== 0) push(r, [n[0] * j, n[1] * j, n[2] * j]);
10484
+ const pv = pointV(r);
10485
+ const pn = dot(pv, n);
10486
+ const vt = [pv[0] - pn * n[0], pv[1] - pn * n[1], pv[2] - pn * n[2]];
10487
+ const speed = Math.hypot(vt[0], vt[1], vt[2]);
10488
+ const f = [accT[k][0], accT[k][1], accT[k][2]];
10489
+ if (speed > 1e-6) {
10490
+ const d = [vt[0] / speed, vt[1] / speed, vt[2] / speed];
10491
+ const jt = speed / reach(r, d);
10492
+ f[0] -= d[0] * jt;
10493
+ f[1] -= d[1] * jt;
10494
+ f[2] -= d[2] * jt;
10495
+ } else if (f[0] === 0 && f[1] === 0 && f[2] === 0) continue;
10496
+ const most = (slipping[k] ? mus[k].dynamic : mus[k].static) * accN[k];
10497
+ const fl = Math.hypot(f[0], f[1], f[2]);
10498
+ if (fl > most) {
10499
+ f[0] *= most / fl;
10500
+ f[1] *= most / fl;
10501
+ f[2] *= most / fl;
10417
10502
  }
10418
- const dx = fx - accT[k][0];
10419
- const dz = fz - accT[k][1];
10420
- accT[k] = [fx, fz];
10421
- if (dx === 0 && dz === 0) continue;
10422
- v[0] += dx * invM;
10423
- v[2] += dz * invM;
10424
- const dw = this.mulInvInertiaWith(b, e, r[1] * dz, r[2] * dx - r[0] * dz, -r[1] * dx);
10425
- w[0] += dw[0];
10426
- w[1] += dw[1];
10427
- w[2] += dw[2];
10503
+ const dj = [f[0] - accT[k][0], f[1] - accT[k][1], f[2] - accT[k][2]];
10504
+ accT[k] = f;
10505
+ if (dj[0] !== 0 || dj[1] !== 0 || dj[2] !== 0) push(r, dj);
10428
10506
  }
10429
10507
  }
10430
10508
  pos[0] += (v[0] - vx0) * h;
@@ -10433,9 +10511,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10433
10511
  for (let k = 0; k < rs.length; k++) {
10434
10512
  const id = ids[k];
10435
10513
  if (id >= WARM_SLOTS) continue;
10436
- warm[id * 3] = accN[k];
10437
- warm[id * 3 + 1] = accT[k][0];
10438
- warm[id * 3 + 2] = accT[k][1];
10514
+ warm[id * 4] = accN[k];
10515
+ warm[id * 4 + 1] = accT[k][0];
10516
+ warm[id * 4 + 2] = accT[k][1];
10517
+ warm[id * 4 + 3] = accT[k][2];
10439
10518
  }
10440
10519
  let x0 = Infinity, x1 = -Infinity, z0 = Infinity, z1 = -Infinity, low = 0;
10441
10520
  for (const r of rs) {
@@ -10507,6 +10586,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10507
10586
  b.calm += dt;
10508
10587
  return b.calm >= CALM_SLEEP_AFTER;
10509
10588
  }
10589
+ /** Whether the centre `c` of `upper` is over the top of `lower`'s box (as it was at the start of the update). */
10590
+ onTopOf(upper, lower, c) {
10591
+ const s = this.solids.find((one) => one.body === lower);
10592
+ if (!s) return false;
10593
+ const top = boxTopAt(s, c.x, c.z);
10594
+ return top > -Infinity && c.y >= top - 0.01;
10595
+ }
10510
10596
  /** Half the height of the box of a piece as it is turned now. */
10511
10597
  halfHeight(b) {
10512
10598
  const e = this.m.makeRotationFromQuaternion(b.root.quaternion).elements;
@@ -10532,6 +10618,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10532
10618
  this.centerOf(b, cb);
10533
10619
  if (Math.abs(ca.y - cb.y) > ra + rb) continue;
10534
10620
  if (Math.abs(ca.y - cb.y) >= (this.halfHeight(a) + this.halfHeight(b)) * 0.9) continue;
10621
+ if (this.onTopOf(b, a, cb) || this.onTopOf(a, b, ca)) continue;
10535
10622
  const rh = (Math.max(a.halfExtents[0], a.halfExtents[2]) + Math.max(b.halfExtents[0], b.halfExtents[2])) * 0.8;
10536
10623
  let dx = cb.x - ca.x;
10537
10624
  let dz = cb.z - ca.z;
@@ -13462,6 +13549,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13462
13549
  var IMPACT_TAGS = ["metal", "glass", "wood"];
13463
13550
  var GLOW_FROM_C = 430;
13464
13551
  var QUEUE_CAP = 4e3;
13552
+ var TRICKLE_SECONDS = [2, 5];
13553
+ var TRICKLE_EVERY = 0.25;
13554
+ var TRICKLE_MAX = 24;
13465
13555
  var STRUCTURAL_EVENTS = /* @__PURE__ */ new Set([
13466
13556
  "fracture",
13467
13557
  "detach",
@@ -13485,6 +13575,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13485
13575
  var FEET_BAND = 0.03;
13486
13576
  var TIP_MIN_HEIGHT = 0.3;
13487
13577
  var TIP_NARROW = 0.3;
13578
+ var CONTACT_MESHES = 12;
13579
+ var BROKEN_GAP = 0.03;
13580
+ var BROKEN_SAMPLES = [[0.5, 0.5], [0.1, 0.1], [0.9, 0.1], [0.1, 0.9], [0.9, 0.9]];
13581
+ var UNCHECKED_KINDS = /* @__PURE__ */ new Set(["structure", "room", "ground"]);
13488
13582
  var FIRE_CEILING_SECONDS = 3600;
13489
13583
  var GENERIC_REFERENCE_RADIUS = 15;
13490
13584
  var SWEEP_CORE = 0.15;
@@ -13584,10 +13678,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13584
13678
  __publicField(this, "staticRound", []);
13585
13679
  /** Boxes of the pieces' meshes (as `staticBoxes`), with the time they were measured. */
13586
13680
  __publicField(this, "pieceBoxes", /* @__PURE__ */ new WeakMap());
13681
+ /** Objects broken since the last update: `checkBroken` looks at what is left of them. */
13682
+ __publicField(this, "broken", /* @__PURE__ */ new Set());
13587
13683
  __publicField(this, "staticAt", 0);
13588
13684
  __publicField(this, "queue", []);
13589
13685
  /** The cover of each body around a part of the world that went off, read as the event is emitted: before the blast tore or moved anything. */
13590
13686
  __publicField(this, "coverWhenWentOff", /* @__PURE__ */ new WeakMap());
13687
+ /** Places a blow broke a hard face at: grit and dust keep coming off them for a few seconds, less and less (`TRICKLE_SECONDS`). */
13688
+ __publicField(this, "trickles", []);
13591
13689
  /** Presentation work an event left for later frames (fragments still to spawn, loose groups still to split), run in order within the frame budget. */
13592
13690
  __publicField(this, "slices", []);
13593
13691
  /** When the work of this frame must stop; `Infinity` outside a drain, where a call finishes what it started. */
@@ -13930,10 +14028,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13930
14028
  dt = clamp6(dt, 0, 0.1);
13931
14029
  this.time += dt;
13932
14030
  if (!this.attached) this.drainQueue();
14031
+ this.checkBroken();
13933
14032
  for (const b of this.bounds.values()) b.onFrame?.(dt);
13934
14033
  for (const b of this.bounds.values()) if (b.soilNode && this.world.state(b.id)?.soil?.status === "yielding") this.onSettle(b.id);
13935
14034
  this.updateActive(dt);
13936
14035
  this.updateLeaks(dt);
14036
+ this.updateTrickles(dt);
13937
14037
  this.pieceSys.update(dt);
13938
14038
  this.fragments.update(dt);
13939
14039
  this.ground.update(dt);
@@ -14132,6 +14232,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14132
14232
  }
14133
14233
  apply(ev) {
14134
14234
  this.staticDirty.add(ev.object);
14235
+ if (ev.type === "fracture" || ev.type === "detach" || ev.type === "collapse" || ev.type === "burnout" || ev.type === "destroyed") this.broken.add(ev.object);
14135
14236
  switch (ev.type) {
14136
14237
  case "damage":
14137
14238
  if (ev.damageType === "heat" || ev.damageType === "fire" || ev.damageType === "explosive") this.activate(ev.object, ev.part);
@@ -14456,9 +14557,34 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14456
14557
  const core = def?.visual?.innerColor;
14457
14558
  if (core !== void 0) this.vfx.burst("chips", ev.point, [normal.x, normal.y, normal.z], 5, 2.5, { color: core });
14458
14559
  else this.playEvent({ kind: "spall", material: this.impactMaterial(def) }, ev.point);
14560
+ if (core !== void 0) this.trickleFrom(ev.point, [normal.x, normal.y, normal.z], core);
14459
14561
  if (mv.mesh.userData.drawnSpalls?.includes(ev.part)) return;
14460
14562
  this.visuals.addSpall(mv, wp, normal, Math.sqrt(ev.area / Math.PI), core ?? fallbackColor(pv.material, def));
14461
14563
  }
14564
+ /** Grit and dust go on falling from where a blow broke a hard face (masonry, concrete) for `TRICKLE_SECONDS`, a pinch at a time and less and less. */
14565
+ trickleFrom(point, normal, color) {
14566
+ if (this.trickles.length >= TRICKLE_MAX) this.trickles.shift();
14567
+ const total = TRICKLE_SECONDS[0] + (TRICKLE_SECONDS[1] - TRICKLE_SECONDS[0]) * this.rng();
14568
+ this.trickles.push({ point: [point[0], point[1], point[2]], normal, color, left: total, total, next: TRICKLE_EVERY });
14569
+ }
14570
+ updateTrickles(dt) {
14571
+ for (let i = this.trickles.length - 1; i >= 0; i--) {
14572
+ const t = this.trickles[i];
14573
+ t.left -= dt;
14574
+ t.next -= dt;
14575
+ if (t.left <= 0) {
14576
+ this.trickles.splice(i, 1);
14577
+ continue;
14578
+ }
14579
+ if (t.next > 0) continue;
14580
+ const share = t.left / t.total;
14581
+ t.next = TRICKLE_EVERY / Math.max(share, 0.25);
14582
+ const out = [t.normal[0] * 0.2, -1, t.normal[2] * 0.2];
14583
+ const options = t.color !== void 0 ? { color: t.color } : void 0;
14584
+ this.vfx.burst("chips", t.point, out, this.low ? 1 : 2, 0.3, options);
14585
+ if (this.rng() < 0.5) this.vfx.burst("dust", t.point, out, 1, 0.2, options);
14586
+ }
14587
+ }
14462
14588
  /**
14463
14589
  * Stage 2 of a staged part: the unit-sized fragments come out and the part stays drawn (hiding it is what `fracture` does). The opening itself
14464
14590
  * (cutting the wall's faces) is not drawn yet: it needs the per-vertex binding of the house renderer.
@@ -14494,6 +14620,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14494
14620
  const tone = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === def ? color : fallbackColor(fr.material, fdef);
14495
14621
  this.fragments.spawn(fr, this.fragments.materialFor(fr.material, tone, false), ev.object, maxAlive);
14496
14622
  }
14623
+ if (def?.visual?.innerColor !== void 0 || pv?.material === "concrete" || pv?.material === "brick") this.trickleFrom(ev.center, [0, 0, 0], color);
14497
14624
  if (ev.torn) {
14498
14625
  this.impactSound(pv?.material ?? "", ev.center, 0.5, ev.blow);
14499
14626
  return;
@@ -14683,26 +14810,30 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14683
14810
  this.o.onPiece?.(piece);
14684
14811
  }
14685
14812
  /**
14686
- * Furniture that lost what it stood on falls: with every leg of a table broken the top is loose, and with the legs of one side gone what is left topples. Feet are the
14687
- * meshes at the bottom of the object as it was whole; while the ones left still span the same floor (a cupboard with a broken door, a table missing one leg) it stands.
14813
+ * What lost what it stood on falls (furniture, a stand, a crate, a car; not a structure): with every leg of a table broken the top is loose, and with the legs of one side
14814
+ * gone what is left topples. Feet are the meshes at the bottom of the object as it was whole; while the ones left still span the same floor (a cupboard with a broken door,
14815
+ * a table missing one leg) it stands. A piece that lost a part is the box of what is left, touching the ground by the corners of each part (`setContacts`).
14688
14816
  */
14689
14817
  loosenIfFootless(objectId) {
14690
14818
  const b = this.bounds.get(objectId);
14691
14819
  if (!b || !this.autoSplit) return;
14692
14820
  const left = this.meshesOf(b).filter((mv) => this.visuals.shown(mv));
14693
14821
  if (left.length === 0) return;
14822
+ const structural = UNCHECKED_KINDS.has(this.kindOf(b));
14694
14823
  const body = b.piece ?? this.pieceSys.get(objectId);
14695
14824
  if (body) {
14696
14825
  const { half, center } = this.measure(b.root, left);
14697
14826
  body.halfExtents = half;
14698
14827
  this.pieceSys.setCenter(body, center);
14699
14828
  body.mass = Math.max(0.05, this.massOf(objectId));
14829
+ if (!structural) this.setContacts(b, body, left);
14700
14830
  body.sleeping = false;
14701
14831
  return;
14702
14832
  }
14703
- if (kitOriginOf(b.root)?.kind !== "furniture") return;
14833
+ if (structural) return;
14704
14834
  const boxOf = (mv) => this.visuals.worldBox(mv, new this.THREE.Box3());
14705
14835
  const whole = [...b.parts.values()].flatMap((pv) => pv.meshes).map(boxOf).filter((one) => !one.isEmpty());
14836
+ if (!this.standsOn(objectId, whole.map((one) => [one.min.x, one.min.y, one.min.z, one.max.x, one.max.y, one.max.z]))) return;
14706
14837
  const floor2 = Math.min(...whole.map((one) => one.min.y));
14707
14838
  const feetOf = (boxes) => boxes.filter((one) => one.min.y < floor2 + FEET_BAND).reduce((feet, one) => feet.union(one), new this.THREE.Box3());
14708
14839
  const was = feetOf(whole);
@@ -14718,9 +14849,116 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14718
14849
  if (!narrow && centreX > now.min.x && centreX < now.max.x && centreZ > now.min.z && centreZ < now.max.z) return;
14719
14850
  }
14720
14851
  const piece = this.makePiece(objectId);
14852
+ this.setContacts(b, b.piece ?? piece, left);
14721
14853
  this.tipIfTopHeavy(b);
14722
14854
  this.o.onLoosened?.(objectId, piece, "apart");
14723
14855
  }
14856
+ shownBoxes(objectId, partId) {
14857
+ const b = this.bounds.get(objectId);
14858
+ if (!b) return [];
14859
+ const box = new this.THREE.Box3();
14860
+ const out = [];
14861
+ const pv = partId === void 0 ? null : b.parts.get(partId);
14862
+ if (partId !== void 0 && (!pv || pv.gone !== "none")) return out;
14863
+ for (const mv of pv ? pv.meshes : this.meshesOf(b)) {
14864
+ if (!this.visuals.shown(mv)) continue;
14865
+ this.visuals.worldBox(mv, box);
14866
+ if (!box.isEmpty()) out.push([box.min.x, box.min.y, box.min.z, box.max.x, box.max.y, box.max.z]);
14867
+ }
14868
+ return out;
14869
+ }
14870
+ /**
14871
+ * What is left of each object broken since the last update stands on something, or falls: an object that is not a piece whose lowest drawn box has neither the ground, a
14872
+ * fixed top nor a piece within `BROKEN_GAP` under it is made a piece on the spot (see `onBrokenChecked`).
14873
+ */
14874
+ checkBroken() {
14875
+ if (this.broken.size === 0) return;
14876
+ const ids = [...this.broken];
14877
+ this.broken.clear();
14878
+ for (const id of ids) {
14879
+ const released = this.releaseIfFloating(id);
14880
+ this.o.onBrokenChecked?.(id, released);
14881
+ }
14882
+ }
14883
+ releaseIfFloating(objectId) {
14884
+ const b = this.bounds.get(objectId);
14885
+ if (!b || b.piece || this.pieceSys.get(objectId) || !this.autoSplit || UNCHECKED_KINDS.has(this.kindOf(b))) return false;
14886
+ const boxes = this.shownBoxes(objectId);
14887
+ if (boxes.length === 0) return false;
14888
+ const whole = new this.THREE.Box3();
14889
+ const wholeBoxes = [];
14890
+ for (const pv of b.parts.values()) {
14891
+ for (const mv of pv.meshes) {
14892
+ this.visuals.worldBox(mv, whole);
14893
+ if (!whole.isEmpty()) wholeBoxes.push([whole.min.x, whole.min.y, whole.min.z, whole.max.x, whole.max.y, whole.max.z]);
14894
+ }
14895
+ }
14896
+ if (!this.standsOn(objectId, wholeBoxes) || this.standsOn(objectId, boxes)) return false;
14897
+ const piece = this.makePiece(objectId);
14898
+ this.setContacts(b, b.piece ?? piece, this.meshesOf(b).filter((mv) => this.visuals.shown(mv)));
14899
+ this.tipIfTopHeavy(b);
14900
+ this.o.onLoosened?.(objectId, piece, "apart");
14901
+ return true;
14902
+ }
14903
+ /** Whether the lowest of `boxes` (world boxes of the object `objectId`) has the ground, a fixed top or a piece within `BROKEN_GAP` under it. */
14904
+ standsOn(objectId, boxes) {
14905
+ if (boxes.length === 0) return false;
14906
+ const low = Math.min(...boxes.map((one) => one[1]));
14907
+ for (const [x0, y0, z0, x1, , z1] of boxes) {
14908
+ if (y0 > low + FEET_BAND) continue;
14909
+ for (const [fx, fz] of BROKEN_SAMPLES) {
14910
+ const x = x0 + (x1 - x0) * fx;
14911
+ const z = z0 + (z1 - z0) * fz;
14912
+ const under = Math.max(this.groundAt(x, z), this.staticTopAtBut(objectId, x, z, y0, BROKEN_GAP), this.pieceSys.topAt(x, z, y0, BROKEN_GAP, objectId));
14913
+ if (under >= y0 - BROKEN_GAP) return true;
14914
+ }
14915
+ }
14916
+ return false;
14917
+ }
14918
+ /** The kind of kit piece a bound object is, by the mark on its root or the nearest one above it (a house binds a group inside its marked root); '' when unmarked. */
14919
+ kindOf(b) {
14920
+ for (let o = b.root; o; o = o.parent) {
14921
+ const kind = kitOriginOf(o)?.kind;
14922
+ if (kind) return kind;
14923
+ }
14924
+ return "";
14925
+ }
14926
+ /** `staticTopAt` without the boxes of `objectId` itself. */
14927
+ staticTopAtBut(objectId, x, z, y, step) {
14928
+ let top = -Infinity;
14929
+ for (const [id, a] of this.staticNow()) {
14930
+ if (id === objectId || a.length === 0 || x < a[0] || x > a[3] || z < a[2] || z > a[5]) continue;
14931
+ for (let i = 6; i < a.length; i += 6) {
14932
+ if (x < a[i] || x > a[i + 3] || z < a[i + 2] || z > a[i + 5]) continue;
14933
+ const t = a[i + 4];
14934
+ if (t > top && t <= y + step) top = t;
14935
+ }
14936
+ }
14937
+ return top;
14938
+ }
14939
+ /**
14940
+ * The piece touches the ground by the corners of each part left of it, not by the box around them all: on one leg, or on the legs of one side, its centre of mass is off
14941
+ * what holds it and it goes over until the top meets the floor. Past `CONTACT_MESHES` meshes it keeps the corners of its box.
14942
+ */
14943
+ setContacts(b, body, meshes) {
14944
+ if (meshes.length > CONTACT_MESHES) {
14945
+ body.contacts = void 0;
14946
+ return;
14947
+ }
14948
+ const inv = specFrameInverse(this.THREE, b.root);
14949
+ const m4 = new this.THREE.Matrix4();
14950
+ const lb = new this.THREE.Box3();
14951
+ const [cx, cy, cz] = body.localCenter;
14952
+ const points = [];
14953
+ for (const mv of meshes) {
14954
+ mv.mesh.updateWorldMatrix(true, false);
14955
+ this.visuals.localBox(mv, lb);
14956
+ if (lb.isEmpty()) continue;
14957
+ lb.applyMatrix4(m4.multiplyMatrices(inv, mv.mesh.matrixWorld));
14958
+ for (let i = 0; i < 8; i++) points.push([(i & 1 ? lb.max.x : lb.min.x) - cx, (i & 2 ? lb.max.y : lb.min.y) - cy, (i & 4 ? lb.max.z : lb.min.z) - cz]);
14959
+ }
14960
+ body.contacts = points.length > 0 ? points : void 0;
14961
+ }
14724
14962
  /**
14725
14963
  * A piece of furniture standing on feet too close for its height (a board on two legs in a line, a lone leg) does not stay up: the box of a piece hides that its weight is up
14726
14964
  * high and its feet are thin, so it gets the start of the fall, across the line of its feet.
@@ -15012,6 +15250,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
15012
15250
  this.active.clear();
15013
15251
  for (const key of [...this.fires.keys()]) this.stopFire(key);
15014
15252
  this.leaks.length = 0;
15253
+ this.trickles.length = 0;
15015
15254
  for (const id of [...this.bounds.keys()]) if (this.bounds.has(id)) this.restore(id);
15016
15255
  for (const b of [...this.bounds.values()]) {
15017
15256
  if (b.isSplit) {
@@ -15147,7 +15386,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
15147
15386
  return { id: options.id, samples };
15148
15387
  }
15149
15388
 
15150
- // ../game-audio/dist/chunk-UUWKSG4J.js
15389
+ // ../game-audio/dist/chunk-BUYBYP4L.js
15151
15390
  var DISTANCE_CURVES = {
15152
15391
  /** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
15153
15392
  game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
@@ -15325,6 +15564,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
15325
15564
  ["revolver_38_357", 2, "sfx_pistol"],
15326
15565
  ["revolver_44_50ae", 3, "sfx_pistol"],
15327
15566
  ["smg_9mm_auto", 1, "sfx_pistol"],
15567
+ ["launcher_40mm", 2],
15568
+ ["rocket_launch", 2],
15328
15569
  ["shotgun_12", 1, "sfx_shotgun"],
15329
15570
  ["rifle_5_56", 1, "sfx_rifle"],
15330
15571
  ["rifle_7_62x39", 1, "sfx_rifle"],
@@ -16790,6 +17031,21 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
16790
17031
  volume: 0,
16791
17032
  sources: ["sdk: the roar under the crackles of a fire"]
16792
17033
  };
17034
+ var SMOKE_HISS_LOOP = {
17035
+ id: "smoke_grenade_hiss",
17036
+ bus: "world",
17037
+ order: "chain_loop",
17038
+ source: { kind: "noise", loop: true },
17039
+ filters: { mode: "serial", bank: [{ type: "highpass", frequency: { set: 1400 }, q: 0.7 }, { type: "lowpass", frequency: { set: 7e3 }, q: 0.5 }] },
17040
+ volume: 0,
17041
+ sources: ["sdk: a smoke canister venting (the gas through its holes, steady for its whole burn)"]
17042
+ };
17043
+ var GRENADE_PIN_PULL = blast(
17044
+ "grenade_pin_pull",
17045
+ [sceneNoise({ type: "highpass", f: [3200, 4200], d: 0.025, vol: 0.5 }), sceneNoise({ type: "bandpass", f: [2200, 2800], q: 2, d: 0.04, vol: 0.4, delay: 0.18 })],
17046
+ ["sdk: the ring pulled from the fuse, then the spoon flying off"],
17047
+ FIRE_FALLOFF
17048
+ );
16793
17049
  var FIRE_FUEL_OF = {
16794
17050
  wood_soft: "wood",
16795
17051
  wood_hard: "wood",
@@ -16979,7 +17235,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
16979
17235
  impact2("plastic_crack", [sceneNoise({ type: "bandpass", f: 1800, q: 2, d: 0.06, vol: 0.75 }), sceneTone({ f: [450, 550], f2: 250, d: 0.05, vol: 0.22 })]),
16980
17236
  impact2("mdf_crumble", [sceneNoise({ type: "lowpass", f: 700, d: 0.25, vol: 0.75, wet: 0.2 }), ...sceneMany(4, () => sceneNoise({ type: "lowpass", f: 500, d: 0.05, vol: 0.32, delay: [0.05, 0.3] }))])
16981
17237
  ];
16982
- var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, ...LEAF_SOUNDS, ...IMPACT_SOUNDS, EXPLOSION_BOOM, ...BLAST_SOUNDS, ...FIRE_SOUNDS], loops: [BURN_LOOP, FIRE_LOOP, ...FIRE_FUEL_LOOPS] };
17238
+ var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, ...LEAF_SOUNDS, ...IMPACT_SOUNDS, EXPLOSION_BOOM, ...BLAST_SOUNDS, ...FIRE_SOUNDS, GRENADE_PIN_PULL], loops: [BURN_LOOP, FIRE_LOOP, ...FIRE_FUEL_LOOPS, SMOKE_HISS_LOOP] };
16983
17239
 
16984
17240
  // src/audio/sound.ts
16985
17241
  var IMPACT_SOUND_KEYS = {