@adatechnology/game-destruction 0.11.0 → 0.13.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
@@ -1864,6 +1864,7 @@ var materials_default = [
1864
1864
  },
1865
1865
  hitDebris: "chips",
1866
1866
  breakDebris: "dust",
1867
+ visual: { innerColor: 10197652 },
1867
1868
  thermal: {
1868
1869
  conductivity: 0.15,
1869
1870
  heatCapacity: 2.5
@@ -1894,6 +1895,7 @@ var materials_default = [
1894
1895
  ],
1895
1896
  hitDebris: "chips",
1896
1897
  breakDebris: "dust",
1898
+ visual: { innerColor: 12085567 },
1897
1899
  thermal: {
1898
1900
  conductivity: 0.15,
1899
1901
  heatCapacity: 2.5
@@ -1924,6 +1926,7 @@ var materials_default = [
1924
1926
  ],
1925
1927
  hitDebris: "dust",
1926
1928
  breakDebris: "dust",
1929
+ visual: { innerColor: 14999507 },
1927
1930
  thermal: {
1928
1931
  conductivity: 0.1,
1929
1932
  heatCapacity: 1.5
@@ -1949,6 +1952,7 @@ var materials_default = [
1949
1952
  ],
1950
1953
  hitDebris: "chips",
1951
1954
  breakDebris: "dust",
1955
+ visual: { innerColor: 9407364 },
1952
1956
  thermal: {
1953
1957
  conductivity: 0.2,
1954
1958
  heatCapacity: 2.5
@@ -2702,6 +2706,7 @@ var CONFINEMENT_DEFAULTS = { rays: 26, reach: 1.5, gain: 1.5, power: 2, max: 3 }
2702
2706
  var FLAMMABLE_FLUIDS = ["gas", "fuel"];
2703
2707
  var VENT_DIAMETER = 8e-3;
2704
2708
  var FLAME_LIGHTS_LEAK_C = 470;
2709
+ var RESTING_HOLD_COS = Math.cos(Math.PI / 6);
2705
2710
  var BREACH_RADIUS = 0.55;
2706
2711
  var BREACH_BLAST_FACTOR = 6;
2707
2712
  var BREACH_AREA = 0.25;
@@ -2717,7 +2722,9 @@ var UNIT_FRAGMENT_FACTOR = 4;
2717
2722
  var BREACH_GRID_CAP = 4096;
2718
2723
  var CHIP_ENERGY = 200;
2719
2724
  var CHIP_SPLASH = 0.5;
2725
+ var CRATER_SPENT = 0.6;
2720
2726
  var BAR_BOW = 0.08;
2727
+ var BULLET_JOINT_SPAN = 0.3;
2721
2728
  var CUT_SLICE = 1;
2722
2729
  var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
2723
2730
  var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -2736,12 +2743,22 @@ var sectionLoss = (p) => {
2736
2743
  const cells = p.st.chipped;
2737
2744
  if (!cells?.length) return 0;
2738
2745
  const [u, v] = planeAxes(p.thin);
2739
- const along = p.dims[u] >= p.dims[v] ? u : v;
2746
+ const role = p.spec.structure?.role;
2747
+ const up = rotateVec(p.q, [0, 0, 0].map((_, k) => k === v ? 1 : 0));
2748
+ const upright = Math.abs(up[1]) > 0.7 ? v : Math.abs(rotateVec(p.q, [0, 0, 0].map((_, k) => k === u ? 1 : 0))[1]) > 0.7 ? u : -1;
2749
+ const along = (role === "wall" || role === "partition" || role === "column") && upright >= 0 ? upright : p.dims[u] >= p.dims[v] ? u : v;
2740
2750
  const across = along === u ? v : u;
2741
2751
  let worst = 0;
2742
2752
  for (const c of cells) {
2743
- let gone = 0;
2744
- for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across];
2753
+ const spans = [];
2754
+ for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) spans.push([d.center[across] - d.size[across] / 2, d.center[across] + d.size[across] / 2]);
2755
+ spans.sort((a, b) => a[0] - b[0]);
2756
+ let gone = 0, end = -Infinity;
2757
+ for (const [a, b] of spans) {
2758
+ if (b <= end) continue;
2759
+ gone += b - Math.max(a, end);
2760
+ end = b;
2761
+ }
2745
2762
  worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
2746
2763
  }
2747
2764
  return Math.min(1, worst);
@@ -3037,7 +3054,18 @@ var createDestruction = (options = {}) => {
3037
3054
  if (k < 0 || j.spec.kind !== "tie") return k;
3038
3055
  return partXf(o, o.parts[k]).c[1] > partXf(o, o.parts[r]).c[1] + 1e-6 ? k : -1;
3039
3056
  };
3057
+ let poseVersion = 0;
3058
+ const contactMemo = /* @__PURE__ */ new WeakMap();
3040
3059
  const contactBox = (p, support) => {
3060
+ let byTouching = contactMemo.get(p);
3061
+ if (!byTouching) contactMemo.set(p, byTouching = /* @__PURE__ */ new Map());
3062
+ const known = byTouching.get(support);
3063
+ if (known && known.version === poseVersion) return known.box;
3064
+ const box = measureContact(p, support);
3065
+ byTouching.set(support, { version: poseVersion, box });
3066
+ return box;
3067
+ };
3068
+ const measureContact = (p, support) => {
3041
3069
  const A = aabbOf(p.spec.shape, p.pos, p.q), B = aabbOf(support.spec.shape, support.pos, support.q);
3042
3070
  const lo = [0, 0, 0], hi = [0, 0, 0];
3043
3071
  for (let k = 0; k < 3; k++) {
@@ -3832,7 +3860,7 @@ var createDestruction = (options = {}) => {
3832
3860
  p.st.holes.push(hole);
3833
3861
  if (p.st.holes.length > maxHoles) p.st.holes.shift();
3834
3862
  markDamaged(p);
3835
- emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
3863
+ emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {}, ...p.spec.powder ? { powder: { ...p.spec.powder } } : {} });
3836
3864
  setStage(o, p, 1, {}, src);
3837
3865
  return hole;
3838
3866
  };
@@ -3844,11 +3872,27 @@ var createDestruction = (options = {}) => {
3844
3872
  emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
3845
3873
  setStage(o, p, 1, {}, src);
3846
3874
  };
3847
- const debris = (o, p, kind, point, normal, count, speed, src) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed });
3875
+ const debris = (o, p, kind, point, normal, count, speed, src, material) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed, ...material && material !== p.spec.material ? { material } : {} });
3848
3876
  const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
