@adatechnology/game-destruction 0.12.0 → 0.13.1
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.d.ts +7 -0
- package/dist/index.global.js +160 -18
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +160 -18
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -452,6 +452,11 @@ interface DestructionWorld {
|
|
|
452
452
|
* that target, and a `blast` event sums it up.
|
|
453
453
|
*/
|
|
454
454
|
explode(point: Vec3, energy: number, radius: number, source?: string, options?: BlastOptions): HitResult[];
|
|
455
|
+
/** Cost of the last `explode`: parts in range of the broad phase (`partsTested`) and those whose bounding sphere the blast reached (`partsNear`). */
|
|
456
|
+
blastProbe(): {
|
|
457
|
+
partsTested: number;
|
|
458
|
+
partsNear: number;
|
|
459
|
+
};
|
|
455
460
|
/** Raycast against every part shape (world space). */
|
|
456
461
|
raycast(origin: Vec3, direction: Vec3, maxDistance?: number): RayHit | null;
|
|
457
462
|
/**
|
|
@@ -550,6 +555,8 @@ interface PartRuntime {
|
|
|
550
555
|
breach?: Array<[number, number, number, number]>;
|
|
551
556
|
/** Energy each unit of the part took so far (`breach.chipEnergy`), by its index in the unit grid; -1 when the unit came out. */
|
|
552
557
|
chips?: Record<number, number>;
|
|
558
|
+
/** Face area (m²) the craters of shots took out of each unit so far, by its index in the unit grid: the rest of a unit comes out past `CRATER_SPENT`. */
|
|
559
|
+
craters?: Record<number, number>;
|
|
553
560
|
/** Mass in kg that left the part in a breach. */
|
|
554
561
|
removed?: number;
|
|
555
562
|
/** Damage taken by each band across a long wooden part (`splinter`), by band index from its negative end: the band that runs out breaks it there. */
|
package/dist/index.global.js
CHANGED
|
@@ -2967,6 +2967,7 @@ var GameDestruction = (() => {
|
|
|
2967
2967
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
2968
2968
|
var VENT_DIAMETER = 8e-3;
|
|
2969
2969
|
var FLAME_LIGHTS_LEAK_C = 470;
|
|
2970
|
+
var RESTING_HOLD_COS = Math.cos(Math.PI / 6);
|
|
2970
2971
|
var BREACH_RADIUS = 0.55;
|
|
2971
2972
|
var BREACH_BLAST_FACTOR = 6;
|
|
2972
2973
|
var BREACH_AREA = 0.25;
|
|
@@ -2982,6 +2983,8 @@ var GameDestruction = (() => {
|
|
|
2982
2983
|
var BREACH_GRID_CAP = 4096;
|
|
2983
2984
|
var CHIP_ENERGY = 200;
|
|
2984
2985
|
var CHIP_SPLASH = 0.5;
|
|
2986
|
+
var CRATER_SPENT = 0.6;
|
|
2987
|
+
var EDGE_REACH = 1.5;
|
|
2985
2988
|
var BAR_BOW = 0.08;
|
|
2986
2989
|
var BULLET_JOINT_SPAN = 0.3;
|
|
2987
2990
|
var CUT_SLICE = 1;
|
|
@@ -3002,12 +3005,22 @@ var GameDestruction = (() => {
|
|
|
3002
3005
|
const cells = p.st.chipped;
|
|
3003
3006
|
if (!cells?.length) return 0;
|
|
3004
3007
|
const [u, v] = planeAxes(p.thin);
|
|
3005
|
-
const
|
|
3008
|
+
const role = p.spec.structure?.role;
|
|
3009
|
+
const up = rotateVec(p.q, [0, 0, 0].map((_, k) => k === v ? 1 : 0));
|
|
3010
|
+
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;
|
|
3011
|
+
const along = (role === "wall" || role === "partition" || role === "column") && upright >= 0 ? upright : p.dims[u] >= p.dims[v] ? u : v;
|
|
3006
3012
|
const across = along === u ? v : u;
|
|
3007
3013
|
let worst = 0;
|
|
3008
3014
|
for (const c of cells) {
|
|
3009
|
-
|
|
3010
|
-
for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6)
|
|
3015
|
+
const spans = [];
|
|
3016
|
+
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]);
|
|
3017
|
+
spans.sort((a, b) => a[0] - b[0]);
|
|
3018
|
+
let gone = 0, end = -Infinity;
|
|
3019
|
+
for (const [a, b] of spans) {
|
|
3020
|
+
if (b <= end) continue;
|
|
3021
|
+
gone += b - Math.max(a, end);
|
|
3022
|
+
end = b;
|
|
3023
|
+
}
|
|
3011
3024
|
worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
|
|
3012
3025
|
}
|
|
3013
3026
|
return Math.min(1, worst);
|
|
@@ -3303,7 +3316,18 @@ var GameDestruction = (() => {
|
|
|
3303
3316
|
if (k < 0 || j.spec.kind !== "tie") return k;
|
|
3304
3317
|
return partXf(o, o.parts[k]).c[1] > partXf(o, o.parts[r]).c[1] + 1e-6 ? k : -1;
|
|
3305
3318
|
};
|
|
3319
|
+
let poseVersion = 0;
|
|
3320
|
+
const contactMemo = /* @__PURE__ */ new WeakMap();
|
|
3306
3321
|
const contactBox = (p, support) => {
|
|
3322
|
+
let byTouching = contactMemo.get(p);
|
|
3323
|
+
if (!byTouching) contactMemo.set(p, byTouching = /* @__PURE__ */ new Map());
|
|
3324
|
+
const known = byTouching.get(support);
|
|
3325
|
+
if (known && known.version === poseVersion) return known.box;
|
|
3326
|
+
const box = measureContact(p, support);
|
|
3327
|
+
byTouching.set(support, { version: poseVersion, box });
|
|
3328
|
+
return box;
|
|
3329
|
+
};
|
|
3330
|
+
const measureContact = (p, support) => {
|
|
3307
3331
|
const A = aabbOf(p.spec.shape, p.pos, p.q), B = aabbOf(support.spec.shape, support.pos, support.q);
|
|
3308
3332
|
const lo = [0, 0, 0], hi = [0, 0, 0];
|
|
3309
3333
|
for (let k = 0; k < 3; k++) {
|
|
@@ -4098,7 +4122,7 @@ var GameDestruction = (() => {
|
|
|
4098
4122
|
p.st.holes.push(hole);
|
|
4099
4123
|
if (p.st.holes.length > maxHoles) p.st.holes.shift();
|
|
4100
4124
|
markDamaged(p);
|
|
4101
|
-
emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
|
|
4125
|
+
emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {}, ...p.spec.powder ? { powder: { ...p.spec.powder } } : {} });
|
|
4102
4126
|
setStage(o, p, 1, {}, src);
|
|
4103
4127
|
return hole;
|
|
4104
4128
|
};
|
|
@@ -4115,6 +4139,22 @@ var GameDestruction = (() => {
|
|
|
4115
4139
|
const detachEvents = (o, opened, by, src) => {
|
|
4116
4140
|
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]) });
|
|
4117
4141
|
};
|
|
4142
|
+
const slideOffResting = (o, rotation) => {
|
|
4143
|
+
const upY = 1 - 2 * (rotation[0] * rotation[0] + rotation[2] * rotation[2]);
|
|
4144
|
+
if (upY >= RESTING_HOLD_COS) return;
|
|
4145
|
+
const opened = o.joints.filter((j) => j.spec.kind === "resting" && !j.st.open);
|
|
4146
|
+
if (opened.length === 0) return;
|
|
4147
|
+
run(() => {
|
|
4148
|
+
for (const j of opened) {
|
|
4149
|
+
j.st.open = true;
|
|
4150
|
+
j.st.openedBy = "break";
|
|
4151
|
+
}
|
|
4152
|
+
solveStructure(o);
|
|
4153
|
+
recomputeGroups(o);
|
|
4154
|
+
detachEvents(o, opened, "break");
|
|
4155
|
+
collapseCheck(o);
|
|
4156
|
+
});
|
|
4157
|
+
};
|
|
4118
4158
|
const checkDestroyed = (o, src) => {
|
|
4119
4159
|
if (!o.st.destroyed && o.parts.length > 0 && o.parts.every(isLost)) {
|
|
4120
4160
|
o.st.destroyed = true;
|
|
@@ -4215,7 +4255,7 @@ var GameDestruction = (() => {
|
|
|
4215
4255
|
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 }));
|
|
4216
4256
|
const unitsOf = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
|
|
4217
4257
|
const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
|
|
4218
|
-
const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
|
|
4258
|
+
const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag, inner = false) => {
|
|
4219
4259
|
const { c, q } = partXf(o, p);
|
|
4220
4260
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
4221
4261
|
const [u, v] = planeAxes(p.thin);
|
|
@@ -4239,7 +4279,8 @@ var GameDestruction = (() => {
|
|
|
4239
4279
|
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
4240
4280
|
angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
|
|
4241
4281
|
mass: mass * faceArea(cell) / total,
|
|
4242
|
-
...marks.length > 0 ? { marks } : {}
|
|
4282
|
+
...marks.length > 0 ? { marks } : {},
|
|
4283
|
+
...inner ? { inner } : {}
|
|
4243
4284
|
};
|
|
4244
4285
|
});
|
|
4245
4286
|
};
|
|
@@ -4280,7 +4321,7 @@ var GameDestruction = (() => {
|
|
|
4280
4321
|
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);
|
|
4281
4322
|
};
|
|
4282
4323
|
const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
|
|
4283
|
-
var _a, _b;
|
|
4324
|
+
var _a, _b, _c;
|
|
4284
4325
|
if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
4285
4326
|
const rule = p.mat.hits?.[kind];
|
|
4286
4327
|
const perKg = rule?.unitJoules ?? chipEnergy;
|
|
@@ -4290,7 +4331,9 @@ var GameDestruction = (() => {
|
|
|
4290
4331
|
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
4291
4332
|
const grid = gridOf(p);
|
|
4292
4333
|
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
4334
|
+
const cut = (_b = p.rt).craters ?? (_b.craters = {});
|
|
4293
4335
|
const out = [];
|
|
4336
|
+
const shot = kind === "ballistic";
|
|
4294
4337
|
for (let i = 0; i < grid.length; i++) {
|
|
4295
4338
|
const cell = grid[i];
|
|
4296
4339
|
if (worn[i] === -1) continue;
|
|
@@ -4303,18 +4346,42 @@ var GameDestruction = (() => {
|
|
|
4303
4346
|
worn[i] = total;
|
|
4304
4347
|
continue;
|
|
4305
4348
|
}
|
|
4306
|
-
|
|
4307
|
-
|
|
4349
|
+
if (!shot) {
|
|
4350
|
+
worn[i] = -1;
|
|
4351
|
+
out.push(cell);
|
|
4352
|
+
continue;
|
|
4353
|
+
}
|
|
4354
|
+
const size = [cell.size[0], cell.size[1], cell.size[2]];
|
|
4355
|
+
const center = [cell.center[0], cell.center[1], cell.center[2]];
|
|
4356
|
+
for (const a of [u, v]) {
|
|
4357
|
+
const w = Math.min(cell.size[a], craterAcross(hitEnergy) * (0.85 + 0.3 * rnd()));
|
|
4358
|
+
const lo = cell.center[a] - cell.size[a] / 2 + w / 2, hi = cell.center[a] + cell.size[a] / 2 - w / 2;
|
|
4359
|
+
size[a] = w;
|
|
4360
|
+
center[a] = clamp2(lp[a], lo, hi);
|
|
4361
|
+
const [from, to] = tornToEdge({ p, cell, a, b: a === u ? v : u, at: lp[a], crater: [center[a] - w / 2, center[a] + w / 2], reach: w * EDGE_REACH, gone: (k) => worn[k] === -1 });
|
|
4362
|
+
size[a] = to - from;
|
|
4363
|
+
center[a] = (from + to) / 2;
|
|
4364
|
+
}
|
|
4365
|
+
const taken2 = (cut[i] ?? 0) + size[u] * size[v];
|
|
4366
|
+
if (taken2 >= cell.size[u] * cell.size[v] * CRATER_SPENT) {
|
|
4367
|
+
worn[i] = -1;
|
|
4368
|
+
out.push({ ...cell, crater: true });
|
|
4369
|
+
continue;
|
|
4370
|
+
}
|
|
4371
|
+
worn[i] = 0;
|
|
4372
|
+
cut[i] = taken2;
|
|
4373
|
+
out.push({ center, size, crater: true });
|
|
4308
4374
|
}
|
|
4309
4375
|
if (out.length === 0) return;
|
|
4310
4376
|
const { c, q } = partXf(o, p);
|
|
4311
|
-
const chipped = (
|
|
4377
|
+
const chipped = (_c = p.st).chipped ?? (_c.chipped = []);
|
|
4312
4378
|
const holes = localHolesOf(p);
|
|
4313
4379
|
for (const cell of out) {
|
|
4314
4380
|
const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
|
|
4315
4381
|
const center = toWorld(c, q, cell.center);
|
|
4316
4382
|
const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
|
|
4317
|
-
const
|
|
4383
|
+
const tag = `c${chipped.length}_`;
|
|
4384
|
+
const fragments = cell.crater ? craterFragments(o, p, cell, dir, speed, mass, tag) : cellFragments(o, p, [cell], wp, dir, speed, mass, tag, true);
|
|
4318
4385
|
p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
|
|
4319
4386
|
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
4320
4387
|
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
@@ -4331,6 +4398,57 @@ var GameDestruction = (() => {
|
|
|
4331
4398
|
collapseCheck(o, src);
|
|
4332
4399
|
}
|
|
4333
4400
|
};
|
|
4401
|
+
const tornToEdge = (o) => {
|
|
4402
|
+
const { p, cell, a, b, at, crater: crater2, reach, gone } = o;
|
|
4403
|
+
const half = p.dims[a] / 2;
|
|
4404
|
+
const lo = cell.center[a] - cell.size[a] / 2, hi = cell.center[a] + cell.size[a] / 2;
|
|
4405
|
+
const free = (side, sign) => {
|
|
4406
|
+
if (sign > 0 ? side >= half - 1e-6 : side <= -half + 1e-6) return sign > 0 || a !== planeAxes(p.thin)[1];
|
|
4407
|
+
const probe = side + sign * 1e-3;
|
|
4408
|
+
return gridOf(p).some((c, k) => gone(k) && Math.abs(probe - c.center[a]) <= c.size[a] / 2 && Math.abs(cell.center[b] - c.center[b]) <= c.size[b] / 2);
|
|
4409
|
+
};
|
|
4410
|
+
if (at - lo <= reach && free(lo, -1)) return [lo, Math.max(crater2[1], lo)];
|
|
4411
|
+
if (hi - at <= reach && free(hi, 1)) return [Math.min(crater2[0], hi), hi];
|
|
4412
|
+
return crater2;
|
|
4413
|
+
};
|
|
4414
|
+
const craterAcross = (energy) => clamp2(0.03 + 12e-4 * Math.sqrt(Math.max(0, energy)), 0.03, 0.14);
|
|
4415
|
+
const craterFragments = (o, p, cell, dir, speed, mass, tag) => {
|
|
4416
|
+
const { c, q } = partXf(o, p);
|
|
4417
|
+
const [u, v] = planeAxes(p.thin);
|
|
4418
|
+
const d = dir ?? rotateVec(q, [p.thin === 0 ? 1 : 0, p.thin === 1 ? 1 : 0, p.thin === 2 ? 1 : 0]);
|
|
4419
|
+
const exitSign = Math.sign(rotateVec(quat.conjugate(q), d)[p.thin]) || 1;
|
|
4420
|
+
const bit = Math.max(0.01, Math.min(cell.size[u], cell.size[v]) / 3);
|
|
4421
|
+
const n = clamp2(Math.round(cell.size[u] * cell.size[v] / (bit * bit * 3)), 4, 10) + Math.floor(rnd() * 3);
|
|
4422
|
+
const out = [];
|
|
4423
|
+
for (let i = 0; i < n; i++) {
|
|
4424
|
+
const back = i % 3 === 2;
|
|
4425
|
+
const lc = [cell.center[0], cell.center[1], cell.center[2]];
|
|
4426
|
+
lc[u] += (rnd() - 0.5) * cell.size[u] * 0.7;
|
|
4427
|
+
lc[v] += (rnd() - 0.5) * cell.size[v] * 0.7;
|
|
4428
|
+
lc[p.thin] += (back ? -exitSign : exitSign) * (p.thickness / 2 + 0.01);
|
|
4429
|
+
const size = [0, 0, 0];
|
|
4430
|
+
size[u] = bit * (0.6 + 0.8 * rnd());
|
|
4431
|
+
size[v] = bit * (0.5 + 0.7 * rnd());
|
|
4432
|
+
size[p.thin] = bit * (0.4 + 0.6 * rnd());
|
|
4433
|
+
const spread3 = [(rnd() - 0.5) * 0.9, (rnd() - 0.3) * 0.9, (rnd() - 0.5) * 0.9];
|
|
4434
|
+
const k = speed * (back ? 0.4 + 0.4 * rnd() : 0.7 + 0.8 * rnd());
|
|
4435
|
+
const vdir = vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3));
|
|
4436
|
+
out.push({
|
|
4437
|
+
id: `${p.spec.id}#${tag}${i}`,
|
|
4438
|
+
part: p.spec.id,
|
|
4439
|
+
material: p.spec.material,
|
|
4440
|
+
shape: "chunk",
|
|
4441
|
+
center: toWorld(c, q, lc),
|
|
4442
|
+
size,
|
|
4443
|
+
rotation: q,
|
|
4444
|
+
velocity: [vdir[0] * k, vdir[1] * k + 0.5 * rnd(), vdir[2] * k],
|
|
4445
|
+
angularVelocity: [(rnd() - 0.5) * 20, (rnd() - 0.5) * 20, (rnd() - 0.5) * 20],
|
|
4446
|
+
mass: mass / n,
|
|
4447
|
+
inner: true
|
|
4448
|
+
});
|
|
4449
|
+
}
|
|
4450
|
+
return out;
|
|
4451
|
+
};
|
|
4334
4452
|
const exposeRebar = (o, p, src) => {
|
|
4335
4453
|
const r = p.spec.reinforcement;
|
|
4336
4454
|
if (!r) return;
|
|
@@ -4618,7 +4736,7 @@ var GameDestruction = (() => {
|
|
|
4618
4736
|
});
|
|
4619
4737
|
}
|
|
4620
4738
|
const patch = [scale * 4, scale * 4, scale * 4];
|
|
4621
|
-
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry, size: patch, rotation: q, fragments });
|
|
4739
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry, size: patch, rotation: q, fragments, torn: true });
|
|
4622
4740
|
};
|
|
4623
4741
|
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
|
|
4624
4742
|
const mat = p.mat;
|
|
@@ -4677,12 +4795,13 @@ var GameDestruction = (() => {
|
|
|
4677
4795
|
} else if (mat.ductility >= 0.5) {
|
|
4678
4796
|
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);
|
|
4679
4797
|
} else if (brittle) {
|
|
4680
|
-
addCrack(o, p, entry, n, crackLen, src);
|
|
4798
|
+
if (!p.spec.units) addCrack(o, p, entry, n, crackLen, src);
|
|
4681
4799
|
if (mat.fracture !== "shatter") addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
|
|
4682
4800
|
} else addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
|
|
4683
4801
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
4684
4802
|
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true, BULLET_JOINT_SPAN);
|
|
4685
|
-
const
|
|
4803
|
+
const hollowTear = mat.fracture === "tear" && p.spec.fill !== void 0 && p.spec.fill < 0.5;
|
|
4804
|
+
const absorbed = penetrated && (mat.ductility >= 0.5 && mat.fracture !== "tear" || hollowTear) ? Math.min(E, thr) : E;
|
|
4686
4805
|
const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry, d, absorbed * 0.1, 12, src);
|
|
4687
4806
|
if (!isLost(p)) tearAt(o, p, "ballistic", entry, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
|
|
4688
4807
|
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);
|
|
@@ -4767,10 +4886,25 @@ var GameDestruction = (() => {
|
|
|
4767
4886
|
if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
4768
4887
|
return reach;
|
|
4769
4888
|
};
|
|
4889
|
+
const blastProbe = { partsTested: 0, partsNear: 0 };
|
|
4890
|
+
const blastCandidates = (o, point, radius) => {
|
|
4891
|
+
const local = toLocal(o.st.position, o.st.rotation, point);
|
|
4892
|
+
const near = [];
|
|
4893
|
+
for (const p of o.parts) {
|
|
4894
|
+
const reach = radius + p.bound;
|
|
4895
|
+
const dx = local[0] - p.pos[0];
|
|
4896
|
+
const dy = local[1] - p.pos[1];
|
|
4897
|
+
const dz = local[2] - p.pos[2];
|
|
4898
|
+
if (dx * dx + dy * dy + dz * dz < reach * reach) near.push(p);
|
|
4899
|
+
}
|
|
4900
|
+
blastProbe.partsTested += o.parts.length;
|
|
4901
|
+
blastProbe.partsNear += near.length;
|
|
4902
|
+
return near;
|
|
4903
|
+
};
|
|
4770
4904
|
const blastObject = (o, point, energy, radius, src) => {
|
|
4771
4905
|
const start = cur.length;
|
|
4772
4906
|
const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
|
|
4773
|
-
for (const p of o
|
|
4907
|
+
for (const p of blastCandidates(o, point, radius)) blastPart(o, p, point, energy, radius, src, res);
|
|
4774
4908
|
res.events = cur.slice(start);
|
|
4775
4909
|
return res;
|
|
4776
4910
|
};
|
|
@@ -4894,7 +5028,7 @@ var GameDestruction = (() => {
|
|
|
4894
5028
|
let order = 0;
|
|
4895
5029
|
for (const o of inRange) {
|
|
4896
5030
|
if (only && only !== o) continue;
|
|
4897
|
-
for (const p of o
|
|
5031
|
+
for (const p of blastCandidates(o, point, radius)) {
|
|
4898
5032
|
if (isLost(p)) continue;
|
|
4899
5033
|
const dist = distToPart(o, p, point);
|
|
4900
5034
|
if (dist < radius) items.push({ dist, order: order++, o, p });
|
|
@@ -5441,6 +5575,7 @@ var GameDestruction = (() => {
|
|
|
5441
5575
|
const p = getPart(o, partId);
|
|
5442
5576
|
p.pos = [position[0], position[1], position[2]];
|
|
5443
5577
|
p.q = q;
|
|
5578
|
+
poseVersion++;
|
|
5444
5579
|
updateBound(o);
|
|
5445
5580
|
},
|
|
5446
5581
|
remove(id) {
|
|
@@ -5459,6 +5594,7 @@ var GameDestruction = (() => {
|
|
|
5459
5594
|
const o = getObj(id);
|
|
5460
5595
|
o.base = { pos: [position[0], position[1], position[2]], rot: [rotation[0], rotation[1], rotation[2], rotation[3]] };
|
|
5461
5596
|
applySoilPose(o);
|
|
5597
|
+
slideOffResting(o, rotation);
|
|
5462
5598
|
},
|
|
5463
5599
|
splitGroup(id, group, newId) {
|
|
5464
5600
|
if (impls.has(id)) throw new Error(`object "${id}" is a registered DestructibleImpl and cannot be split; its module owns its pieces`);
|
|
@@ -5482,7 +5618,7 @@ var GameDestruction = (() => {
|
|
|
5482
5618
|
if (o.spec.name !== void 0) s.name = o.spec.name;
|
|
5483
5619
|
if (o.spec.tags) s.tags = [...o.spec.tags];
|
|
5484
5620
|
if (o.spec.soil !== void 0) s.soil = o.spec.soil;
|
|
5485
|
-
return
|
|
5621
|
+
return s;
|
|
5486
5622
|
};
|
|
5487
5623
|
const inNew = (pid) => moved.has(pid);
|
|
5488
5624
|
const inOld = (pid) => !moved.has(pid);
|
|
@@ -5509,7 +5645,12 @@ var GameDestruction = (() => {
|
|
|
5509
5645
|
const p = getPart(o, partId);
|
|
5510
5646
|
return whole(() => doHit(o, h, p));
|
|
5511
5647
|
},
|
|
5648
|
+
blastProbe() {
|
|
5649
|
+
return { ...blastProbe };
|
|
5650
|
+
},
|
|
5512
5651
|
explode(point, energy, radius, source, blast) {
|
|
5652
|
+
blastProbe.partsTested = 0;
|
|
5653
|
+
blastProbe.partsNear = 0;
|
|
5513
5654
|
return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
|
|
5514
5655
|
},
|
|
5515
5656
|
raycast(origin, direction, maxDistance = Infinity) {
|
|
@@ -5519,7 +5660,8 @@ var GameDestruction = (() => {
|
|
|
5519
5660
|
sweepBox(box, direction, distance, options2 = {}) {
|
|
5520
5661
|
const d = vec.normalize(direction);
|
|
5521
5662
|
const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
|
|
5522
|
-
const
|
|
5663
|
+
const hosted = options2.ignore === void 0 || ignored ? void 0 : impls.get(options2.ignore);
|
|
5664
|
+
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;
|
|
5523
5665
|
let best = null;
|
|
5524
5666
|
for (const origin of sweepOrigins(box, d, options2.spacing ?? 0.2)) {
|
|
5525
5667
|
const r = raycastInternal(origin, d, distance + SWEEP_BACK, ignore);
|