@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.d.ts +7 -0
- package/dist/index.global.js +159 -23
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +159 -23
- 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
|
@@ -1974,6 +1974,7 @@ var GameDestruction = (() => {
|
|
|
1974
1974
|
},
|
|
1975
1975
|
hitDebris: "chips",
|
|
1976
1976
|
breakDebris: "dust",
|
|
1977
|
+
visual: { innerColor: 10197652 },
|
|
1977
1978
|
thermal: {
|
|
1978
1979
|
conductivity: 0.15,
|
|
1979
1980
|
heatCapacity: 2.5
|
|
@@ -2004,6 +2005,7 @@ var GameDestruction = (() => {
|
|
|
2004
2005
|
],
|
|
2005
2006
|
hitDebris: "chips",
|
|
2006
2007
|
breakDebris: "dust",
|
|
2008
|
+
visual: { innerColor: 12085567 },
|
|
2007
2009
|
thermal: {
|
|
2008
2010
|
conductivity: 0.15,
|
|
2009
2011
|
heatCapacity: 2.5
|
|
@@ -2034,6 +2036,7 @@ var GameDestruction = (() => {
|
|
|
2034
2036
|
],
|
|
2035
2037
|
hitDebris: "dust",
|
|
2036
2038
|
breakDebris: "dust",
|
|
2039
|
+
visual: { innerColor: 14999507 },
|
|
2037
2040
|
thermal: {
|
|
2038
2041
|
conductivity: 0.1,
|
|
2039
2042
|
heatCapacity: 1.5
|
|
@@ -2059,6 +2062,7 @@ var GameDestruction = (() => {
|
|
|
2059
2062
|
],
|
|
2060
2063
|
hitDebris: "chips",
|
|
2061
2064
|
breakDebris: "dust",
|
|
2065
|
+
visual: { innerColor: 9407364 },
|
|
2062
2066
|
thermal: {
|
|
2063
2067
|
conductivity: 0.2,
|
|
2064
2068
|
heatCapacity: 2.5
|
|
@@ -2963,6 +2967,7 @@ var GameDestruction = (() => {
|
|
|
2963
2967
|
var FLAMMABLE_FLUIDS = ["gas", "fuel"];
|
|
2964
2968
|
var VENT_DIAMETER = 8e-3;
|
|
2965
2969
|
var FLAME_LIGHTS_LEAK_C = 470;
|
|
2970
|
+
var RESTING_HOLD_COS = Math.cos(Math.PI / 6);
|
|
2966
2971
|
var BREACH_RADIUS = 0.55;
|
|
2967
2972
|
var BREACH_BLAST_FACTOR = 6;
|
|
2968
2973
|
var BREACH_AREA = 0.25;
|
|
@@ -2978,7 +2983,9 @@ var GameDestruction = (() => {
|
|
|
2978
2983
|
var BREACH_GRID_CAP = 4096;
|
|
2979
2984
|
var CHIP_ENERGY = 200;
|
|
2980
2985
|
var CHIP_SPLASH = 0.5;
|
|
2986
|
+
var CRATER_SPENT = 0.6;
|
|
2981
2987
|
var BAR_BOW = 0.08;
|
|
2988
|
+
var BULLET_JOINT_SPAN = 0.3;
|
|
2982
2989
|
var CUT_SLICE = 1;
|
|
2983
2990
|
var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
|
|
2984
2991
|
var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
|
|
@@ -2997,12 +3004,22 @@ var GameDestruction = (() => {
|
|
|
2997
3004
|
const cells = p.st.chipped;
|
|
2998
3005
|
if (!cells?.length) return 0;
|
|
2999
3006
|
const [u, v] = planeAxes(p.thin);
|
|
3000
|
-
const
|
|
3007
|
+
const role = p.spec.structure?.role;
|
|
3008
|
+
const up = rotateVec(p.q, [0, 0, 0].map((_, k) => k === v ? 1 : 0));
|
|
3009
|
+
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;
|
|
3010
|
+
const along = (role === "wall" || role === "partition" || role === "column") && upright >= 0 ? upright : p.dims[u] >= p.dims[v] ? u : v;
|
|
3001
3011
|
const across = along === u ? v : u;
|
|
3002
3012
|
let worst = 0;
|
|
3003
3013
|
for (const c of cells) {
|
|
3004
|
-
|
|
3005
|
-
for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6)
|
|
3014
|
+
const spans = [];
|
|
3015
|
+
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]);
|
|
3016
|
+
spans.sort((a, b) => a[0] - b[0]);
|
|
3017
|
+
let gone = 0, end = -Infinity;
|
|
3018
|
+
for (const [a, b] of spans) {
|
|
3019
|
+
if (b <= end) continue;
|
|
3020
|
+
gone += b - Math.max(a, end);
|
|
3021
|
+
end = b;
|
|
3022
|
+
}
|
|
3006
3023
|
worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
|
|
3007
3024
|
}
|
|
3008
3025
|
return Math.min(1, worst);
|
|
@@ -3298,7 +3315,18 @@ var GameDestruction = (() => {
|
|
|
3298
3315
|
if (k < 0 || j.spec.kind !== "tie") return k;
|
|
3299
3316
|
return partXf(o, o.parts[k]).c[1] > partXf(o, o.parts[r]).c[1] + 1e-6 ? k : -1;
|
|
3300
3317
|
};
|
|
3318
|
+
let poseVersion = 0;
|
|
3319
|
+
const contactMemo = /* @__PURE__ */ new WeakMap();
|
|
3301
3320
|
const contactBox = (p, support) => {
|
|
3321
|
+
let byTouching = contactMemo.get(p);
|
|
3322
|
+
if (!byTouching) contactMemo.set(p, byTouching = /* @__PURE__ */ new Map());
|
|
3323
|
+
const known = byTouching.get(support);
|
|
3324
|
+
if (known && known.version === poseVersion) return known.box;
|
|
3325
|
+
const box = measureContact(p, support);
|
|
3326
|
+
byTouching.set(support, { version: poseVersion, box });
|
|
3327
|
+
return box;
|
|
3328
|
+
};
|
|
3329
|
+
const measureContact = (p, support) => {
|
|
3302
3330
|
const A = aabbOf(p.spec.shape, p.pos, p.q), B = aabbOf(support.spec.shape, support.pos, support.q);
|
|
3303
3331
|
const lo = [0, 0, 0], hi = [0, 0, 0];
|
|
3304
3332
|
for (let k = 0; k < 3; k++) {
|
|
@@ -4093,7 +4121,7 @@ var GameDestruction = (() => {
|
|
|
4093
4121
|
p.st.holes.push(hole);
|
|
4094
4122
|
if (p.st.holes.length > maxHoles) p.st.holes.shift();
|
|
4095
4123
|
markDamaged(p);
|
|
4096
|
-
emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
|
|
4124
|
+
emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {}, ...p.spec.powder ? { powder: { ...p.spec.powder } } : {} });
|
|
4097
4125
|
setStage(o, p, 1, {}, src);
|
|
4098
4126
|
return hole;
|
|
4099
4127
|
};
|
|
@@ -4105,11 +4133,27 @@ var GameDestruction = (() => {
|
|
|
4105
4133
|
emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
|
|
4106
4134
|
setStage(o, p, 1, {}, src);
|
|
4107
4135
|
};
|
|
4108
|
-
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 });
|
|
4136
|
+
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 } : {} });
|
|
4109
4137
|
const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
|
|
4110
4138
|
const detachEvents = (o, opened, by, src) => {
|
|
4111
4139
|
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]) });
|
|
4112
4140
|
};
|
|
4141
|
+
const slideOffResting = (o, rotation) => {
|
|
4142
|
+
const upY = 1 - 2 * (rotation[0] * rotation[0] + rotation[2] * rotation[2]);
|
|
4143
|
+
if (upY >= RESTING_HOLD_COS) return;
|
|
4144
|
+
const opened = o.joints.filter((j) => j.spec.kind === "resting" && !j.st.open);
|
|
4145
|
+
if (opened.length === 0) return;
|
|
4146
|
+
run(() => {
|
|
4147
|
+
for (const j of opened) {
|
|
4148
|
+
j.st.open = true;
|
|
4149
|
+
j.st.openedBy = "break";
|
|
4150
|
+
}
|
|
4151
|
+
solveStructure(o);
|
|
4152
|
+
recomputeGroups(o);
|
|
4153
|
+
detachEvents(o, opened, "break");
|
|
4154
|
+
collapseCheck(o);
|
|
4155
|
+
});
|
|
4156
|
+
};
|
|
4113
4157
|
const checkDestroyed = (o, src) => {
|
|
4114
4158
|
if (!o.st.destroyed && o.parts.length > 0 && o.parts.every(isLost)) {
|
|
4115
4159
|
o.st.destroyed = true;
|
|
@@ -4146,7 +4190,13 @@ var GameDestruction = (() => {
|
|
|
4146
4190
|
const small = pa.dims[0] * pa.dims[1] * pa.dims[2] <= pb.dims[0] * pb.dims[1] * pb.dims[2] ? pa : pb;
|
|
4147
4191
|
return Math.max(0.06, 0.5 * Math.min(small.dims[0], small.dims[1], small.dims[2]));
|
|
4148
4192
|
};
|
|
4149
|
-
const
|
|
4193
|
+
const jointLength = (o, j) => {
|
|
4194
|
+
const A = partWorldAabb(o, o.parts[j.ia]), B = partWorldAabb(o, o.parts[j.ib]);
|
|
4195
|
+
let len = 0;
|
|
4196
|
+
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]));
|
|
4197
|
+
return len;
|
|
4198
|
+
};
|
|
4199
|
+
const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false, span) => {
|
|
4150
4200
|
if (amount <= 0) return;
|
|
4151
4201
|
const mine = (o.byPart.get(p.spec.id) ?? []).filter((j) => !j.st.open);
|
|
4152
4202
|
if (split && mine.length) amount /= mine.length;
|
|
@@ -4154,7 +4204,7 @@ var GameDestruction = (() => {
|
|
|
4154
4204
|
if (j.st.open) continue;
|
|
4155
4205
|
if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
|
|
4156
4206
|
if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
|
|
4157
|
-
j.st.hp -= amount;
|
|
4207
|
+
j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
|
|
4158
4208
|
if (j.st.hp <= 0) {
|
|
4159
4209
|
j.st.hp = 0;
|
|
4160
4210
|
j.st.open = true;
|
|
@@ -4183,7 +4233,7 @@ var GameDestruction = (() => {
|
|
|
4183
4233
|
if (spalls.length > MAX_SPALLS) spalls.shift();
|
|
4184
4234
|
markDamaged(p);
|
|
4185
4235
|
emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
|
|
4186
|
-
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
|
|
4236
|
+
debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src, layer.mat.id);
|
|
4187
4237
|
setStage(o, p, 1, {}, src);
|
|
4188
4238
|
};
|
|
4189
4239
|
const crater = (o, p, wp, normal, radius, src) => {
|
|
@@ -4204,7 +4254,7 @@ var GameDestruction = (() => {
|
|
|
4204
4254
|
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 }));
|
|
4205
4255
|
const unitsOf = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
|
|
4206
4256
|
const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
|
|
4207
|
-
const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
|
|
4257
|
+
const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag, inner = false) => {
|
|
4208
4258
|
const { c, q } = partXf(o, p);
|
|
4209
4259
|
const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
|
|
4210
4260
|
const [u, v] = planeAxes(p.thin);
|
|
@@ -4228,7 +4278,8 @@ var GameDestruction = (() => {
|
|
|
4228
4278
|
velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
|
|
4229
4279
|
angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
|
|
4230
4280
|
mass: mass * faceArea(cell) / total,
|
|
4231
|
-
...marks.length > 0 ? { marks } : {}
|
|
4281
|
+
...marks.length > 0 ? { marks } : {},
|
|
4282
|
+
...inner ? { inner } : {}
|
|
4232
4283
|
};
|
|
4233
4284
|
});
|
|
4234
4285
|
};
|
|
@@ -4269,7 +4320,7 @@ var GameDestruction = (() => {
|
|
|
4269
4320
|
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);
|
|
4270
4321
|
};
|
|
4271
4322
|
const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
|
|
4272
|
-
var _a, _b;
|
|
4323
|
+
var _a, _b, _c;
|
|
4273
4324
|
if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
|
|
4274
4325
|
const rule = p.mat.hits?.[kind];
|
|
4275
4326
|
const perKg = rule?.unitJoules ?? chipEnergy;
|
|
@@ -4279,7 +4330,9 @@ var GameDestruction = (() => {
|
|
|
4279
4330
|
const lp = toLocal(p.pos, p.q, objLocal(o, wp));
|
|
4280
4331
|
const grid = gridOf(p);
|
|
4281
4332
|
const worn = (_a = p.rt).chips ?? (_a.chips = {});
|
|
4333
|
+
const cut = (_b = p.rt).craters ?? (_b.craters = {});
|
|
4282
4334
|
const out = [];
|
|
4335
|
+
const shot = kind === "ballistic";
|
|
4283
4336
|
for (let i = 0; i < grid.length; i++) {
|
|
4284
4337
|
const cell = grid[i];
|
|
4285
4338
|
if (worn[i] === -1) continue;
|
|
@@ -4292,18 +4345,39 @@ var GameDestruction = (() => {
|
|
|
4292
4345
|
worn[i] = total;
|
|
4293
4346
|
continue;
|
|
4294
4347
|
}
|
|
4295
|
-
|
|
4296
|
-
|
|
4348
|
+
if (!shot) {
|
|
4349
|
+
worn[i] = -1;
|
|
4350
|
+
out.push(cell);
|
|
4351
|
+
continue;
|
|
4352
|
+
}
|
|
4353
|
+
const size = [cell.size[0], cell.size[1], cell.size[2]];
|
|
4354
|
+
const center = [cell.center[0], cell.center[1], cell.center[2]];
|
|
4355
|
+
for (const a of [u, v]) {
|
|
4356
|
+
const w = Math.min(cell.size[a], craterAcross(hitEnergy) * (0.85 + 0.3 * rnd()));
|
|
4357
|
+
const lo = cell.center[a] - cell.size[a] / 2 + w / 2, hi = cell.center[a] + cell.size[a] / 2 - w / 2;
|
|
4358
|
+
size[a] = w;
|
|
4359
|
+
center[a] = clamp2(lp[a], lo, hi);
|
|
4360
|
+
}
|
|
4361
|
+
const taken2 = (cut[i] ?? 0) + size[u] * size[v];
|
|
4362
|
+
if (taken2 >= cell.size[u] * cell.size[v] * CRATER_SPENT) {
|
|
4363
|
+
worn[i] = -1;
|
|
4364
|
+
out.push({ ...cell, crater: true });
|
|
4365
|
+
continue;
|
|
4366
|
+
}
|
|
4367
|
+
worn[i] = 0;
|
|
4368
|
+
cut[i] = taken2;
|
|
4369
|
+
out.push({ center, size, crater: true });
|
|
4297
4370
|
}
|
|
4298
4371
|
if (out.length === 0) return;
|
|
4299
4372
|
const { c, q } = partXf(o, p);
|
|
4300
|
-
const chipped = (
|
|
4373
|
+
const chipped = (_c = p.st).chipped ?? (_c.chipped = []);
|
|
4301
4374
|
const holes = localHolesOf(p);
|
|
4302
4375
|
for (const cell of out) {
|
|
4303
4376
|
const mass = Math.min(p.st.mass, p.mat.density * cell.size[0] * cell.size[1] * cell.size[2]);
|
|
4304
4377
|
const center = toWorld(c, q, cell.center);
|
|
4305
4378
|
const speed = clamp2(Math.sqrt(2 * energy * 0.05 / Math.max(mass, 0.05)), 0.5, 6);
|
|
4306
|
-
const
|
|
4379
|
+
const tag = `c${chipped.length}_`;
|
|
4380
|
+
const fragments = cell.crater ? craterFragments(o, p, cell, dir, speed, mass, tag) : cellFragments(o, p, [cell], wp, dir, speed, mass, tag, true);
|
|
4307
4381
|
p.st.holes = p.st.holes.filter((_, k) => marksInBox(cell.center, cell.size, void 0, [holes[k]]).length === 0);
|
|
4308
4382
|
chipped.push({ center: [cell.center[0], cell.center[1], cell.center[2]], size: [cell.size[0], cell.size[1], cell.size[2]] });
|
|
4309
4383
|
p.st.mass = Math.max(0, p.st.mass - mass);
|
|
@@ -4320,6 +4394,44 @@ var GameDestruction = (() => {
|
|
|
4320
4394
|
collapseCheck(o, src);
|
|
4321
4395
|
}
|
|
4322
4396
|
};
|
|
4397
|
+
const craterAcross = (energy) => clamp2(0.03 + 12e-4 * Math.sqrt(Math.max(0, energy)), 0.03, 0.14);
|
|
4398
|
+
const craterFragments = (o, p, cell, dir, speed, mass, tag) => {
|
|
4399
|
+
const { c, q } = partXf(o, p);
|
|
4400
|
+
const [u, v] = planeAxes(p.thin);
|
|
4401
|
+
const d = dir ?? rotateVec(q, [p.thin === 0 ? 1 : 0, p.thin === 1 ? 1 : 0, p.thin === 2 ? 1 : 0]);
|
|
4402
|
+
const exitSign = Math.sign(rotateVec(quat.conjugate(q), d)[p.thin]) || 1;
|
|
4403
|
+
const bit = Math.max(0.01, Math.min(cell.size[u], cell.size[v]) / 3);
|
|
4404
|
+
const n = clamp2(Math.round(cell.size[u] * cell.size[v] / (bit * bit * 3)), 4, 10) + Math.floor(rnd() * 3);
|
|
4405
|
+
const out = [];
|
|
4406
|
+
for (let i = 0; i < n; i++) {
|
|
4407
|
+
const back = i % 3 === 2;
|
|
4408
|
+
const lc = [cell.center[0], cell.center[1], cell.center[2]];
|
|
4409
|
+
lc[u] += (rnd() - 0.5) * cell.size[u] * 0.7;
|
|
4410
|
+
lc[v] += (rnd() - 0.5) * cell.size[v] * 0.7;
|
|
4411
|
+
lc[p.thin] += (back ? -exitSign : exitSign) * (p.thickness / 2 + 0.01);
|
|
4412
|
+
const size = [0, 0, 0];
|
|
4413
|
+
size[u] = bit * (0.6 + 0.8 * rnd());
|
|
4414
|
+
size[v] = bit * (0.5 + 0.7 * rnd());
|
|
4415
|
+
size[p.thin] = bit * (0.4 + 0.6 * rnd());
|
|
4416
|
+
const spread3 = [(rnd() - 0.5) * 0.9, (rnd() - 0.3) * 0.9, (rnd() - 0.5) * 0.9];
|
|
4417
|
+
const k = speed * (back ? 0.4 + 0.4 * rnd() : 0.7 + 0.8 * rnd());
|
|
4418
|
+
const vdir = vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3));
|
|
4419
|
+
out.push({
|
|
4420
|
+
id: `${p.spec.id}#${tag}${i}`,
|
|
4421
|
+
part: p.spec.id,
|
|
4422
|
+
material: p.spec.material,
|
|
4423
|
+
shape: "chunk",
|
|
4424
|
+
center: toWorld(c, q, lc),
|
|
4425
|
+
size,
|
|
4426
|
+
rotation: q,
|
|
4427
|
+
velocity: [vdir[0] * k, vdir[1] * k + 0.5 * rnd(), vdir[2] * k],
|
|
4428
|
+
angularVelocity: [(rnd() - 0.5) * 20, (rnd() - 0.5) * 20, (rnd() - 0.5) * 20],
|
|
4429
|
+
mass: mass / n,
|
|
4430
|
+
inner: true
|
|
4431
|
+
});
|
|
4432
|
+
}
|
|
4433
|
+
return out;
|
|
4434
|
+
};
|
|
4323
4435
|
const exposeRebar = (o, p, src) => {
|
|
4324
4436
|
const r = p.spec.reinforcement;
|
|
4325
4437
|
if (!r) return;
|
|
@@ -4607,7 +4719,7 @@ var GameDestruction = (() => {
|
|
|
4607
4719
|
});
|
|
4608
4720
|
}
|
|
4609
4721
|
const patch = [scale * 4, scale * 4, scale * 4];
|
|
4610
|
-
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry, size: patch, rotation: q, fragments });
|
|
4722
|
+
emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry, size: patch, rotation: q, fragments, torn: true });
|
|
4611
4723
|
};
|
|
4612
4724
|
const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
|
|
4613
4725
|
const mat = p.mat;
|
|
@@ -4666,12 +4778,13 @@ var GameDestruction = (() => {
|
|
|
4666
4778
|
} else if (mat.ductility >= 0.5) {
|
|
4667
4779
|
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);
|
|
4668
4780
|
} else if (brittle) {
|
|
4669
|
-
addCrack(o, p, entry, n, crackLen, src);
|
|
4781
|
+
if (!p.spec.units) addCrack(o, p, entry, n, crackLen, src);
|
|
4670
4782
|
if (mat.fracture !== "shatter") addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
|
|
4671
4783
|
} else addHole(o, p, entry, n, caliber / 1e3, false, void 0, src);
|
|
4672
4784
|
debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
|
|
4673
|
-
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
|
|
4674
|
-
const
|
|
4785
|
+
wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true, BULLET_JOINT_SPAN);
|
|
4786
|
+
const hollowTear = mat.fracture === "tear" && p.spec.fill !== void 0 && p.spec.fill < 0.5;
|
|
4787
|
+
const absorbed = penetrated && (mat.ductility >= 0.5 && mat.fracture !== "tear" || hollowTear) ? Math.min(E, thr) : E;
|
|
4675
4788
|
const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry, d, absorbed * 0.1, 12, src);
|
|
4676
4789
|
if (!isLost(p)) tearAt(o, p, "ballistic", entry, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
|
|
4677
4790
|
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);
|
|
@@ -4756,10 +4869,25 @@ var GameDestruction = (() => {
|
|
|
4756
4869
|
if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
|
|
4757
4870
|
return reach;
|
|
4758
4871
|
};
|
|
4872
|
+
const blastProbe = { partsTested: 0, partsNear: 0 };
|
|
4873
|
+
const blastCandidates = (o, point, radius) => {
|
|
4874
|
+
const local = toLocal(o.st.position, o.st.rotation, point);
|
|
4875
|
+
const near = [];
|
|
4876
|
+
for (const p of o.parts) {
|
|
4877
|
+
const reach = radius + p.bound;
|
|
4878
|
+
const dx = local[0] - p.pos[0];
|
|
4879
|
+
const dy = local[1] - p.pos[1];
|
|
4880
|
+
const dz = local[2] - p.pos[2];
|
|
4881
|
+
if (dx * dx + dy * dy + dz * dz < reach * reach) near.push(p);
|
|
4882
|
+
}
|
|
4883
|
+
blastProbe.partsTested += o.parts.length;
|
|
4884
|
+
blastProbe.partsNear += near.length;
|
|
4885
|
+
return near;
|
|
4886
|
+
};
|
|
4759
4887
|
const blastObject = (o, point, energy, radius, src) => {
|
|
4760
4888
|
const start = cur.length;
|
|
4761
4889
|
const res = { object: o.id, damage: 0, penetrated: false, exitEnergy: 0, events: [] };
|
|
4762
|
-
for (const p of o
|
|
4890
|
+
for (const p of blastCandidates(o, point, radius)) blastPart(o, p, point, energy, radius, src, res);
|
|
4763
4891
|
res.events = cur.slice(start);
|
|
4764
4892
|
return res;
|
|
4765
4893
|
};
|
|
@@ -4883,7 +5011,7 @@ var GameDestruction = (() => {
|
|
|
4883
5011
|
let order = 0;
|
|
4884
5012
|
for (const o of inRange) {
|
|
4885
5013
|
if (only && only !== o) continue;
|
|
4886
|
-
for (const p of o
|
|
5014
|
+
for (const p of blastCandidates(o, point, radius)) {
|
|
4887
5015
|
if (isLost(p)) continue;
|
|
4888
5016
|
const dist = distToPart(o, p, point);
|
|
4889
5017
|
if (dist < radius) items.push({ dist, order: order++, o, p });
|
|
@@ -5430,6 +5558,7 @@ var GameDestruction = (() => {
|
|
|
5430
5558
|
const p = getPart(o, partId);
|
|
5431
5559
|
p.pos = [position[0], position[1], position[2]];
|
|
5432
5560
|
p.q = q;
|
|
5561
|
+
poseVersion++;
|
|
5433
5562
|
updateBound(o);
|
|
5434
5563
|
},
|
|
5435
5564
|
remove(id) {
|
|
@@ -5448,6 +5577,7 @@ var GameDestruction = (() => {
|
|
|
5448
5577
|
const o = getObj(id);
|
|
5449
5578
|
o.base = { pos: [position[0], position[1], position[2]], rot: [rotation[0], rotation[1], rotation[2], rotation[3]] };
|
|
5450
5579
|
applySoilPose(o);
|
|
5580
|
+
slideOffResting(o, rotation);
|
|
5451
5581
|
},
|
|
5452
5582
|
splitGroup(id, group, newId) {
|
|
5453
5583
|
if (impls.has(id)) throw new Error(`object "${id}" is a registered DestructibleImpl and cannot be split; its module owns its pieces`);
|
|
@@ -5471,7 +5601,7 @@ var GameDestruction = (() => {
|
|
|
5471
5601
|
if (o.spec.name !== void 0) s.name = o.spec.name;
|
|
5472
5602
|
if (o.spec.tags) s.tags = [...o.spec.tags];
|
|
5473
5603
|
if (o.spec.soil !== void 0) s.soil = o.spec.soil;
|
|
5474
|
-
return
|
|
5604
|
+
return s;
|
|
5475
5605
|
};
|
|
5476
5606
|
const inNew = (pid) => moved.has(pid);
|
|
5477
5607
|
const inOld = (pid) => !moved.has(pid);
|
|
@@ -5498,7 +5628,12 @@ var GameDestruction = (() => {
|
|
|
5498
5628
|
const p = getPart(o, partId);
|
|
5499
5629
|
return whole(() => doHit(o, h, p));
|
|
5500
5630
|
},
|
|
5631
|
+
blastProbe() {
|
|
5632
|
+
return { ...blastProbe };
|
|
5633
|
+
},
|
|
5501
5634
|
explode(point, energy, radius, source, blast) {
|
|
5635
|
+
blastProbe.partsTested = 0;
|
|
5636
|
+
blastProbe.partsNear = 0;
|
|
5502
5637
|
return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
|
|
5503
5638
|
},
|
|
5504
5639
|
raycast(origin, direction, maxDistance = Infinity) {
|
|
@@ -5508,7 +5643,8 @@ var GameDestruction = (() => {
|
|
|
5508
5643
|
sweepBox(box, direction, distance, options2 = {}) {
|
|
5509
5644
|
const d = vec.normalize(direction);
|
|
5510
5645
|
const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
|
|
5511
|
-
const
|
|
5646
|
+
const hosted = options2.ignore === void 0 || ignored ? void 0 : impls.get(options2.ignore);
|
|
5647
|
+
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;
|
|
5512
5648
|
let best = null;
|
|
5513
5649
|
for (const origin of sweepOrigins(box, d, options2.spacing ?? 0.2)) {
|
|
5514
5650
|
const r = raycastInternal(origin, d, distance + SWEEP_BACK, ignore);
|