3849
3877
  const detachEvents = (o, opened, by, src) => {
3850
3878
  for (const j of opened) emit({ ...base(o, src), type: "detach", joint: j.spec.id, parts: [j.spec.a, j.spec.b], by, groups: o.st.groups.map((g) => [...g]) });
3851
3879
  };
3880
+ const slideOffResting = (o, rotation) => {
3881
+ const upY = 1 - 2 * (rotation[0] * rotation[0] + rotation[2] * rotation[2]);
3882
+ if (upY >= RESTING_HOLD_COS) return;
3883
+ const opened = o.joints.filter((j) => j.spec.kind === "resting" && !j.st.open);
3884
+ if (opened.length === 0) return;
3885
+ run(() => {
3886
+ for (const j of opened) {
3887
+ j.st.open = true;
3888
+ j.st.openedBy = "break";
3889
+ }
3890
+ solveStructure(o);
3891
+ recomputeGroups(o);
3892
+ detachEvents(o, opened, "break");
3893
+ collapseCheck(o);
3894
+ });
3895
+ };
3852
3896
  const checkDestroyed = (o, src) => {
3853
3897
  if (!o.st.destroyed && o.parts.length > 0 && o.parts.every(isLost)) {
3854
3898
  o.st.destroyed = true;
@@ -3885,7 +3929,13 @@ var createDestruction = (options = {}) => {
3885
3929
  const small = pa.dims[0] * pa.dims[1] * pa.dims[2] <= pb.dims[0] * pb.dims[1] * pb.dims[2] ? pa : pb;
3886
3930
  return Math.max(0.06, 0.5 * Math.min(small.dims[0], small.dims[1], small.dims[2]));
3887
3931
  };
3888
- const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false) => {
3932
+ const jointLength = (o, j) => {
3933
+ const A = partWorldAabb(o, o.parts[j.ia]), B = partWorldAabb(o, o.parts[j.ib]);
3934
+ let len = 0;
3935
+ for (let k = 0; k < 3; k++) len = Math.max(len, Math.min(A.max[k], B.max[k]) - Math.max(A.min[k], B.min[k]));
3936
+ return len;
3937
+ };
3938
+ const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false, span) => {
3889
3939
  if (amount <= 0) return;
3890
3940
  const mine = (o.byPart.get(p.spec.id) ?? []).filter((j) => !j.st.open);
3891
3941
  if (split && mine.length) amount /= mine.length;
@@ -3893,7 +3943,7 @@ var createDestruction = (options = {}) => {
3893
3943
  if (j.st.open) continue;
3894
3944
  if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
3895
3945
  if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
3896
- j.st.hp -= amount;
3946
+ j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
3897
3947
  if (j.st.hp <= 0) {
3898
3948
  j.st.hp = 0;
3899
3949
  j.st.open = true;
@@ -3922,7 +3972,7 @@ var createDestruction = (options = {}) => {
3922
3972
  if (spalls.length > MAX_SPALLS) spalls.shift();
3923
3973
  markDamaged(p);
3924
3974
  emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
3925
- debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
3975
+ debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src, layer.mat.id);
3926
3976
  setStage(o, p, 1, {}, src);
3927
3977
  };
3928
3978
  const crater = (o, p, wp, normal, radius, src) => {
@@ -3943,7 +3993,7 @@ var createDestruction = (options = {}) => {
3943
3993
  const localHolesOf = (p) => p.st.holes.map((h) => ({ point: toLocal(p.pos, p.q, h.point), normal: rotateVec(quat.conjugate(p.q), h.normal), diameter: h.diameter }));
3944
3994
  const unitsOf = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
3945
3995
  const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
3946
- const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
3996
+ const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag, inner = false) => {
3947
3997
  const { c, q } = partXf(o, p);
3948
3998
  const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
3949
3999
  const [u, v] = planeAxes(p.thin);
@@ -3967,7 +4017,8 @@ var createDestruction = (options = {}) => {
3967
4017
  velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
3968
4018
  angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
3969
4019
  mass: mass * faceArea(cell) / total,
3970
- ...marks.length > 0 ? { marks } : {}
4020
+ ...marks.length > 0 ? { marks } : {},
4021
+ ...inner ? { inner } : {}
3971
4022
  };
3972
4023
  });
3973
4024
  };
@@ -4008,7 +4059,7 @@ var createDestruction = (options = {}) => {
4008
4059
  return cells.some((c) => Math.abs(lp[u] - c.center[u]) <= c.size[u] / 2 && Math.abs(lp[v] - c.center[v]) <= c.size[v] / 2);
4009
4060
  };
4010
4061
  const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
4011
- var _a, _b;
4062
+ var _a, _b, _c;
4012
4063
  if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
4013
4064
  const rule = p.mat.hits?.[kind];
4014
4065
  const perKg = rule?.unitJoules ?? chipEnergy;
@@ -4018,7 +4069,9 @@ var createDestruction = (options = {}) => {
4018
4069
  const lp = toLocal(p.pos, p.q, objLocal(o, wp));
4019
4070
  const grid = gridOf(p);
4020
4071
  const worn = (_a = p.rt).chips ?? (_a.chips = {});
4072
+ const cut = (_b = p.rt).craters ?? (_b.craters = {});
4021
4073
  const out = [];
4074
+ const shot = kind === "ballistic";
4022
4075
  for (let i = 0; i < grid.length; i++) {
4023
4076
  const cell = grid[i];
4024
4077
  if (worn[i] === -1) continue;
@@ -4031,18 +4084,39 @@ var createDestruction = (options = {}) => {
4031
4084
  worn[i] = total;
4032
4085
  continue;
4033
4086
  }
4034
- worn[i] = -1;
4035
- out.push(cell);
4087
+ if (!shot) {
4088
+ worn[i] = -1;
4089
+ out.push(cell);
4090
+ continue;
4091
+ }
4092
+ const size = [cell.size[0], cell.size[1], cell.size[2]];
4093
+ const center = [cell.center[0], cell.center[1], cell.center[2]];
4094
+ for (const a of [u, v]) {
4095
+ const w = Math.min(cell.size[a], craterAcross(hitEnergy) * (0.85 + 0.3 * rnd()));
4096
+ const lo = cell.center[a] - cell.size[a] / 2 + w / 2, hi = cell.center[a] + cell.size[a] / 2 - w / 2;
4097
+ size[a] = w;
4098
+ center[a] = clamp2(lp[a], lo, hi);
4099
+ }
4100
+ const taken2 = (cut[i] ?? 0) + size[u] * size[v];
4101
+ if (taken2 >= cell.size[u] * cell.size[v] * CRATER_SPENT) {
4102
+ worn[i] = -1;
4103
+ out.push({ ...cell, crater: true });
4104
+ continue;
4105
+ }
4106
+ worn[i] = 0;
4107
+ cut[i] = taken2;
4108
+ out.push({ center, size, crater: true });
4036
4109
  }
4037
4110
  if (out.length === 0) return;
4038
4111
  const { c, q } = partXf(o, p);
4039
- const chipped = (_b = p.st).chipped ?? (_b.chipped = []);
4112
+ const chipped = (_c = p.st).chipped ?? (_c.chipped = []);
4040
4113
  const holes = localHolesOf(p);
4041
4114
  for (const cell of out) {
4042
4115
  const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
4043
4116
  const center = toWorld(c, q, cell.center);
4044
4117
  const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
4045
- const fragments = cellFragments(o, p, [cell], wp, dir, speed, mass, `c${chipped.length}_`);
4118
+ const tag = `c${chipped.length}_`;
4119
+ const fragments = cell.crater ? craterFragments(o, p, cell, dir, speed, mass, tag) : cellFragments(o, p, [cell], wp, dir, speed, mass, tag, true);
4046
4120
  p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
4047
4121
  chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
4048
4122
  p.st.mass = Math.max(0, p.st.mass - mass);
@@ -4059,6 +4133,44 @@ var createDestruction = (options = {}) => {
4059
4133
  collapseCheck(o, src);
4060
4134
  }
4061
4135
  };
4136
+ const craterAcross = (energy) => clamp2(0.03 + 12e-4 * Math.sqrt(Math.max(0, energy)), 0.03, 0.14);
4137
+ const craterFragments = (o, p, cell, dir, speed, mass, tag) => {
4138
+ const { c, q } = partXf(o, p);
4139
+ const [u, v] = planeAxes(p.thin);
4140
+ const d = dir ?? rotateVec(q, [p.thin === 0 ? 1 : 0, p.thin === 1 ? 1 : 0, p.thin === 2 ? 1 : 0]);
4141
+ const exitSign = Math.sign(rotateVec(quat.conjugate(q), d)[p.thin]) || 1;
4142
+ const bit = Math.max(0.01, Math.min(cell.size[u], cell.size[v]) / 3);
4143
+ const n = clamp2(Math.round(cell.size[u] * cell.size[v] / (bit * bit * 3)), 4, 10) + Math.floor(rnd() * 3);
4144
+ const out = [];
4145
+ for (let i = 0; i < n; i++) {
4146
+ const back = i % 3 === 2;
4147
+ const lc = [cell.center[0], cell.center[1], cell.center[2]];
4148
+ lc[u] += (rnd() - 0.5) * cell.size[u] * 0.7;
4149
+ lc[v] += (rnd() - 0.5) * cell.size[v] * 0.7;
4150
+ lc[p.thin] += (back ? -exitSign : exitSign) * (p.thickness / 2 + 0.01);
4151
+ const size = [0, 0, 0];
4152
+ size[u] = bit * (0.6 + 0.8 * rnd());
4153
+ size[v] = bit * (0.5 + 0.7 * rnd());
4154
+ size[p.thin] = bit * (0.4 + 0.6 * rnd());
4155
+ const spread3 = [(rnd() - 0.5) * 0.9, (rnd() - 0.3) * 0.9, (rnd() - 0.5) * 0.9];
4156
+ const k = speed * (back ? 0.4 + 0.4 * rnd() : 0.7 + 0.8 * rnd());
4157
+ const vdir = vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3));
4158
+ out.push({
4159
+ id: `${p.spec.id}#${tag}${i}`,
4160
+ part: p.spec.id,
4161
+ material: p.spec.material,
4162
+ shape: "chunk",
4163
+ center: toWorld(c, q, lc),
4164
+ size,
4165
+ rotation: q,
4166
+ velocity: [vdir[0] * k, vdir[1] * k + 0.5 * rnd(), vdir[2] * k],
4167
+ angularVelocity: [(rnd() - 0.5) * 20, (rnd() - 0.5) * 20, (rnd() - 0.5) * 20],
4168
+ mass: mass / n,
4169
+ inner: true
4170
+ });
4171
+ }
4172
+ return out;
4173
+ };
4062
4174
  const exposeRebar = (o, p, src) => {
4063
4175
  const r = p.spec.reinforcement;
4064
4176
  if (!r) return;
@@ -4346,7 +4458,7 @@ var createDestruction = (options = {}) => {
4346
4458
  });
4347
4459
  }
