@adatechnology/game-destruction-three 0.15.4 → 0.16.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/README.md +2 -2
- package/dist/index.global.js +662 -61
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +411 -17
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -122,13 +122,154 @@ function mixHex(a, b, t) {
|
|
|
122
122
|
return r << 16 | g << 8 | bl;
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
// src/fragment-shapes.ts
|
|
126
|
+
var FRAGMENT_SHAPE_VARIANTS = 6;
|
|
127
|
+
var SLIVER_STATIONS = 6;
|
|
128
|
+
var SLIVER_FRAY = 0.3;
|
|
129
|
+
var SLIVER_SECTION = [[0.5, 0.5], [0, 0.5], [-0.5, 0.5], [-0.5, -0.5], [0, -0.5], [0.5, -0.5]];
|
|
130
|
+
var sliverWidth = (t) => t < 0.25 ? 0.6 + 1.6 * t : t > 0.5 ? Math.max(0, (1 - t) / 0.5) ** 0.85 : 1;
|
|
131
|
+
var pushTriangle = (out, a, b, c) => {
|
|
132
|
+
out.push(a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2]);
|
|
133
|
+
};
|
|
134
|
+
var faceted = (THREE, positions) => {
|
|
135
|
+
const geometry = new THREE.BufferGeometry();
|
|
136
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
|
|
137
|
+
geometry.computeVertexNormals();
|
|
138
|
+
return geometry;
|
|
139
|
+
};
|
|
140
|
+
function buildSliverGeometry(THREE, seed) {
|
|
141
|
+
const rnd = createRng(7e3 + seed);
|
|
142
|
+
const rings = [];
|
|
143
|
+
const bendY = (rnd() - 0.5) * 0.3;
|
|
144
|
+
const bendZ = (rnd() - 0.5) * 0.3;
|
|
145
|
+
for (let i = 0; i < SLIVER_STATIONS - 1; i++) {
|
|
146
|
+
const t = i / (SLIVER_STATIONS - 1);
|
|
147
|
+
const x = -0.5 + t + (i > 0 ? (rnd() - 0.5) * 0.08 : 0);
|
|
148
|
+
const width = sliverWidth(t);
|
|
149
|
+
const lift = Math.sin(t * Math.PI);
|
|
150
|
+
rings.push(SLIVER_SECTION.map(([y, z]) => {
|
|
151
|
+
const fray = 1 - SLIVER_FRAY + SLIVER_FRAY * 1.4 * rnd();
|
|
152
|
+
return [x, Math.max(-0.5, Math.min(0.5, y * width * fray + bendY * lift)), Math.max(-0.5, Math.min(0.5, z * width * fray + bendZ * lift))];
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
const tip = [0.5, (rnd() - 0.5) * 0.5, (rnd() - 0.5) * 0.3];
|
|
156
|
+
const torn = [-0.5 + rnd() * 0.12, (rnd() - 0.5) * 0.2, (rnd() - 0.5) * 0.2];
|
|
157
|
+
const sides = SLIVER_SECTION.length;
|
|
158
|
+
const positions = [];
|
|
159
|
+
for (let k = 0; k < sides; k++) {
|
|
160
|
+
const next = (k + 1) % sides;
|
|
161
|
+
pushTriangle(positions, torn, rings[0][next], rings[0][k]);
|
|
162
|
+
for (let i = 0; i < rings.length - 1; i++) {
|
|
163
|
+
const a = rings[i][k], b = rings[i][next], c = rings[i + 1][next], d = rings[i + 1][k];
|
|
164
|
+
pushTriangle(positions, a, b, c);
|
|
165
|
+
pushTriangle(positions, a, c, d);
|
|
166
|
+
}
|
|
167
|
+
const last = rings[rings.length - 1];
|
|
168
|
+
pushTriangle(positions, last[k], last[next], tip);
|
|
169
|
+
}
|
|
170
|
+
return faceted(THREE, positions);
|
|
171
|
+
}
|
|
172
|
+
function buildLumpGeometry(THREE, seed) {
|
|
173
|
+
const rnd = createRng(8e3 + seed);
|
|
174
|
+
const base = new THREE.IcosahedronGeometry(0.5, 0);
|
|
175
|
+
const source = base.getAttribute("position");
|
|
176
|
+
const pushOf = /* @__PURE__ */ new Map();
|
|
177
|
+
const positions = [];
|
|
178
|
+
for (let i = 0; i < source.count; i++) {
|
|
179
|
+
const x = source.getX(i), y = source.getY(i), z = source.getZ(i);
|
|
180
|
+
const key = `${x.toFixed(3)},${y.toFixed(3)},${z.toFixed(3)}`;
|
|
181
|
+
let push = pushOf.get(key);
|
|
182
|
+
if (push === void 0) {
|
|
183
|
+
push = 0.6 + 0.55 * rnd();
|
|
184
|
+
pushOf.set(key, push);
|
|
185
|
+
}
|
|
186
|
+
positions.push(x * push, y * push, z * push);
|
|
187
|
+
}
|
|
188
|
+
base.dispose();
|
|
189
|
+
const extent = [0, 0, 0];
|
|
190
|
+
for (let i = 0; i < positions.length; i++) extent[i % 3] = Math.max(extent[i % 3], Math.abs(positions[i]));
|
|
191
|
+
for (let i = 0; i < positions.length; i++) positions[i] *= 0.5 / Math.max(extent[i % 3], 1e-3);
|
|
192
|
+
return faceted(THREE, positions);
|
|
193
|
+
}
|
|
194
|
+
var VENEER_EDGE = 11;
|
|
195
|
+
function buildVeneerGeometry(THREE, seed) {
|
|
196
|
+
const rnd = createRng(9e3 + seed);
|
|
197
|
+
const shape = new THREE.Shape();
|
|
198
|
+
const outline = [];
|
|
199
|
+
const edge = (y, inward) => y - inward * (rnd() * 0.16 + (rnd() < 0.15 ? 0.12 : 0));
|
|
200
|
+
for (let i = 0; i <= VENEER_EDGE; i++) outline.push([-0.5 + i / VENEER_EDGE, edge(0.5, 1)]);
|
|
201
|
+
outline.push([0.5 - rnd() * 0.12, 0.2 * rnd()], [0.5, -0.1 + 0.2 * rnd()], [0.5 - rnd() * 0.12, -0.25 * rnd()]);
|
|
202
|
+
for (let i = VENEER_EDGE; i >= 0; i--) outline.push([-0.5 + i / VENEER_EDGE, edge(-0.5, -1)]);
|
|
203
|
+
outline.push([-0.5 + rnd() * 0.12, -0.2 * rnd()], [-0.5, 0.1 * rnd()], [-0.5 + rnd() * 0.12, 0.25 * rnd()]);
|
|
204
|
+
shape.moveTo(outline[0][0], outline[0][1]);
|
|
205
|
+
for (let i = 1; i < outline.length; i++) shape.lineTo(outline[i][0], outline[i][1]);
|
|
206
|
+
shape.closePath();
|
|
207
|
+
const geometry = new THREE.ExtrudeGeometry(shape, { depth: 1, bevelEnabled: false });
|
|
208
|
+
geometry.translate(0, 0, -0.5);
|
|
209
|
+
return geometry;
|
|
210
|
+
}
|
|
211
|
+
var TORN_END = 0.24;
|
|
212
|
+
var TORN_STRANDS = [4, 7];
|
|
213
|
+
var pushQuad = (out, quad) => {
|
|
214
|
+
pushTriangle(out, quad[0], quad[1], quad[2]);
|
|
215
|
+
pushTriangle(out, quad[0], quad[2], quad[3]);
|
|
216
|
+
};
|
|
217
|
+
var unevenCuts = (count, rnd) => {
|
|
218
|
+
const shares = Array.from({ length: count }, () => 0.35 + 1.65 * rnd());
|
|
219
|
+
const total = shares.reduce((sum, share) => sum + share, 0);
|
|
220
|
+
const cuts = [-0.5];
|
|
221
|
+
for (const share of shares) cuts.push(cuts[cuts.length - 1] + share / total);
|
|
222
|
+
cuts[cuts.length - 1] = 0.5;
|
|
223
|
+
return cuts;
|
|
224
|
+
};
|
|
225
|
+
function buildBrokenBoardGeometry(THREE, seed) {
|
|
226
|
+
const rnd = createRng(1e4 + seed);
|
|
227
|
+
const end = 0.5 - TORN_END;
|
|
228
|
+
const positions = [];
|
|
229
|
+
const lo = -0.5, hi = 0.5;
|
|
230
|
+
pushQuad(positions, [[lo, lo, hi], [end, lo, hi], [end, hi, hi], [lo, hi, hi]]);
|
|
231
|
+
pushQuad(positions, [[lo, hi, lo], [end, hi, lo], [end, lo, lo], [lo, lo, lo]]);
|
|
232
|
+
pushQuad(positions, [[lo, hi, hi], [end, hi, hi], [end, hi, lo], [lo, hi, lo]]);
|
|
233
|
+
pushQuad(positions, [[lo, lo, lo], [end, lo, lo], [end, lo, hi], [lo, lo, hi]]);
|
|
234
|
+
pushQuad(positions, [[lo, lo, lo], [lo, lo, hi], [lo, hi, hi], [lo, hi, lo]]);
|
|
235
|
+
const count = TORN_STRANDS[0] + Math.floor(rnd() * (TORN_STRANDS[1] - TORN_STRANDS[0] + 1));
|
|
236
|
+
const cuts = unevenCuts(count, rnd);
|
|
237
|
+
for (let k = 0; k < count; k++) {
|
|
238
|
+
const y0 = cuts[k], y1 = cuts[k + 1];
|
|
239
|
+
const length = TORN_END * (0.15 + 0.85 * rnd());
|
|
240
|
+
const midX = end + length * (0.35 + 0.3 * rnd());
|
|
241
|
+
const narrow = 0.55 + 0.35 * rnd();
|
|
242
|
+
const shiftY = (rnd() - 0.5) * (y1 - y0) * 0.6;
|
|
243
|
+
const shiftZ = (rnd() - 0.5) * 0.5;
|
|
244
|
+
const cy = (y0 + y1) / 2 + shiftY, hy = (y1 - y0) / 2 * narrow, hz = 0.5 * (0.4 + 0.5 * rnd());
|
|
245
|
+
const base = [[end, y0, lo], [end, y1, lo], [end, y1, hi], [end, y0, hi]];
|
|
246
|
+
const clampZ = (z) => Math.max(lo, Math.min(hi, z));
|
|
247
|
+
const mid = [[midX, cy - hy, clampZ(shiftZ - hz)], [midX, cy + hy, clampZ(shiftZ - hz)], [midX, cy + hy, clampZ(shiftZ + hz)], [midX, cy - hy, clampZ(shiftZ + hz)]];
|
|
248
|
+
const tip = [end + length, y0 + (y1 - y0) * rnd(), clampZ(shiftZ + (rnd() - 0.5) * 0.4)];
|
|
249
|
+
for (let i = 0; i < 4; i++) {
|
|
250
|
+
const next = (i + 1) % 4;
|
|
251
|
+
pushQuad(positions, [base[i], base[next], mid[next], mid[i]]);
|
|
252
|
+
pushTriangle(positions, mid[i], mid[next], tip);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return faceted(THREE, positions);
|
|
256
|
+
}
|
|
257
|
+
|
|
125
258
|
// src/fragments.ts
|
|
259
|
+
var BUILDERS = {
|
|
260
|
+
sliver: buildSliverGeometry,
|
|
261
|
+
lump: buildLumpGeometry,
|
|
262
|
+
veneer: buildVeneerGeometry,
|
|
263
|
+
board: buildBrokenBoardGeometry
|
|
264
|
+
};
|
|
126
265
|
var SHEET_MAX = 4e-3;
|
|
127
266
|
var SHEET_AIR = 2.5;
|
|
128
267
|
var SHEET_SWAY = 3;
|
|
129
268
|
var BIG_LENGTH = 0.2;
|
|
130
269
|
var BIG_WIDTH = 0.03;
|
|
131
270
|
var FRAGMENT_SEED = 1374496523;
|
|
271
|
+
var SUPPORT_CHECKS = 32;
|
|
272
|
+
var SUPPORT_GAP = 0.02;
|
|
132
273
|
var SHARD_SHAPES = 6;
|
|
133
274
|
var isBig = (size) => {
|
|
134
275
|
const [, middle, longest] = [...size].sort((a, b) => a - b);
|
|
@@ -143,6 +284,7 @@ var FragmentSystem = class {
|
|
|
143
284
|
/** The mesh box of a fragment before its scale. */
|
|
144
285
|
__publicField(this, "unitBox", { min: { x: -0.5, y: -0.5, z: -0.5 }, max: { x: 0.5, y: 0.5, z: 0.5 } });
|
|
145
286
|
__publicField(this, "shardGeos", []);
|
|
287
|
+
__publicField(this, "shapeGeos", /* @__PURE__ */ new Map());
|
|
146
288
|
__publicField(this, "materials", /* @__PURE__ */ new Map());
|
|
147
289
|
__publicField(this, "q");
|
|
148
290
|
__publicField(this, "dq");
|
|
@@ -152,6 +294,7 @@ var FragmentSystem = class {
|
|
|
152
294
|
__publicField(this, "rng", createRng(FRAGMENT_SEED));
|
|
153
295
|
/** Count of `update` calls: what makes the fragments of one burst tell apart from older ones. */
|
|
154
296
|
__publicField(this, "tick", 0);
|
|
297
|
+
__publicField(this, "supportTurn", 0);
|
|
155
298
|
__publicField(this, "markGeometry", null);
|
|
156
299
|
__publicField(this, "markMaterial", null);
|
|
157
300
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
@@ -200,21 +343,54 @@ var FragmentSystem = class {
|
|
|
200
343
|
}
|
|
201
344
|
return g;
|
|
202
345
|
}
|
|
346
|
+
sharedGeometry(shape, seed) {
|
|
347
|
+
const idx = seed % FRAGMENT_SHAPE_VARIANTS;
|
|
348
|
+
let variants = this.shapeGeos.get(shape);
|
|
349
|
+
if (!variants) {
|
|
350
|
+
variants = [];
|
|
351
|
+
this.shapeGeos.set(shape, variants);
|
|
352
|
+
}
|
|
353
|
+
let g = variants[idx];
|
|
354
|
+
if (!g) {
|
|
355
|
+
g = BUILDERS[shape](this.THREE, idx);
|
|
356
|
+
variants[idx] = g;
|
|
357
|
+
}
|
|
358
|
+
return g;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* The geometry a fragment is drawn with: a splinter is a pointed, frayed sliver; MDF breaks in lumps, plywood peels in ragged veneer, the stub of a broken
|
|
362
|
+
* board ends in torn strands at +x; the rest are boxes.
|
|
363
|
+
*/
|
|
364
|
+
geometryOf(f) {
|
|
365
|
+
const seed = hashString(f.id);
|
|
366
|
+
if (f.shape === "shard") return this.shardGeometry(seed);
|
|
367
|
+
if (f.shape === "splinter") return this.sharedGeometry("sliver", seed);
|
|
368
|
+
const form = this.o.formOf?.(f.material);
|
|
369
|
+
if (f.shape === "chunk" && form === "granules") return this.sharedGeometry("lump", seed);
|
|
370
|
+
if (f.shape === "scrap" && form === "plies") return this.sharedGeometry("veneer", seed);
|
|
371
|
+
if (f.shape === "slab" && (form === "slivers" || form === "plies")) return this.sharedGeometry("board", seed);
|
|
372
|
+
return this.box;
|
|
373
|
+
}
|
|
203
374
|
markParts() {
|
|
204
375
|
this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
|
|
205
376
|
this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
|
|
206
377
|
return { geometry: this.markGeometry, material: this.markMaterial };
|
|
207
378
|
}
|
|
208
379
|
/**
|
|
209
|
-
* Meshes, never put in a scene by this class, that draw every shared piece of a fragment once: the box, the six shards, the bullet-hole disc, and the opaque and
|
|
380
|
+
* Meshes, never put in a scene by this class, that draw every shared piece of a fragment once: the box, the six shards, the wood splinters, MDF lumps and plywood veneer, the bullet-hole disc, and the opaque and
|
|
210
381
|
* glass materials. A host draws them at load so the first blast does not upload these buffers in the middle of a frame.
|
|
211
382
|
*/
|
|
212
383
|
warmMeshes() {
|
|
213
384
|
const opaque = this.materialFor("warm", 8421504, false);
|
|
214
385
|
const glass2 = this.materialFor("warm", 8421504, true);
|
|
215
386
|
const geometries = [this.box, ...Array.from({ length: SHARD_SHAPES }, (_, index) => this.shardGeometry(index))];
|
|
387
|
+
const wood = ["sliver", "lump", "veneer", "board"].flatMap((shape) => Array.from({ length: FRAGMENT_SHAPE_VARIANTS }, (_, index) => this.sharedGeometry(shape, index)));
|
|
216
388
|
const { geometry, material } = this.markParts();
|
|
217
|
-
return [
|
|
389
|
+
return [
|
|
390
|
+
...geometries.flatMap((shared) => [new this.THREE.Mesh(shared, opaque), new this.THREE.Mesh(shared, glass2)]),
|
|
391
|
+
...wood.map((shared) => new this.THREE.Mesh(shared, opaque)),
|
|
392
|
+
new this.THREE.Mesh(geometry, material)
|
|
393
|
+
];
|
|
218
394
|
}
|
|
219
395
|
/**
|
|
220
396
|
* Bullet holes that stay on a fragment (`Fragment.marks`): dark discs that are children of its mesh, so they fall, spin and settle with it. The mesh is
|
|
@@ -239,7 +415,11 @@ var FragmentSystem = class {
|
|
|
239
415
|
* bigger than the budget (every live fragment was made in this same frame) does not make the rest at all, so a blast never builds a thousand meshes only to fade them
|
|
240
416
|
* out. Returns null when the fragment was not made.
|
|
241
417
|
*/
|
|
242
|
-
|
|
418
|
+
/**
|
|
419
|
+
* Throws `f` into the scene, within `maxAllowed` live fragments (the oldest small ones make room first). A `lasting` fragment (a steel bar) is debris that
|
|
420
|
+
* stays: it has a body the player and the pieces run into, however thin it is, and it never fades.
|
|
421
|
+
*/
|
|
422
|
+
spawn(f, material, home, maxAllowed, lasting = false) {
|
|
243
423
|
if (this.list.length >= maxAllowed) {
|
|
244
424
|
let alive = 0;
|
|
245
425
|
let oldest;
|
|
@@ -256,7 +436,7 @@ var FragmentSystem = class {
|
|
|
256
436
|
oldest.dying = true;
|
|
257
437
|
}
|
|
258
438
|
}
|
|
259
|
-
const geo =
|
|
439
|
+
const geo = this.geometryOf(f);
|
|
260
440
|
const mesh = new this.THREE.Mesh(geo, material);
|
|
261
441
|
mesh.position.set(f.center[0], f.center[1], f.center[2]);
|
|
262
442
|
mesh.quaternion.set(f.rotation[0], f.rotation[1], f.rotation[2], f.rotation[3]).normalize();
|
|
@@ -284,9 +464,9 @@ var FragmentSystem = class {
|
|
|
284
464
|
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0,
|
|
285
465
|
body: null,
|
|
286
466
|
born: this.tick,
|
|
287
|
-
life: Math.max(0, typeof this.o.life === "function" ? this.o.life(f, home) : this.o.life)
|
|
467
|
+
life: lasting ? 0 : Math.max(0, typeof this.o.life === "function" ? this.o.life(f, home) : this.o.life)
|
|
288
468
|
};
|
|
289
|
-
if (f.shape !== "shard" && isBig(f.size)) {
|
|
469
|
+
if (lasting || f.shape !== "shard" && isBig(f.size)) {
|
|
290
470
|
live.body = {
|
|
291
471
|
mass: f.mass,
|
|
292
472
|
push: (x, z) => {
|
|
@@ -346,6 +526,7 @@ var FragmentSystem = class {
|
|
|
346
526
|
}
|
|
347
527
|
update(dt) {
|
|
348
528
|
this.tick++;
|
|
529
|
+
this.wakeUnsupported();
|
|
349
530
|
const g = this.o.gravity;
|
|
350
531
|
for (let i = this.list.length - 1; i >= 0; i--) {
|
|
351
532
|
const f = this.list[i];
|
|
@@ -370,6 +551,18 @@ var FragmentSystem = class {
|
|
|
370
551
|
this.step(f, dt, g);
|
|
371
552
|
}
|
|
372
553
|
}
|
|
554
|
+
/** A few fragments asleep each update (`SUPPORT_CHECKS`, in turn) look at the ground under them again: one left over air falls. */
|
|
555
|
+
wakeUnsupported() {
|
|
556
|
+
const n = this.list.length;
|
|
557
|
+
for (let k = 0; k < Math.min(SUPPORT_CHECKS, n); k++) {
|
|
558
|
+
this.supportTurn = (this.supportTurn + 1) % n;
|
|
559
|
+
const f = this.list[this.supportTurn];
|
|
560
|
+
if (!f.asleep || f.dying) continue;
|
|
561
|
+
const e = this.m.makeRotationFromQuaternion(f.mesh.quaternion).elements;
|
|
562
|
+
const ext = Math.abs(e[1]) * f.half.x + Math.abs(e[5]) * f.half.y + Math.abs(e[9]) * f.half.z;
|
|
563
|
+
if (f.mesh.position.y - ext > this.o.groundAt(f.mesh.position.x, f.mesh.position.z) + SUPPORT_GAP) this.rouse(f);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
373
566
|
step(f, dt, g) {
|
|
374
567
|
const mesh = f.mesh;
|
|
375
568
|
f.vel.y -= g * dt;
|
|
@@ -477,6 +670,8 @@ var FragmentSystem = class {
|
|
|
477
670
|
this.markMaterial?.dispose();
|
|
478
671
|
for (const g of this.shardGeos) g?.dispose();
|
|
479
672
|
this.shardGeos = [];
|
|
673
|
+
for (const variants of this.shapeGeos.values()) for (const g of variants) g?.dispose();
|
|
674
|
+
this.shapeGeos.clear();
|
|
480
675
|
for (const m of this.materials.values()) m.dispose();
|
|
481
676
|
this.materials.clear();
|
|
482
677
|
}
|
|
@@ -586,6 +781,8 @@ var WARM_SLOTS = 8 + 12 * 7;
|
|
|
586
781
|
var SOLVER_PASSES = 10;
|
|
587
782
|
var SIDE_BOUNCE = 0.1;
|
|
588
783
|
var EDGE_POINTS = [-0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75];
|
|
784
|
+
var SUPPORT_CHECKS2 = 16;
|
|
785
|
+
var SUPPORT_GAP2 = 0.02;
|
|
589
786
|
var boxTopAt = (s, x, z) => {
|
|
590
787
|
const e = s.e;
|
|
591
788
|
const dx = x - s.c[0], dz = z - s.c[2];
|
|
@@ -618,6 +815,7 @@ var PieceSystem = class {
|
|
|
618
815
|
__publicField(this, "m");
|
|
619
816
|
__publicField(this, "ax");
|
|
620
817
|
__publicField(this, "solids", []);
|
|
818
|
+
__publicField(this, "supportTurn", 0);
|
|
621
819
|
this.p = new THREE.Vector3();
|
|
622
820
|
this.q = new THREE.Quaternion();
|
|
623
821
|
this.dq = new THREE.Quaternion();
|
|
@@ -777,6 +975,7 @@ var PieceSystem = class {
|
|
|
777
975
|
const h = body.halfExtents;
|
|
778
976
|
return { body, c: [c.x, c.y, c.z], e: [...new this.THREE.Matrix4().makeRotationFromQuaternion(body.root.quaternion).elements], half: h, r: Math.hypot(h[0], h[1], h[2]) };
|
|
779
977
|
});
|
|
978
|
+
this.wakeUnsupported();
|
|
780
979
|
for (const b of this.list) {
|
|
781
980
|
if (b.held) {
|
|
782
981
|
b.velocity = [0, 0, 0];
|
|
@@ -791,6 +990,33 @@ var PieceSystem = class {
|
|
|
791
990
|
}
|
|
792
991
|
this.separate(dt);
|
|
793
992
|
}
|
|
993
|
+
/** A few sleeping pieces each update (`SUPPORT_CHECKS`, in turn) look under them again: one whose table broke, or whose shelf fell over, falls. */
|
|
994
|
+
wakeUnsupported() {
|
|
995
|
+
const n = this.list.length;
|
|
996
|
+
for (let k = 0; k < Math.min(SUPPORT_CHECKS2, n); k++) {
|
|
997
|
+
this.supportTurn = (this.supportTurn + 1) % n;
|
|
998
|
+
const b = this.list[this.supportTurn];
|
|
999
|
+
if (!b.sleeping || b.held || this.supported(b, false) || this.supported(b, true)) continue;
|
|
1000
|
+
b.sleeping = false;
|
|
1001
|
+
b.still = 0;
|
|
1002
|
+
b.calmAt = null;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Whether a point of `b` (its corners, or with `edges` the points along its edges too) has the ground, a piece or a fixed top under it. Any point, not only the lowest:
|
|
1007
|
+
* a book propped on a fallen case rests by a corner high on it while its lowest one hangs beside it.
|
|
1008
|
+
*/
|
|
1009
|
+
supported(b, edges) {
|
|
1010
|
+
const c = this.centerOf(b, this.p);
|
|
1011
|
+
const e = this.m.makeRotationFromQuaternion(b.root.quaternion).elements;
|
|
1012
|
+
for (const [sx, sy, sz] of this.samples(b, edges)) {
|
|
1013
|
+
const y = c.y + e[1] * sx + e[5] * sy + e[9] * sz;
|
|
1014
|
+
const x = c.x + e[0] * sx + e[4] * sy + e[8] * sz;
|
|
1015
|
+
const z = c.z + e[2] * sx + e[6] * sy + e[10] * sz;
|
|
1016
|
+
if (this.supportAt(b, x, y, z, STEP) >= y - SUPPORT_GAP2) return true;
|
|
1017
|
+
}
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
794
1020
|
/** Where a corner of `self` at `(x, y, z)` lands: the ground, the top of another piece or of something fixed under it. */
|
|
795
1021
|
supportAt(self, x, y, z, step) {
|
|
796
1022
|
let top = this.o.groundAt(x, z);
|
|
@@ -1164,6 +1390,7 @@ var RebarCages = class {
|
|
|
1164
1390
|
constructor(THREE, scene2, radius = 4e-3) {
|
|
1165
1391
|
__publicField(this, "THREE", THREE);
|
|
1166
1392
|
__publicField(this, "scene", scene2);
|
|
1393
|
+
__publicField(this, "radius", radius);
|
|
1167
1394
|
__publicField(this, "cages", /* @__PURE__ */ new Map());
|
|
1168
1395
|
__publicField(this, "geometry");
|
|
1169
1396
|
__publicField(this, "material");
|
|
@@ -1192,28 +1419,62 @@ var RebarCages = class {
|
|
|
1192
1419
|
meshes() {
|
|
1193
1420
|
return [...this.cages.values()];
|
|
1194
1421
|
}
|
|
1195
|
-
/**
|
|
1196
|
-
|
|
1422
|
+
/**
|
|
1423
|
+
* Draw `bars` (world space) as the cage of `key`; an empty list removes it. The cage is a child of `holder` (the drawing of the part's object, the scene
|
|
1424
|
+
* by default), so it goes where the part goes: a beam that comes down takes its bare bars with it.
|
|
1425
|
+
*/
|
|
1426
|
+
set(key, bars, holder) {
|
|
1197
1427
|
this.remove(key);
|
|
1198
1428
|
if (bars.length === 0) return;
|
|
1429
|
+
const parent = holder ?? this.scene;
|
|
1430
|
+
parent.updateWorldMatrix(true, false);
|
|
1431
|
+
const toParent = new this.THREE.Matrix4().copy(parent.matrixWorld).invert();
|
|
1199
1432
|
const mesh = new this.THREE.InstancedMesh(this.geometry, this.material, bars.length);
|
|
1200
1433
|
bars.forEach(([a, b], i) => {
|
|
1201
1434
|
this.dir.set(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
1202
1435
|
const length = this.dir.length();
|
|
1203
1436
|
this.mid.set((a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2);
|
|
1204
1437
|
this.q.setFromUnitVectors(this.up, length > 1e-9 ? this.dir.divideScalar(length) : this.up);
|
|
1205
|
-
this.m.compose(this.mid, this.q, this.scale.set(1, Math.max(length, 1e-4), 1));
|
|
1438
|
+
this.m.compose(this.mid, this.q, this.scale.set(1, Math.max(length, 1e-4), 1)).premultiply(toParent);
|
|
1206
1439
|
mesh.setMatrixAt(i, this.m);
|
|
1207
1440
|
});
|
|
1208
1441
|
mesh.instanceMatrix.needsUpdate = true;
|
|
1209
1442
|
mesh.castShadow = true;
|
|
1210
|
-
|
|
1443
|
+
mesh.name = "rebar-cage";
|
|
1444
|
+
parent.add(mesh);
|
|
1211
1445
|
this.cages.set(key, mesh);
|
|
1212
1446
|
}
|
|
1447
|
+
/** The cage of `from` now belongs to `to` (a part that left its object for one of its own) and hangs from `holder`, where it is in the world now. */
|
|
1448
|
+
move(from, to, holder) {
|
|
1449
|
+
const mesh = this.cages.get(from);
|
|
1450
|
+
if (!mesh) return;
|
|
1451
|
+
this.cages.delete(from);
|
|
1452
|
+
this.remove(to);
|
|
1453
|
+
holder.attach(mesh);
|
|
1454
|
+
this.cages.set(to, mesh);
|
|
1455
|
+
}
|
|
1456
|
+
/** Takes the cage of `key` away and gives its bars, where they are in the world now: centre, turn (the bar along its y) and length. */
|
|
1457
|
+
take(key) {
|
|
1458
|
+
const mesh = this.cages.get(key);
|
|
1459
|
+
if (!mesh) return [];
|
|
1460
|
+
mesh.updateWorldMatrix(true, false);
|
|
1461
|
+
const bars = [];
|
|
1462
|
+
for (let i = 0; i < mesh.count; i++) {
|
|
1463
|
+
mesh.getMatrixAt(i, this.m);
|
|
1464
|
+
this.m.premultiply(mesh.matrixWorld).decompose(this.mid, this.q, this.scale);
|
|
1465
|
+
bars.push({ center: [this.mid.x, this.mid.y, this.mid.z], rotation: [this.q.x, this.q.y, this.q.z, this.q.w], length: this.scale.y });
|
|
1466
|
+
}
|
|
1467
|
+
this.remove(key);
|
|
1468
|
+
return bars;
|
|
1469
|
+
}
|
|
1470
|
+
/** Radius of the bars drawn, metres. */
|
|
1471
|
+
get barRadius() {
|
|
1472
|
+
return this.radius;
|
|
1473
|
+
}
|
|
1213
1474
|
remove(key) {
|
|
1214
1475
|
const mesh = this.cages.get(key);
|
|
1215
1476
|
if (!mesh) return;
|
|
1216
|
-
|
|
1477
|
+
mesh.removeFromParent();
|
|
1217
1478
|
mesh.dispose();
|
|
1218
1479
|
this.cages.delete(key);
|
|
1219
1480
|
}
|
|
@@ -1556,6 +1817,23 @@ var DecalKit = class {
|
|
|
1556
1817
|
}
|
|
1557
1818
|
};
|
|
1558
1819
|
var tmpV = (THREE) => new THREE.Vector3();
|
|
1820
|
+
function plainPositions(mesh, v0, v1) {
|
|
1821
|
+
const { collapsedRanges, plainPositions: plain } = mesh.userData;
|
|
1822
|
+
if (!plain || !collapsedRanges?.some(([a, b]) => a < v1 && b > v0)) return null;
|
|
1823
|
+
return plain;
|
|
1824
|
+
}
|
|
1825
|
+
function showCollapsedRanges(mesh) {
|
|
1826
|
+
const data = mesh.userData;
|
|
1827
|
+
const plain = data.plainPositions;
|
|
1828
|
+
const position = mesh.geometry?.getAttribute(POS_ATTR);
|
|
1829
|
+
if (!plain || !data.collapsedRanges?.length || !position) return;
|
|
1830
|
+
const array = position.array;
|
|
1831
|
+
for (const [v0, v1] of data.collapsedRanges) array.set(plain.subarray(v0 * 3, v1 * 3), v0 * 3);
|
|
1832
|
+
data.collapsedRanges = [];
|
|
1833
|
+
position.needsUpdate = true;
|
|
1834
|
+
mesh.geometry.computeBoundingBox();
|
|
1835
|
+
mesh.geometry.computeBoundingSphere();
|
|
1836
|
+
}
|
|
1559
1837
|
var MIN_DENT_VERTICES = 10;
|
|
1560
1838
|
var WIDEN_MARGIN = 1.1;
|
|
1561
1839
|
var SKIN_FOLLOW = 0.85;
|
|
@@ -2321,7 +2599,7 @@ var Visuals = class {
|
|
|
2321
2599
|
const a = src.getAttribute(name);
|
|
2322
2600
|
if (a.isInterleavedBufferAttribute) continue;
|
|
2323
2601
|
const size = a.itemSize;
|
|
2324
|
-
const from = (name === POS_ATTR ? host.work : null) ?? a.array;
|
|
2602
|
+
const from = (name === POS_ATTR ? plainPositions(host.mesh, v0, v1) ?? host.work : null) ?? a.array;
|
|
2325
2603
|
const slice = from.slice(v0 * size, v1 * size);
|
|
2326
2604
|
const Ctor = a.array.constructor;
|
|
2327
2605
|
geo.setAttribute(name, new this.THREE.BufferAttribute(new Ctor(slice), size, a.normalized));
|
|
@@ -2479,11 +2757,29 @@ var Visuals = class {
|
|
|
2479
2757
|
// src/renderer.ts
|
|
2480
2758
|
var rendererCount = 0;
|
|
2481
2759
|
var COLLAPSE_CLOUD_KG = 1e3;
|
|
2760
|
+
var REBAR_COLOR = 7628380;
|
|
2761
|
+
var STEEL_DENSITY = 7850;
|
|
2482
2762
|
var CHIP_DECAL_EDGE = 0.05;
|
|
2483
2763
|
var WOOD_RIM = 1.5;
|
|
2484
2764
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
2485
2765
|
var GLOW_FROM_C = 430;
|
|
2486
2766
|
var QUEUE_CAP = 4e3;
|
|
2767
|
+
var STRUCTURAL_EVENTS = /* @__PURE__ */ new Set([
|
|
2768
|
+
"fracture",
|
|
2769
|
+
"detach",
|
|
2770
|
+
"collapse",
|
|
2771
|
+
"settle",
|
|
2772
|
+
"destroyed",
|
|
2773
|
+
"expose",
|
|
2774
|
+
"stage",
|
|
2775
|
+
"hole",
|
|
2776
|
+
"ignite",
|
|
2777
|
+
"primed",
|
|
2778
|
+
"extinguish",
|
|
2779
|
+
"burnout",
|
|
2780
|
+
"leak",
|
|
2781
|
+
"explode"
|
|
2782
|
+
]);
|
|
2487
2783
|
var MIN_FRAGMENTS_PER_SLICE = 8;
|
|
2488
2784
|
var STATIC_REFRESH = 0.5;
|
|
2489
2785
|
var STATIC_REFRESH_SLICE_MS = 1;
|
|
@@ -2592,6 +2888,8 @@ var Renderer = class {
|
|
|
2592
2888
|
__publicField(this, "pieceBoxes", /* @__PURE__ */ new WeakMap());
|
|
2593
2889
|
__publicField(this, "staticAt", 0);
|
|
2594
2890
|
__publicField(this, "queue", []);
|
|
2891
|
+
/** 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. */
|
|
2892
|
+
__publicField(this, "coverWhenWentOff", /* @__PURE__ */ new WeakMap());
|
|
2595
2893
|
/** Presentation work an event left for later frames (fragments still to spawn, loose groups still to split), run in order within the frame budget. */
|
|
2596
2894
|
__publicField(this, "slices", []);
|
|
2597
2895
|
/** When the work of this frame must stop; `Infinity` outside a drain, where a call finishes what it started. */
|
|
@@ -2671,7 +2969,8 @@ var Renderer = class {
|
|
|
2671
2969
|
groundAt: this.groundAt,
|
|
2672
2970
|
gravity: this.gravity,
|
|
2673
2971
|
max: this.maxFragments,
|
|
2674
|
-
life: o.fragmentLife ?? 12
|
|
2972
|
+
life: o.fragmentLife ?? 12,
|
|
2973
|
+
formOf: (id) => this.world.materials.get(id)?.splitForm
|
|
2675
2974
|
});
|
|
2676
2975
|
this.cages = new RebarCages(THREE, o.scene);
|
|
2677
2976
|
this.pieceSys = new PieceSystem(THREE, {
|
|
@@ -2684,7 +2983,8 @@ var Renderer = class {
|
|
|
2684
2983
|
onSlam: (p, hit, speed) => this.slam(p, hit, speed)
|
|
2685
2984
|
});
|
|
2686
2985
|
this.unsubscribe = this.world.on("*", (ev) => {
|
|
2687
|
-
if (
|
|
2986
|
+
if (ev.type === "explode") this.coverWhenWentOff.set(ev, this.coverAround(ev));
|
|
2987
|
+
if (this.queue.length < QUEUE_CAP || STRUCTURAL_EVENTS.has(ev.type)) this.queue.push(ev);
|
|
2688
2988
|
});
|
|
2689
2989
|
}
|
|
2690
2990
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -3207,7 +3507,8 @@ var Renderer = class {
|
|
|
3207
3507
|
this.onChip(ev);
|
|
3208
3508
|
break;
|
|
3209
3509
|
case "expose":
|
|
3210
|
-
this.cages.set(`${ev.object}|${ev.part}`, ev.bars);
|
|
3510
|
+
if (ev.bars.length > 0) this.cages.set(`${ev.object}|${ev.part}`, ev.bars, this.bounds.get(ev.object)?.root);
|
|
3511
|
+
else this.dropCage(ev.object, ev.part);
|
|
3211
3512
|
if (ev.bars.length > 0) {
|
|
3212
3513
|
const [a, b] = ev.bars[0];
|
|
3213
3514
|
this.playEvent({ kind: "expose" }, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2]);
|
|
@@ -3505,7 +3806,7 @@ var Renderer = class {
|
|
|
3505
3806
|
const rotation = new T.Quaternion(ev.rotation[0], ev.rotation[1], ev.rotation[2], ev.rotation[3]);
|
|
3506
3807
|
for (const part of this.bounds.get(ev.object)?.parts.values() ?? []) for (const mv of part.meshes) this.visuals.clearDecals(mv, center, half, rotation);
|
|
3507
3808
|
this.vfx.peekWorkshop()?.decals.clearInside(ev.center, [half.x, half.y, half.z], ev.rotation);
|
|
3508
|
-
this.vfx.burst("dust", ev.center, [0, 1, 0], 8, 0.8, { color });
|
|
3809
|
+
this.vfx.burst("dust", ev.center, [0, 1, 0], ev.units ? Math.min(40, 8 + ev.units.length) : 8, ev.units ? 1.6 : 0.8, { color });
|
|
3509
3810
|
this.impactSound(pv?.material ?? "", ev.center, 0.7, ev.blow);
|
|
3510
3811
|
}
|
|
3511
3812
|
onFracture(ev) {
|
|
@@ -3524,6 +3825,7 @@ var Renderer = class {
|
|
|
3524
3825
|
this.visuals.hide(pv, "broken");
|
|
3525
3826
|
}
|
|
3526
3827
|
this.spawnFragments(ev, 0, { def, color, opacity, transparent });
|
|
3828
|
+
this.loosenIfFootless(ev.object);
|
|
3527
3829
|
const up = [0, 1, 0];
|
|
3528
3830
|
const sizeK = clamp(ev.fragments.length / 10, 0.5, 2);
|
|
3529
3831
|
switch (ev.style) {
|
|
@@ -3569,6 +3871,33 @@ var Renderer = class {
|
|
|
3569
3871
|
this.sound("detach", this.rootPoint(ev.object), 0.5);
|
|
3570
3872
|
this.splitLoose(ev.object);
|
|
3571
3873
|
}
|
|
3874
|
+
/**
|
|
3875
|
+
* The cage of a part gave way (`expose` with no bars): nothing holds its bars any more, so each comes down where it is, as steel debris that stays on the
|
|
3876
|
+
* ground with a body (`FragmentSystem.spawn` with `lasting`), not as bars left standing in the air.
|
|
3877
|
+
*/
|
|
3878
|
+
dropCage(objectId, partId) {
|
|
3879
|
+
const bars = this.cages.take(`${objectId}|${partId}`);
|
|
3880
|
+
if (bars.length === 0) return;
|
|
3881
|
+
const radius = this.cages.barRadius;
|
|
3882
|
+
const density = this.world.materials.get("steel")?.density ?? STEEL_DENSITY;
|
|
3883
|
+
const material = this.fragments.materialFor("steel", REBAR_COLOR, false);
|
|
3884
|
+
const rnd = createRng(hashString(`${objectId}|${partId}`));
|
|
3885
|
+
bars.forEach((bar, i) => {
|
|
3886
|
+
const fragment = {
|
|
3887
|
+
id: `${partId}#bar${i}`,
|
|
3888
|
+
part: partId,
|
|
3889
|
+
material: "steel",
|
|
3890
|
+
shape: "chunk",
|
|
3891
|
+
center: bar.center,
|
|
3892
|
+
size: [2 * radius, bar.length, 2 * radius],
|
|
3893
|
+
rotation: bar.rotation,
|
|
3894
|
+
velocity: [(rnd() - 0.5) * 0.4, 0, (rnd() - 0.5) * 0.4],
|
|
3895
|
+
angularVelocity: [(rnd() - 0.5) * 1.5, (rnd() - 0.5) * 0.5, (rnd() - 0.5) * 1.5],
|
|
3896
|
+
mass: density * Math.PI * radius * radius * bar.length
|
|
3897
|
+
};
|
|
3898
|
+
this.fragments.spawn(fragment, material, objectId, this.maxFragments, true);
|
|
3899
|
+
});
|
|
3900
|
+
}
|
|
3572
3901
|
/** Turn every loose group of the object (a detach, a collapse) into a piece of its own. */
|
|
3573
3902
|
splitLoose(objectId) {
|
|
3574
3903
|
if (!this.autoSplit) return;
|
|
@@ -3623,10 +3952,12 @@ var Renderer = class {
|
|
|
3623
3952
|
if (!pv) continue;
|
|
3624
3953
|
from.parts.delete(p.id);
|
|
3625
3954
|
nb.parts.set(p.id, pv);
|
|
3955
|
+
this.cages.move(`${from.id}|${p.id}`, `${newId}|${p.id}`, group);
|
|
3626
3956
|
for (const mv of pv.meshes) {
|
|
3627
3957
|
if (mv.host && !mv.loose) this.visuals.extract(mv, group);
|
|
3628
3958
|
else {
|
|
3629
3959
|
group.attach(mv.mesh);
|
|
3960
|
+
showCollapsedRanges(mv.mesh);
|
|
3630
3961
|
mv.moved = true;
|
|
3631
3962
|
}
|
|
3632
3963
|
}
|
|
@@ -3653,6 +3984,45 @@ var Renderer = class {
|
|
|
3653
3984
|
if (kitOriginOf(from.root)?.kind === "furniture") this.tipIfTopHeavy(nb);
|
|
3654
3985
|
this.o.onPiece?.(piece);
|
|
3655
3986
|
}
|
|
3987
|
+
/**
|
|
3988
|
+
* 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
|
|
3989
|
+
* 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.
|
|
3990
|
+
*/
|
|
3991
|
+
loosenIfFootless(objectId) {
|
|
3992
|
+
const b = this.bounds.get(objectId);
|
|
3993
|
+
if (!b || !this.autoSplit) return;
|
|
3994
|
+
const left = this.meshesOf(b).filter((mv) => this.visuals.shown(mv));
|
|
3995
|
+
if (left.length === 0) return;
|
|
3996
|
+
const body = b.piece ?? this.pieceSys.get(objectId);
|
|
3997
|
+
if (body) {
|
|
3998
|
+
const { half, center } = this.measure(b.root, left);
|
|
3999
|
+
body.halfExtents = half;
|
|
4000
|
+
this.pieceSys.setCenter(body, center);
|
|
4001
|
+
body.mass = Math.max(0.05, this.massOf(objectId));
|
|
4002
|
+
body.sleeping = false;
|
|
4003
|
+
return;
|
|
4004
|
+
}
|
|
4005
|
+
if (kitOriginOf(b.root)?.kind !== "furniture") return;
|
|
4006
|
+
const boxOf = (mv) => this.visuals.worldBox(mv, new this.THREE.Box3());
|
|
4007
|
+
const whole = [...b.parts.values()].flatMap((pv) => pv.meshes).map(boxOf).filter((one) => !one.isEmpty());
|
|
4008
|
+
const floor = Math.min(...whole.map((one) => one.min.y));
|
|
4009
|
+
const feetOf = (boxes) => boxes.filter((one) => one.min.y < floor + FEET_BAND).reduce((feet, one) => feet.union(one), new this.THREE.Box3());
|
|
4010
|
+
const was = feetOf(whole);
|
|
4011
|
+
const now = feetOf(left.map(boxOf).filter((one) => !one.isEmpty()));
|
|
4012
|
+
const same = !now.isEmpty() && now.min.x <= was.min.x + FEET_BAND && now.max.x >= was.max.x - FEET_BAND && now.min.z <= was.min.z + FEET_BAND && now.max.z >= was.max.z - FEET_BAND;
|
|
4013
|
+
if (same) return;
|
|
4014
|
+
if (!now.isEmpty()) {
|
|
4015
|
+
const box = left.map(boxOf).reduce((all, one) => all.union(one), new this.THREE.Box3());
|
|
4016
|
+
const centreX = (box.min.x + box.max.x) / 2;
|
|
4017
|
+
const centreZ = (box.min.z + box.max.z) / 2;
|
|
4018
|
+
const height = box.max.y - box.min.y;
|
|
4019
|
+
const narrow = height >= TIP_MIN_HEIGHT && Math.min(now.max.x - now.min.x, now.max.z - now.min.z) < TIP_NARROW * height;
|
|
4020
|
+
if (!narrow && centreX > now.min.x && centreX < now.max.x && centreZ > now.min.z && centreZ < now.max.z) return;
|
|
4021
|
+
}
|
|
4022
|
+
const piece = this.makePiece(objectId);
|
|
4023
|
+
this.tipIfTopHeavy(b);
|
|
4024
|
+
this.o.onLoosened?.(objectId, piece, "apart");
|
|
4025
|
+
}
|
|
3656
4026
|
/**
|
|
3657
4027
|
* 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
|
|
3658
4028
|
* high and its feet are thin, so it gets the start of the fall, across the line of its feet.
|
|
@@ -3684,7 +4054,8 @@ var Renderer = class {
|
|
|
3684
4054
|
const report = options.report;
|
|
3685
4055
|
const confinement = report?.find((r) => r.blast !== void 0)?.blast?.confinement ?? 1;
|
|
3686
4056
|
const energy = ev.energy * confinement;
|
|
3687
|
-
const
|
|
4057
|
+
const wentOff = this.coverWhenWentOff.get(ev);
|
|
4058
|
+
const reachOf = (objectId, at) => report?.find((r) => r.object === objectId)?.blast?.shelter ?? wentOff?.get(objectId) ?? this.world.blastReach?.(p, at, { ignore: ev.object ? [ev.object, objectId] : [objectId] }) ?? 1;
|
|
3688
4059
|
const shove = (pc) => {
|
|
3689
4060
|
this.pieceSys.centerOf(pc, c);
|
|
3690
4061
|
if (c.distanceTo(this.v2.set(p[0], p[1], p[2])) > ev.radius) return;
|
|
@@ -3704,6 +4075,29 @@ var Renderer = class {
|
|
|
3704
4075
|
...report ? { confinement } : {}
|
|
3705
4076
|
});
|
|
3706
4077
|
}
|
|
4078
|
+
/** The cover between a part of the world that goes off and the centre of each piece and each shoveable body within its radius, as the world stands at that moment. */
|
|
4079
|
+
coverAround(ev) {
|
|
4080
|
+
const cover = /* @__PURE__ */ new Map();
|
|
4081
|
+
const reach = this.world.blastReach;
|
|
4082
|
+
if (!reach) return cover;
|
|
4083
|
+
const point = new this.THREE.Vector3(ev.point[0], ev.point[1], ev.point[2]);
|
|
4084
|
+
const centre = new this.THREE.Vector3();
|
|
4085
|
+
const read = (objectId) => {
|
|
4086
|
+
if (centre.distanceTo(point) <= ev.radius) cover.set(objectId, reach.call(this.world, ev.point, [centre.x, centre.y, centre.z], { ignore: [ev.object, objectId] }));
|
|
4087
|
+
};
|
|
4088
|
+
for (const pc of this.pieceSys.list) {
|
|
4089
|
+
this.pieceSys.centerOf(pc, centre);
|
|
4090
|
+
read(pc.objectId);
|
|
4091
|
+
}
|
|
4092
|
+
const shoveable = this.o.shoveable;
|
|
4093
|
+
if (!shoveable) return cover;
|
|
4094
|
+
for (const b of this.bounds.values()) {
|
|
4095
|
+
if (b.piece || cover.has(b.id) || !shoveable(b.id) || !this.world.has(b.id)) continue;
|
|
4096
|
+
centre.copy(this.measure(b.root, this.meshesOf(b)).center).applyQuaternion(b.root.quaternion).add(b.root.position);
|
|
4097
|
+
read(b.id);
|
|
4098
|
+
}
|
|
4099
|
+
return cover;
|
|
4100
|
+
}
|
|
3707
4101
|
/** The bound objects without a piece that the host lets a blast move (`shoveable`): the ones it reaches with enough speed become pieces and get the shove. */
|
|
3708
4102
|
loosen(point, radius, energy, reachOf) {
|
|
3709
4103
|
const shoveable = this.o.shoveable;
|