4348
4460
  const patch = [scale * 4, scale * 4, scale * 4];
4349
- emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry, size: patch, rotation: q, fragments });
4461
+ emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry, size: patch, rotation: q, fragments, torn: true });
4350
4462
  };
4351
4463
  const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
4352
4464
  const mat = p.mat;
@@ -4405,12 +4517,13 @@ var createDestruction = (options = {}) => {
4405
4517
  } else if (mat.ductility >= 0.5) {
4406
4518
  addDent(o, p, entry, n, Math.max(caliber * 3 / 1e3, 0.012), clamp2(Math.min(Math.max(p.thickness * 3, 3e-3), ratio * 0.01), 1e-3, 0.05), src);
4407
4519
  } else if (brittle) {
4408
- addCrack(o, p, entry, n, crackLen, src);
4520
+ if (!p.spec.units) addCrack(o, p, entry, n, crackLen, src);
4409
4521
  if (mat.fracture !== "shatter") addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
4410
4522
  } else addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
4411
4523
  debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
4412
- wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
4413
- const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
4524
+ wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true, BULLET_JOINT_SPAN);
4525
+ const hollowTear = mat.fracture === "tear" && p.spec.fill !== void 0 && p.spec.fill < 0.5;
4526
+ const absorbed = penetrated && (mat.ductility >= 0.5 && mat.fracture !== "tear" || hollowTear) ? Math.min(E, thr) : E;
4414
4527
  const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry, d, absorbed * 0.1, 12, src);
4415
4528
  if (!isLost(p)) tearAt(o, p, "ballistic", entry, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
4416
4529
  chipUnits(o, p, "ballistic", entry, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
@@ -4495,10 +4608,25 @@ var createDestruction = (options = {}) => {
4495
4608
  if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
4496
4609
  return reach;
4497
4610
  };
4611
+ const blastProbe = { partsTested: 0, partsNear: 0 };
4612
+ const blastCandidates = (o, point, radius) => {
4613
+ const local = toLocal(o.st.position, o.st.rotation, point);
4614
+ const near = [];
4615
+ for (const p of o.parts) {
4616
+ const reach = radius + p.bound;
4617
+ const dx = local[0] - p.pos[0];
4618
+ const dy = local[1] - p.pos[1];
4619
+ const dz = local[2] - p.pos[2];
4620
+ if (dx * dx + dy * dy + dz * dz < reach * reach) near.push(p);
4621
+ }
4622
+ blastProbe.partsTested += o.parts.length;
4623
+ blastProbe.partsNear += near.length;
4624
+ return near;
4625
+ };
4498
4626
  const blastObject = (o, point, energy, radius, src) => {
4499
4627
  const start = cur.length;
4500
4628
  const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
4501
- for (const p of o.parts) blastPart(o, p, point, energy, radius, src, res);
4629
+ for (const p of blastCandidates(o, point, radius)) blastPart(o, p, point, energy, radius, src, res);
4502
4630
  res.events = cur.slice(start);
4503
4631
  return res;
4504
4632
  };
@@ -4622,7 +4750,7 @@ var createDestruction = (options = {}) => {
4622
4750
  let order = 0;
4623
4751
  for (const o of inRange) {
4624
4752
  if (only && only !== o) continue;
4625
- for (const p of o.parts) {
4753
+ for (const p of blastCandidates(o, point, radius)) {
4626
4754
  if (isLost(p)) continue;
4627
4755
  const dist = distToPart(o, p, point);
4628
4756
  if (dist < radius) items.push({ dist, order: order++, o, p });
@@ -5169,6 +5297,7 @@ var createDestruction = (options = {}) => {
5169
5297
  const p = getPart(o, partId);
5170
5298
  p.pos = [position[0], position[1], position[2]];
5171
5299
  p.q = q;
5300
+ poseVersion++;
5172
5301
  updateBound(o);
5173
5302
  },
5174
5303
  remove(id) {
@@ -5187,6 +5316,7 @@ var createDestruction = (options = {}) => {
5187
5316
  const o = getObj(id);
5188
5317
  o.base = { pos: [position[0], position[1], position[2]], rot: [rotation[0], rotation[1], rotation[2], rotation[3]] };
5189
5318
  applySoilPose(o);
5319
+ slideOffResting(o, rotation);
5190
5320
  },
5191
5321
  splitGroup(id, group, newId) {
5192
5322
  if (impls.has(id)) throw new Error(`object "${id}" is a registered DestructibleImpl and cannot be split; its module owns its pieces`);
@@ -5210,7 +5340,7 @@ var createDestruction = (options = {}) => {
5210
5340
  if (o.spec.name !== void 0) s.name = o.spec.name;
5211
5341
  if (o.spec.tags) s.tags = [...o.spec.tags];
5212
5342
  if (o.spec.soil !== void 0) s.soil = o.spec.soil;
5213
- return clone(s);
5343
+ return s;
5214
5344
  };
5215
5345
  const inNew = (pid) => moved.has(pid);
5216
5346
  const inOld = (pid) => !moved.has(pid);
@@ -5237,7 +5367,12 @@ var createDestruction = (options = {}) => {
5237
5367
  const p = getPart(o, partId);
5238
5368
  return whole(() => doHit(o, h, p));
5239
5369
  },
5370
+ blastProbe() {
5371
+ return { ...blastProbe };
5372
+ },
5240
5373
  explode(point, energy, radius, source, blast) {
5374
+ blastProbe.partsTested = 0;
5375
+ blastProbe.partsNear = 0;
5241
5376
  return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
5242
5377
  },
5243
5378
  raycast(origin, direction, maxDistance = Infinity) {
@@ -5247,7 +5382,8 @@ var createDestruction = (options = {}) => {
5247
5382
  sweepBox(box, direction, distance, options2 = {}) {
5248
5383
  const d = vec.normalize(direction);
5249
5384
  const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
5250
- const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
5385
+ const hosted = options2.ignore === void 0 || ignored ? void 0 : impls.get(options2.ignore);
5386
+ const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : hosted ? new Set(hosted.impl.describe().parts.map((p) => `${hosted.impl.id}\0${p.id}`)) : void 0;
5251
5387
  let best = null;
5252
5388
  for (const origin of sweepOrigins(box, d, options2.spacing ?? 0.2)) {
5253
5389
  const r = raycastInternal(origin, d, distance + SWEEP_BACK, ignore);