@adatechnology/game-destruction-three 0.3.3 → 0.5.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 +6 -3
- package/dist/index.d.ts +30 -7
- package/dist/index.global.js +8190 -538
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +284 -454
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -2,6 +2,10 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
|
+
// src/renderer.ts
|
|
6
|
+
import { createVfx, effectForEvent, EVENT_EFFECTS_MAP } from "@adatechnology/game-vfx";
|
|
7
|
+
import { createVfxRenderer } from "@adatechnology/game-vfx-three";
|
|
8
|
+
|
|
5
9
|
// src/math.ts
|
|
6
10
|
function createRng(seed) {
|
|
7
11
|
let a = seed >>> 0;
|
|
@@ -29,9 +33,6 @@ function hashString(s) {
|
|
|
29
33
|
}
|
|
30
34
|
var clamp = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
|
|
31
35
|
var lerp = (a, b, t) => a + (b - a) * t;
|
|
32
|
-
function hexToRgb(hex) {
|
|
33
|
-
return [(hex >> 16 & 255) / 255, (hex >> 8 & 255) / 255, (hex & 255) / 255];
|
|
34
|
-
}
|
|
35
36
|
function mixHex(a, b, t) {
|
|
36
37
|
const ar = a >> 16 & 255;
|
|
37
38
|
const ag = a >> 8 & 255;
|
|
@@ -57,6 +58,8 @@ var FragmentSystem = class {
|
|
|
57
58
|
__publicField(this, "v");
|
|
58
59
|
__publicField(this, "w");
|
|
59
60
|
__publicField(this, "rng", createRng(1374496523));
|
|
61
|
+
__publicField(this, "markGeometry", null);
|
|
62
|
+
__publicField(this, "markMaterial", null);
|
|
60
63
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
61
64
|
this.q = new THREE.Quaternion();
|
|
62
65
|
this.dq = new THREE.Quaternion();
|
|
@@ -103,6 +106,25 @@ var FragmentSystem = class {
|
|
|
103
106
|
}
|
|
104
107
|
return g;
|
|
105
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* 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
|
|
111
|
+
* a unit box scaled to the fragment, so each disc is scaled back; exact for a mark on a face, which is where a bullet hole is.
|
|
112
|
+
*/
|
|
113
|
+
addMarks(mesh, f) {
|
|
114
|
+
if (!f.marks?.length) return;
|
|
115
|
+
this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
|
|
116
|
+
this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
|
|
117
|
+
const sc = mesh.scale;
|
|
118
|
+
for (const mark of f.marks) {
|
|
119
|
+
const disc = new this.THREE.Mesh(this.markGeometry, this.markMaterial);
|
|
120
|
+
const radius = Math.max(4e-3, mark.diameter) * 0.6;
|
|
121
|
+
const n = this.v.set(mark.normal[0], mark.normal[1], mark.normal[2]).normalize();
|
|
122
|
+
disc.position.set(mark.point[0] / sc.x + n.x * 2e-3 / sc.x, mark.point[1] / sc.y + n.y * 2e-3 / sc.y, mark.point[2] / sc.z + n.z * 2e-3 / sc.z);
|
|
123
|
+
disc.quaternion.setFromUnitVectors(this.w.set(0, 0, 1), n);
|
|
124
|
+
disc.scale.set(radius / sc.x, radius / sc.y, radius / sc.z);
|
|
125
|
+
mesh.add(disc);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
106
128
|
/** Create the mesh of a fragment and start its motion. `maxAllowed` is the live budget. */
|
|
107
129
|
spawn(f, material, home, maxAllowed) {
|
|
108
130
|
const geo = f.shape === "shard" ? this.shardGeometry(hashString(f.id)) : this.box;
|
|
@@ -136,6 +158,8 @@ var FragmentSystem = class {
|
|
|
136
158
|
alive--;
|
|
137
159
|
}
|
|
138
160
|
}
|
|
161
|
+
this.addMarks(mesh, f);
|
|
162
|
+
return mesh;
|
|
139
163
|
}
|
|
140
164
|
/** Add velocity to every fragment within `radius` of a point (blasts). */
|
|
141
165
|
blast(point, radius, speed) {
|
|
@@ -259,6 +283,8 @@ var FragmentSystem = class {
|
|
|
259
283
|
dispose() {
|
|
260
284
|
this.clear();
|
|
261
285
|
this.box.dispose();
|
|
286
|
+
this.markGeometry?.dispose();
|
|
287
|
+
this.markMaterial?.dispose();
|
|
262
288
|
for (const g of this.shardGeos) g?.dispose();
|
|
263
289
|
this.shardGeos = [];
|
|
264
290
|
for (const m of this.materials.values()) m.dispose();
|
|
@@ -347,345 +373,6 @@ var GroundSpots = class {
|
|
|
347
373
|
}
|
|
348
374
|
};
|
|
349
375
|
|
|
350
|
-
// src/particles.ts
|
|
351
|
-
var KINDS = {
|
|
352
|
-
fire: { additive: true, life: [0.45, 0.9], size: [0.1, 0.2], grow: 0.3, gravity: 0, rise: 2.4, drag: 1.2, c0: 16760896, c1: 12591104, alpha: 0.9, fadeIn: 0.12, cone: 0.7, ground: "none", flicker: 0.35 },
|
|
353
|
-
smoke: { additive: false, life: [1.8, 3.2], size: [0.14, 0.24], grow: 3.2, gravity: 0, rise: 0.7, drag: 0.8, c0: 6710886, c1: 10132122, alpha: 0.35, fadeIn: 0.15, cone: 0.6, ground: "none" },
|
|
354
|
-
sparks: { additive: true, life: [0.25, 0.6], size: [0.018, 0.03], grow: 0.5, gravity: 1, rise: 0, drag: 0.3, c0: 16773792, c1: 16738832, alpha: 1, fadeIn: 0.02, cone: 0.55, ground: "bounce" },
|
|
355
|
-
dust: { additive: false, life: [1, 2], size: [0.08, 0.14], grow: 2.5, gravity: 0.05, rise: 0, drag: 2.5, c0: 9274745, c1: 9274745, alpha: 0.4, fadeIn: 0.1, cone: 0.4, ground: "none" },
|
|
356
|
-
shards: { additive: true, life: [0.8, 1.4], size: [0.02, 0.035], grow: 0.8, gravity: 1, rise: 0, drag: 0.2, c0: 14677247, c1: 10473727, alpha: 0.9, fadeIn: 0.03, cone: 0.5, ground: "bounce", flicker: 0.5 },
|
|
357
|
-
splinters: { additive: false, life: [0.8, 1.5], size: [0.02, 0.035], grow: 1, gravity: 1, rise: 0, drag: 0.3, c0: 10516042, c1: 8017200, alpha: 1, fadeIn: 0.02, cone: 0.5, ground: "bounce" },
|
|
358
|
-
chips: { additive: false, life: [0.8, 1.5], size: [0.025, 0.04], grow: 1, gravity: 1, rise: 0, drag: 0.3, c0: 9077624, c1: 7235679, alpha: 1, fadeIn: 0.02, cone: 0.5, ground: "bounce" },
|
|
359
|
-
fibers: { additive: false, life: [1.5, 2.5], size: [0.02, 0.03], grow: 1, gravity: 0.15, rise: 0, drag: 2, c0: 12101768, c1: 12101768, alpha: 0.9, fadeIn: 0.05, cone: 0.4, ground: "stop", flutter: 2.5 },
|
|
360
|
-
leaves: { additive: false, life: [1.5, 2.8], size: [0.03, 0.05], grow: 1, gravity: 0.15, rise: 0, drag: 2, c0: 6003260, c1: 4882224, alpha: 0.95, fadeIn: 0.05, cone: 0.4, ground: "stop", flutter: 2.8 },
|
|
361
|
-
drips: { additive: false, life: [2, 3], size: [0.025, 0.035], grow: 1, gravity: 1, rise: 0, drag: 0, c0: 6974058, c1: 6974058, alpha: 1, fadeIn: 0.01, cone: 0.2, ground: "land" },
|
|
362
|
-
ash: { additive: false, life: [2, 4], size: [0.02, 0.04], grow: 1.2, gravity: 0.08, rise: 0, drag: 1.5, c0: 2762275, c1: 1710102, alpha: 0.8, fadeIn: 0.1, cone: 0.5, ground: "stop", flutter: 0.6 },
|
|
363
|
-
steam: { additive: false, life: [1, 2], size: [0.12, 0.2], grow: 3, gravity: 0, rise: 0.9, drag: 1, c0: 16777215, c1: 14739694, alpha: 0.35, fadeIn: 0.15, cone: 0.6, ground: "none" },
|
|
364
|
-
flash: { additive: true, life: [0.12, 0.2], size: [1.2, 1.8], grow: 1.6, gravity: 0, rise: 0, drag: 0, c0: 16774080, c1: 16748592, alpha: 1, fadeIn: 0.1, cone: 0, ground: "none" },
|
|
365
|
-
foam: { additive: false, life: [1, 1.8], size: [0.06, 0.1], grow: 1.5, gravity: 0.6, rise: 0, drag: 1.5, c0: 16054527, c1: 14542576, alpha: 0.8, fadeIn: 0.05, cone: 0.5, ground: "stop" }
|
|
366
|
-
};
|
|
367
|
-
var KIND_IDS = Object.keys(KINDS);
|
|
368
|
-
var KIND_INDEX = {};
|
|
369
|
-
KIND_IDS.forEach((k, i) => KIND_INDEX[k] = i);
|
|
370
|
-
var KIND_LIST = KIND_IDS.map((k) => KINDS[k]);
|
|
371
|
-
var VERT = `
|
|
372
|
-
uniform float uScale;
|
|
373
|
-
attribute float size;
|
|
374
|
-
attribute float alpha;
|
|
375
|
-
attribute vec3 pcolor;
|
|
376
|
-
varying float vAlpha;
|
|
377
|
-
varying vec3 vColor;
|
|
378
|
-
void main() {
|
|
379
|
-
vAlpha = alpha;
|
|
380
|
-
vColor = pcolor;
|
|
381
|
-
vec4 mv = modelViewMatrix * vec4(position, 1.0);
|
|
382
|
-
gl_PointSize = min(size * uScale / max(0.1, -mv.z), 256.0);
|
|
383
|
-
gl_Position = projectionMatrix * mv;
|
|
384
|
-
}`;
|
|
385
|
-
var FRAG = `
|
|
386
|
-
varying float vAlpha;
|
|
387
|
-
varying vec3 vColor;
|
|
388
|
-
void main() {
|
|
389
|
-
float d = length(gl_PointCoord - vec2(0.5)) * 2.0;
|
|
390
|
-
float a = (1.0 - smoothstep(0.35, 1.0, d)) * vAlpha;
|
|
391
|
-
if (a < 0.004) discard;
|
|
392
|
-
gl_FragColor = vec4(vColor, a);
|
|
393
|
-
}`;
|
|
394
|
-
var Pool = class {
|
|
395
|
-
constructor(THREE, capacity, additive, pointScale) {
|
|
396
|
-
__publicField(this, "capacity", capacity);
|
|
397
|
-
__publicField(this, "points");
|
|
398
|
-
__publicField(this, "geometry");
|
|
399
|
-
__publicField(this, "material");
|
|
400
|
-
__publicField(this, "live", 0);
|
|
401
|
-
__publicField(this, "next", 0);
|
|
402
|
-
__publicField(this, "high", 0);
|
|
403
|
-
__publicField(this, "pos");
|
|
404
|
-
__publicField(this, "col");
|
|
405
|
-
__publicField(this, "alp");
|
|
406
|
-
__publicField(this, "siz");
|
|
407
|
-
__publicField(this, "vel");
|
|
408
|
-
__publicField(this, "age");
|
|
409
|
-
__publicField(this, "life");
|
|
410
|
-
__publicField(this, "size0");
|
|
411
|
-
__publicField(this, "alive");
|
|
412
|
-
__publicField(this, "kind");
|
|
413
|
-
__publicField(this, "seed");
|
|
414
|
-
__publicField(this, "alphaMul");
|
|
415
|
-
__publicField(this, "tag");
|
|
416
|
-
__publicField(this, "colorHex");
|
|
417
|
-
__publicField(this, "rgbStart");
|
|
418
|
-
__publicField(this, "rgbEnd");
|
|
419
|
-
__publicField(this, "bounced");
|
|
420
|
-
const n = Math.max(1, capacity);
|
|
421
|
-
this.pos = new Float32Array(n * 3);
|
|
422
|
-
this.col = new Float32Array(n * 3);
|
|
423
|
-
this.alp = new Float32Array(n);
|
|
424
|
-
this.siz = new Float32Array(n);
|
|
425
|
-
this.vel = new Float32Array(n * 3);
|
|
426
|
-
this.age = new Float32Array(n);
|
|
427
|
-
this.life = new Float32Array(n);
|
|
428
|
-
this.size0 = new Float32Array(n);
|
|
429
|
-
this.alive = new Uint8Array(n);
|
|
430
|
-
this.kind = new Uint8Array(n);
|
|
431
|
-
this.seed = new Float32Array(n);
|
|
432
|
-
this.alphaMul = new Float32Array(n);
|
|
433
|
-
this.tag = new Int32Array(n);
|
|
434
|
-
this.colorHex = new Int32Array(n);
|
|
435
|
-
this.rgbStart = new Float32Array(n * 3);
|
|
436
|
-
this.rgbEnd = new Float32Array(n * 3);
|
|
437
|
-
this.bounced = new Uint8Array(n);
|
|
438
|
-
this.geometry = new THREE.BufferGeometry();
|
|
439
|
-
const attr = (a, size) => {
|
|
440
|
-
const at = new THREE.BufferAttribute(a, size);
|
|
441
|
-
at.setUsage(THREE.DynamicDrawUsage);
|
|
442
|
-
return at;
|
|
443
|
-
};
|
|
444
|
-
this.geometry.setAttribute("position", attr(this.pos, 3));
|
|
445
|
-
this.geometry.setAttribute("pcolor", attr(this.col, 3));
|
|
446
|
-
this.geometry.setAttribute("alpha", attr(this.alp, 1));
|
|
447
|
-
this.geometry.setAttribute("size", attr(this.siz, 1));
|
|
448
|
-
this.geometry.setDrawRange(0, 0);
|
|
449
|
-
this.material = new THREE.ShaderMaterial({
|
|
450
|
-
uniforms: { uScale: { value: pointScale } },
|
|
451
|
-
vertexShader: VERT,
|
|
452
|
-
fragmentShader: FRAG,
|
|
453
|
-
transparent: true,
|
|
454
|
-
depthWrite: false,
|
|
455
|
-
blending: additive ? THREE.AdditiveBlending : THREE.NormalBlending
|
|
456
|
-
});
|
|
457
|
-
this.points = new THREE.Points(this.geometry, this.material);
|
|
458
|
-
this.points.frustumCulled = false;
|
|
459
|
-
this.points.name = additive ? "destruction-particles-additive" : "destruction-particles";
|
|
460
|
-
this.points.renderOrder = additive ? 11 : 10;
|
|
461
|
-
}
|
|
462
|
-
spawn(kind, x, y, z, vx, vy, vz, def, rnd, o) {
|
|
463
|
-
if (this.capacity <= 0) return;
|
|
464
|
-
const i = this.next;
|
|
465
|
-
this.next = (this.next + 1) % this.capacity;
|
|
466
|
-
if (this.alive[i]) this.live--;
|
|
467
|
-
this.alive[i] = 1;
|
|
468
|
-
this.live++;
|
|
469
|
-
if (i + 1 > this.high) this.high = i + 1;
|
|
470
|
-
this.kind[i] = kind;
|
|
471
|
-
this.pos[i * 3] = x;
|
|
472
|
-
this.pos[i * 3 + 1] = y;
|
|
473
|
-
this.pos[i * 3 + 2] = z;
|
|
474
|
-
this.vel[i * 3] = vx;
|
|
475
|
-
this.vel[i * 3 + 1] = vy;
|
|
476
|
-
this.vel[i * 3 + 2] = vz;
|
|
477
|
-
this.age[i] = 0;
|
|
478
|
-
this.life[i] = lerp(def.life[0], def.life[1], rnd()) * (o?.life ?? 1);
|
|
479
|
-
this.size0[i] = lerp(def.size[0], def.size[1], rnd()) * (o?.size ?? 1);
|
|
480
|
-
this.seed[i] = rnd();
|
|
481
|
-
this.alphaMul[i] = o?.alpha ?? 1;
|
|
482
|
-
this.tag[i] = o?.tag ?? 0;
|
|
483
|
-
this.colorHex[i] = o?.color ?? -1;
|
|
484
|
-
this.rgbStart.set(hexToRgb(o?.color ?? def.c0), i * 3);
|
|
485
|
-
this.rgbEnd.set(hexToRgb(o?.color ?? def.c1), i * 3);
|
|
486
|
-
this.bounced[i] = 0;
|
|
487
|
-
this.siz[i] = this.size0[i];
|
|
488
|
-
this.alp[i] = 0;
|
|
489
|
-
}
|
|
490
|
-
kill(i) {
|
|
491
|
-
if (!this.alive[i]) return;
|
|
492
|
-
this.alive[i] = 0;
|
|
493
|
-
this.live--;
|
|
494
|
-
this.alp[i] = 0;
|
|
495
|
-
this.siz[i] = 0;
|
|
496
|
-
}
|
|
497
|
-
clear() {
|
|
498
|
-
this.alive.fill(0);
|
|
499
|
-
this.alp.fill(0);
|
|
500
|
-
this.siz.fill(0);
|
|
501
|
-
this.live = 0;
|
|
502
|
-
this.high = 0;
|
|
503
|
-
this.next = 0;
|
|
504
|
-
this.geometry.setDrawRange(0, 0);
|
|
505
|
-
}
|
|
506
|
-
/** Move a live particle (used by external pushes such as blasts). */
|
|
507
|
-
push(cx, cy, cz, radius, speed) {
|
|
508
|
-
for (let i = 0; i < this.high; i++) {
|
|
509
|
-
if (!this.alive[i]) continue;
|
|
510
|
-
const dx = this.pos[i * 3] - cx;
|
|
511
|
-
const dy = this.pos[i * 3 + 1] - cy;
|
|
512
|
-
const dz = this.pos[i * 3 + 2] - cz;
|
|
513
|
-
const d = Math.hypot(dx, dy, dz);
|
|
514
|
-
if (d > radius || d < 1e-4) continue;
|
|
515
|
-
const f = (1 - d / radius) * speed;
|
|
516
|
-
this.vel[i * 3] += dx / d * f;
|
|
517
|
-
this.vel[i * 3 + 1] += dy / d * f;
|
|
518
|
-
this.vel[i * 3 + 2] += dz / d * f;
|
|
519
|
-
}
|
|
520
|
-
}
|
|
521
|
-
update(dt, host) {
|
|
522
|
-
let top = 0;
|
|
523
|
-
for (let i = 0; i < this.high; i++) {
|
|
524
|
-
if (!this.alive[i]) continue;
|
|
525
|
-
const def = KIND_LIST[this.kind[i]];
|
|
526
|
-
const age = this.age[i] += dt;
|
|
527
|
-
const life = this.life[i];
|
|
528
|
-
if (age >= life) {
|
|
529
|
-
this.kill(i);
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
const t = age / life;
|
|
533
|
-
const i3 = i * 3;
|
|
534
|
-
let vx = this.vel[i3];
|
|
535
|
-
let vy = this.vel[i3 + 1];
|
|
536
|
-
let vz = this.vel[i3 + 2];
|
|
537
|
-
vy += (def.rise - def.gravity * host.gravity) * dt;
|
|
538
|
-
if (def.flutter) {
|
|
539
|
-
const ph = age * 7 + this.seed[i] * 40;
|
|
540
|
-
vx += Math.sin(ph) * def.flutter * dt;
|
|
541
|
-
vz += Math.cos(ph * 1.3) * def.flutter * dt;
|
|
542
|
-
}
|
|
543
|
-
if (def.drag > 0) {
|
|
544
|
-
const k = Math.exp(-def.drag * dt);
|
|
545
|
-
vx *= k;
|
|
546
|
-
vy *= k;
|
|
547
|
-
vz *= k;
|
|
548
|
-
}
|
|
549
|
-
const x = this.pos[i3] + vx * dt;
|
|
550
|
-
let y = this.pos[i3 + 1] + vy * dt;
|
|
551
|
-
const z = this.pos[i3 + 2] + vz * dt;
|
|
552
|
-
if (def.ground !== "none") {
|
|
553
|
-
const gy = host.groundAt(x, z) + 5e-3;
|
|
554
|
-
if (y < gy) {
|
|
555
|
-
if (def.ground === "land") {
|
|
556
|
-
const tag = this.tag[i];
|
|
557
|
-
if (tag !== 0) host.onLand(x, gy, z, this.colorHex[i] >= 0 ? this.colorHex[i] : def.c0, tag);
|
|
558
|
-
this.kill(i);
|
|
559
|
-
continue;
|
|
560
|
-
}
|
|
561
|
-
y = gy;
|
|
562
|
-
if (def.ground === "bounce" && !this.bounced[i]) {
|
|
563
|
-
this.bounced[i] = 1;
|
|
564
|
-
vy = Math.abs(vy) * 0.35;
|
|
565
|
-
vx *= 0.6;
|
|
566
|
-
vz *= 0.6;
|
|
567
|
-
} else {
|
|
568
|
-
vx = vy = vz = 0;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
}
|
|
572
|
-
this.vel[i3] = vx;
|
|
573
|
-
this.vel[i3 + 1] = vy;
|
|
574
|
-
this.vel[i3 + 2] = vz;
|
|
575
|
-
this.pos[i3] = x;
|
|
576
|
-
this.pos[i3 + 1] = y;
|
|
577
|
-
this.pos[i3 + 2] = z;
|
|
578
|
-
this.siz[i] = this.size0[i] * lerp(1, def.grow, t);
|
|
579
|
-
this.col[i3] = lerp(this.rgbStart[i3], this.rgbEnd[i3], t);
|
|
580
|
-
this.col[i3 + 1] = lerp(this.rgbStart[i3 + 1], this.rgbEnd[i3 + 1], t);
|
|
581
|
-
this.col[i3 + 2] = lerp(this.rgbStart[i3 + 2], this.rgbEnd[i3 + 2], t);
|
|
582
|
-
let env = t < def.fadeIn ? t / def.fadeIn : 1 - (t - def.fadeIn) / (1 - def.fadeIn);
|
|
583
|
-
if (def.flicker) env *= 1 - def.flicker * (0.5 + 0.5 * Math.sin(age * 38 + this.seed[i] * 50));
|
|
584
|
-
this.alp[i] = clamp(env, 0, 1) * def.alpha * this.alphaMul[i];
|
|
585
|
-
top = i + 1;
|
|
586
|
-
}
|
|
587
|
-
this.high = top;
|
|
588
|
-
this.geometry.setDrawRange(0, top);
|
|
589
|
-
for (const name of ["position", "pcolor", "alpha", "size"]) {
|
|
590
|
-
;
|
|
591
|
-
this.geometry.getAttribute(name).needsUpdate = true;
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
dispose() {
|
|
595
|
-
this.geometry.dispose();
|
|
596
|
-
this.material.dispose();
|
|
597
|
-
}
|
|
598
|
-
};
|
|
599
|
-
var ParticleSystem = class {
|
|
600
|
-
constructor(THREE, o) {
|
|
601
|
-
__publicField(this, "o", o);
|
|
602
|
-
__publicField(this, "add");
|
|
603
|
-
__publicField(this, "normal");
|
|
604
|
-
__publicField(this, "rng", createRng(2654435761));
|
|
605
|
-
__publicField(this, "attached", false);
|
|
606
|
-
__publicField(this, "host");
|
|
607
|
-
const addCap = Math.floor(o.capacity * 0.4);
|
|
608
|
-
this.add = new Pool(THREE, addCap, true, o.pointScale);
|
|
609
|
-
this.normal = new Pool(THREE, o.capacity - addCap, false, o.pointScale);
|
|
610
|
-
this.host = { groundAt: o.groundAt, gravity: o.gravity, onLand: o.onLand };
|
|
611
|
-
}
|
|
612
|
-
get capacity() {
|
|
613
|
-
return this.add.capacity + this.normal.capacity;
|
|
614
|
-
}
|
|
615
|
-
get live() {
|
|
616
|
-
return this.add.live + this.normal.live;
|
|
617
|
-
}
|
|
618
|
-
ensureAttached() {
|
|
619
|
-
if (this.attached) return;
|
|
620
|
-
this.attached = true;
|
|
621
|
-
this.o.scene.add(this.add.points, this.normal.points);
|
|
622
|
-
}
|
|
623
|
-
/** Remove the Points from the scene (they come back on the next emit). */
|
|
624
|
-
detach() {
|
|
625
|
-
if (!this.attached) return;
|
|
626
|
-
this.attached = false;
|
|
627
|
-
this.o.scene.remove(this.add.points, this.normal.points);
|
|
628
|
-
}
|
|
629
|
-
/** Spawn one particle with an explicit velocity. */
|
|
630
|
-
spawn(kind, p, v, opts) {
|
|
631
|
-
const def = KINDS[kind];
|
|
632
|
-
this.ensureAttached();
|
|
633
|
-
(def.additive ? this.add : this.normal).spawn(KIND_INDEX[kind], p[0], p[1], p[2], v[0], v[1], v[2], def, this.rng, opts);
|
|
634
|
-
}
|
|
635
|
-
/** Spawn `count` particles leaving `point` around `normal` at about `speed` m/s. */
|
|
636
|
-
burst(kind, point, normal, count, speed = 1, opts) {
|
|
637
|
-
const def = KINDS[kind];
|
|
638
|
-
const n = Math.max(0, Math.round(count * this.o.density));
|
|
639
|
-
const rnd = this.rng;
|
|
640
|
-
const nl = Math.hypot(normal[0], normal[1], normal[2]) || 1;
|
|
641
|
-
const nx = normal[0] / nl;
|
|
642
|
-
const ny = normal[1] / nl;
|
|
643
|
-
const nz = normal[2] / nl;
|
|
644
|
-
for (let k = 0; k < n; k++) {
|
|
645
|
-
const u = rnd() * 2 - 1;
|
|
646
|
-
const ph = rnd() * Math.PI * 2;
|
|
647
|
-
const s = Math.sqrt(1 - u * u);
|
|
648
|
-
const rx = s * Math.cos(ph);
|
|
649
|
-
const ry = u;
|
|
650
|
-
const rz = s * Math.sin(ph);
|
|
651
|
-
let dx = nx * def.cone + rx * (1 - def.cone);
|
|
652
|
-
let dy = ny * def.cone + ry * (1 - def.cone);
|
|
653
|
-
let dz = nz * def.cone + rz * (1 - def.cone);
|
|
654
|
-
dx += rx * 0.35;
|
|
655
|
-
dy += ry * 0.35;
|
|
656
|
-
dz += rz * 0.35;
|
|
657
|
-
const l = Math.hypot(dx, dy, dz) || 1;
|
|
658
|
-
const sp = speed * (0.35 + 0.65 * rnd());
|
|
659
|
-
const jitter = 0.01 + (def.size[1] > 0.5 ? 0.15 : 0.02);
|
|
660
|
-
this.spawn(
|
|
661
|
-
kind,
|
|
662
|
-
[point[0] + (rnd() - 0.5) * jitter, point[1] + (rnd() - 0.5) * jitter, point[2] + (rnd() - 0.5) * jitter],
|
|
663
|
-
[dx / l * sp, dy / l * sp, dz / l * sp],
|
|
664
|
-
opts
|
|
665
|
-
);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
/** Blast wave: shove live particles away from a point. */
|
|
669
|
-
push(point, radius, speed) {
|
|
670
|
-
this.add.push(point[0], point[1], point[2], radius, speed);
|
|
671
|
-
this.normal.push(point[0], point[1], point[2], radius, speed);
|
|
672
|
-
}
|
|
673
|
-
update(dt) {
|
|
674
|
-
if (this.add.live > 0 || this.add.points.geometry.drawRange.count > 0) this.add.update(dt, this.host);
|
|
675
|
-
if (this.normal.live > 0 || this.normal.points.geometry.drawRange.count > 0) this.normal.update(dt, this.host);
|
|
676
|
-
}
|
|
677
|
-
clear() {
|
|
678
|
-
this.add.clear();
|
|
679
|
-
this.normal.clear();
|
|
680
|
-
this.detach();
|
|
681
|
-
}
|
|
682
|
-
dispose() {
|
|
683
|
-
this.detach();
|
|
684
|
-
this.add.dispose();
|
|
685
|
-
this.normal.dispose();
|
|
686
|
-
}
|
|
687
|
-
};
|
|
688
|
-
|
|
689
376
|
// src/pieces.ts
|
|
690
377
|
var SUB_DT = 1 / 120;
|
|
691
378
|
var MU = 0.7;
|
|
@@ -1010,6 +697,78 @@ var PieceSystem = class {
|
|
|
1010
697
|
}
|
|
1011
698
|
};
|
|
1012
699
|
|
|
700
|
+
// src/rebar.ts
|
|
701
|
+
var RebarCages = class {
|
|
702
|
+
constructor(THREE, scene, radius = 4e-3) {
|
|
703
|
+
__publicField(this, "THREE", THREE);
|
|
704
|
+
__publicField(this, "scene", scene);
|
|
705
|
+
__publicField(this, "cages", /* @__PURE__ */ new Map());
|
|
706
|
+
__publicField(this, "geometry");
|
|
707
|
+
__publicField(this, "material");
|
|
708
|
+
__publicField(this, "up");
|
|
709
|
+
__publicField(this, "dir");
|
|
710
|
+
__publicField(this, "mid");
|
|
711
|
+
__publicField(this, "q");
|
|
712
|
+
__publicField(this, "scale");
|
|
713
|
+
__publicField(this, "m");
|
|
714
|
+
this.geometry = new THREE.CylinderGeometry(radius, radius, 1, 6);
|
|
715
|
+
this.material = new THREE.MeshStandardMaterial({ color: 7628380, roughness: 0.55, metalness: 0.6 });
|
|
716
|
+
this.up = new THREE.Vector3(0, 1, 0);
|
|
717
|
+
this.dir = new THREE.Vector3();
|
|
718
|
+
this.mid = new THREE.Vector3();
|
|
719
|
+
this.q = new THREE.Quaternion();
|
|
720
|
+
this.scale = new THREE.Vector3();
|
|
721
|
+
this.m = new THREE.Matrix4();
|
|
722
|
+
}
|
|
723
|
+
/** Bars drawn now, over every part. */
|
|
724
|
+
get count() {
|
|
725
|
+
let n = 0;
|
|
726
|
+
for (const mesh of this.cages.values()) n += mesh.count;
|
|
727
|
+
return n;
|
|
728
|
+
}
|
|
729
|
+
/** Meshes of the live cages (tests and picking). */
|
|
730
|
+
meshes() {
|
|
731
|
+
return [...this.cages.values()];
|
|
732
|
+
}
|
|
733
|
+
/** Draw `bars` (world space) as the cage of `key`; an empty list removes it. */
|
|
734
|
+
set(key, bars) {
|
|
735
|
+
this.remove(key);
|
|
736
|
+
if (bars.length === 0) return;
|
|
737
|
+
const mesh = new this.THREE.InstancedMesh(this.geometry, this.material, bars.length);
|
|
738
|
+
bars.forEach(([a, b], i) => {
|
|
739
|
+
this.dir.set(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
740
|
+
const length = this.dir.length();
|
|
741
|
+
this.mid.set((a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2);
|
|
742
|
+
this.q.setFromUnitVectors(this.up, length > 1e-9 ? this.dir.divideScalar(length) : this.up);
|
|
743
|
+
this.m.compose(this.mid, this.q, this.scale.set(1, Math.max(length, 1e-4), 1));
|
|
744
|
+
mesh.setMatrixAt(i, this.m);
|
|
745
|
+
});
|
|
746
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
747
|
+
mesh.castShadow = true;
|
|
748
|
+
this.scene.add(mesh);
|
|
749
|
+
this.cages.set(key, mesh);
|
|
750
|
+
}
|
|
751
|
+
remove(key) {
|
|
752
|
+
const mesh = this.cages.get(key);
|
|
753
|
+
if (!mesh) return;
|
|
754
|
+
this.scene.remove(mesh);
|
|
755
|
+
mesh.dispose();
|
|
756
|
+
this.cages.delete(key);
|
|
757
|
+
}
|
|
758
|
+
/** Remove the cages of every part of `object` (keys are `object|part`). */
|
|
759
|
+
removeObject(object) {
|
|
760
|
+
for (const key of [...this.cages.keys()]) if (key.startsWith(`${object}|`)) this.remove(key);
|
|
761
|
+
}
|
|
762
|
+
clear() {
|
|
763
|
+
for (const key of [...this.cages.keys()]) this.remove(key);
|
|
764
|
+
}
|
|
765
|
+
dispose() {
|
|
766
|
+
this.clear();
|
|
767
|
+
this.geometry.dispose();
|
|
768
|
+
this.material.dispose();
|
|
769
|
+
}
|
|
770
|
+
};
|
|
771
|
+
|
|
1013
772
|
// src/spec.ts
|
|
1014
773
|
import { guessMaterial, inferJoints } from "@adatechnology/game-destruction";
|
|
1015
774
|
function partHintOf(value) {
|
|
@@ -1214,6 +973,7 @@ var DecalKit = class {
|
|
|
1214
973
|
__publicField(this, "disc", null);
|
|
1215
974
|
__publicField(this, "discMat", null);
|
|
1216
975
|
__publicField(this, "lineMats", /* @__PURE__ */ new Map());
|
|
976
|
+
__publicField(this, "patchMats", /* @__PURE__ */ new Map());
|
|
1217
977
|
}
|
|
1218
978
|
discGeometry() {
|
|
1219
979
|
return this.disc ?? (this.disc = new this.THREE.CircleGeometry(1, 14));
|
|
@@ -1229,6 +989,15 @@ var DecalKit = class {
|
|
|
1229
989
|
polygonOffsetUnits: -4
|
|
1230
990
|
}));
|
|
1231
991
|
}
|
|
992
|
+
/** Opaque patch in the colour of what shows under a spalled coat. */
|
|
993
|
+
patchMaterial(color) {
|
|
994
|
+
let m = this.patchMats.get(color);
|
|
995
|
+
if (!m) {
|
|
996
|
+
m = new this.THREE.MeshBasicMaterial({ color, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -3, polygonOffsetUnits: -3 });
|
|
997
|
+
this.patchMats.set(color, m);
|
|
998
|
+
}
|
|
999
|
+
return m;
|
|
1000
|
+
}
|
|
1232
1001
|
lineMaterial(color) {
|
|
1233
1002
|
let m = this.lineMats.get(color);
|
|
1234
1003
|
if (!m) {
|
|
@@ -1241,6 +1010,8 @@ var DecalKit = class {
|
|
|
1241
1010
|
this.disc?.dispose();
|
|
1242
1011
|
this.discMat?.dispose();
|
|
1243
1012
|
for (const m of this.lineMats.values()) m.dispose();
|
|
1013
|
+
for (const m of this.patchMats.values()) m.dispose();
|
|
1014
|
+
this.patchMats.clear();
|
|
1244
1015
|
this.disc = null;
|
|
1245
1016
|
this.discMat = null;
|
|
1246
1017
|
this.lineMats.clear();
|
|
@@ -1532,6 +1303,16 @@ var Visuals = class {
|
|
|
1532
1303
|
disc.renderOrder = 2;
|
|
1533
1304
|
this.addDecal(mv, { obj: disc });
|
|
1534
1305
|
}
|
|
1306
|
+
/** A patch where a surface coat came off, in the colour of the core under it; drawn under the bullet holes. */
|
|
1307
|
+
addSpall(mv, worldPoint, worldNormal, radius, color) {
|
|
1308
|
+
const { s, pos, quat } = this.decalPose(mv.mesh, worldPoint, worldNormal, 15e-4);
|
|
1309
|
+
const disc = new this.THREE.Mesh(this.kit.discGeometry(), this.kit.patchMaterial(color));
|
|
1310
|
+
disc.position.copy(pos);
|
|
1311
|
+
disc.quaternion.copy(quat);
|
|
1312
|
+
disc.scale.setScalar(Math.max(0.01, radius) / s);
|
|
1313
|
+
disc.renderOrder = 1;
|
|
1314
|
+
this.addDecal(mv, { obj: disc });
|
|
1315
|
+
}
|
|
1535
1316
|
/** A star of jagged lines: `rays` rays up to `length` metres, seeded so the same event always draws the same star. */
|
|
1536
1317
|
addCrack(mv, worldPoint, worldNormal, rays, length, color, seed) {
|
|
1537
1318
|
const { s, pos, quat } = this.decalPose(mv.mesh, worldPoint, worldNormal, 2e-3);
|
|
@@ -1972,8 +1753,13 @@ var Visuals = class {
|
|
|
1972
1753
|
};
|
|
1973
1754
|
|
|
1974
1755
|
// src/renderer.ts
|
|
1756
|
+
var rendererCount = 0;
|
|
1757
|
+
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
1975
1758
|
var GLOW_FROM_C = 430;
|
|
1976
1759
|
var QUEUE_CAP = 4e3;
|
|
1760
|
+
var FIRE_CEILING_SECONDS = 3600;
|
|
1761
|
+
var GENERIC_REFERENCE_RADIUS = 15;
|
|
1762
|
+
var PRIMED_FIRE_RADIUS = 0.2;
|
|
1977
1763
|
var FLUID_COLORS = {
|
|
1978
1764
|
water: 9226495,
|
|
1979
1765
|
fuel: 14262322,
|
|
@@ -2053,20 +1839,22 @@ var Renderer = class {
|
|
|
2053
1839
|
__publicField(this, "queue", []);
|
|
2054
1840
|
__publicField(this, "unsubscribe");
|
|
2055
1841
|
__publicField(this, "visuals");
|
|
2056
|
-
__publicField(this, "particles");
|
|
2057
1842
|
__publicField(this, "fragments");
|
|
2058
1843
|
__publicField(this, "ground");
|
|
2059
1844
|
__publicField(this, "pieceSys");
|
|
1845
|
+
__publicField(this, "cages");
|
|
2060
1846
|
__publicField(this, "active", /* @__PURE__ */ new Map());
|
|
2061
1847
|
__publicField(this, "fires", /* @__PURE__ */ new Map());
|
|
2062
1848
|
__publicField(this, "leaks", []);
|
|
2063
1849
|
__publicField(this, "homeTags", []);
|
|
2064
1850
|
__publicField(this, "throttles", /* @__PURE__ */ new Map());
|
|
2065
1851
|
__publicField(this, "rng", createRng(12648430));
|
|
2066
|
-
__publicField(this, "
|
|
2067
|
-
|
|
2068
|
-
__publicField(this, "
|
|
2069
|
-
__publicField(this, "
|
|
1852
|
+
__publicField(this, "vfx");
|
|
1853
|
+
/** Names this renderer to the world's drips, so a shared world tells each renderer where its own landed. */
|
|
1854
|
+
__publicField(this, "owner", `destruction-${++rendererCount}`);
|
|
1855
|
+
__publicField(this, "stopLanding");
|
|
1856
|
+
/** The effects renderer made here when the host gave no shared world; it is stepped in `update`. */
|
|
1857
|
+
__publicField(this, "ownVfx");
|
|
2070
1858
|
__publicField(this, "time", 0);
|
|
2071
1859
|
__publicField(this, "scanTimer", 0);
|
|
2072
1860
|
__publicField(this, "disposed", false);
|
|
@@ -2089,17 +1877,11 @@ var Renderer = class {
|
|
|
2089
1877
|
const maxParticles = Math.max(8, Math.floor((o.maxParticles ?? 2500) * (this.low ? 0.5 : 1)));
|
|
2090
1878
|
this.visuals = new Visuals(THREE, this.low, 600);
|
|
2091
1879
|
this.ground = new GroundSpots(THREE, o.scene, this.groundAt, 48);
|
|
2092
|
-
this.
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
groundAt: this.groundAt,
|
|
2098
|
-
gravity: this.gravity,
|
|
2099
|
-
onLand: (x, _y, z, color, tag) => {
|
|
2100
|
-
const home = this.homeTags[tag - 1] ?? "";
|
|
2101
|
-
this.ground.add("puddle", x, z, 0.05 + this.rng() * 0.04, color, home);
|
|
2102
|
-
}
|
|
1880
|
+
this.vfx = o.vfx ?? createVfx({ profile: this.quality, gravity: this.gravity, groundAt: this.groundAt, sprites: { capacity: maxParticles, density: this.low ? 0.6 : 1 } });
|
|
1881
|
+
this.ownVfx = o.vfx ? void 0 : createVfxRenderer(THREE, { scene: o.scene, camera: o.camera ?? new THREE.PerspectiveCamera(), vfx: this.vfx, pointScale: o.pointScale ?? 700 });
|
|
1882
|
+
this.stopLanding = this.vfx.onLand(this.owner, (x, _y, z, color, tag) => {
|
|
1883
|
+
const home = this.homeTags[tag - 1] ?? "";
|
|
1884
|
+
this.ground.add("puddle", x, z, 0.05 + this.rng() * 0.04, color, home);
|
|
2103
1885
|
});
|
|
2104
1886
|
this.fragments = new FragmentSystem(THREE, {
|
|
2105
1887
|
scene: o.scene,
|
|
@@ -2108,6 +1890,7 @@ var Renderer = class {
|
|
|
2108
1890
|
max: this.maxFragments,
|
|
2109
1891
|
life: o.fragmentLife ?? 12
|
|
2110
1892
|
});
|
|
1893
|
+
this.cages = new RebarCages(THREE, o.scene);
|
|
2111
1894
|
this.pieceSys = new PieceSystem(THREE, {
|
|
2112
1895
|
groundAt: this.groundAt,
|
|
2113
1896
|
gravity: this.gravity,
|
|
@@ -2315,8 +2098,7 @@ var Renderer = class {
|
|
|
2315
2098
|
this.pieceSys.update(dt);
|
|
2316
2099
|
this.fragments.update(dt);
|
|
2317
2100
|
this.ground.update(dt);
|
|
2318
|
-
this.
|
|
2319
|
-
this.particles.update(dt);
|
|
2101
|
+
this.ownVfx?.update(dt);
|
|
2320
2102
|
this.syncTransforms();
|
|
2321
2103
|
}
|
|
2322
2104
|
drainQueue() {
|
|
@@ -2370,12 +2152,12 @@ var Renderer = class {
|
|
|
2370
2152
|
break;
|
|
2371
2153
|
case "ricochet": {
|
|
2372
2154
|
const d = ev.direction;
|
|
2373
|
-
this.
|
|
2155
|
+
this.vfx.burst("sparks", ev.point, d, 4 + Math.min(10, ev.energy / 150), 4);
|
|
2374
2156
|
this.sound("ricochet", ev.point, 0.7);
|
|
2375
2157
|
break;
|
|
2376
2158
|
}
|
|
2377
2159
|
case "debris":
|
|
2378
|
-
this.
|
|
2160
|
+
this.vfx.burst(ev.kind, ev.point, ev.normal, ev.count, ev.speed > 0 ? ev.speed : DEFAULT_SPEED[ev.kind] ?? 1, this.debrisOptions(ev.kind, ev.object, ev.part));
|
|
2379
2161
|
break;
|
|
2380
2162
|
case "fracture":
|
|
2381
2163
|
this.onFracture(ev);
|
|
@@ -2385,6 +2167,7 @@ var Renderer = class {
|
|
|
2385
2167
|
break;
|
|
2386
2168
|
case "collapse":
|
|
2387
2169
|
this.splitLoose(ev.object);
|
|
2170
|
+
this.playEvent({ kind: "collapse" }, this.rootPoint(ev.object));
|
|
2388
2171
|
break;
|
|
2389
2172
|
case "disassemble_progress": {
|
|
2390
2173
|
const k = `unscrew:${ev.joint}`;
|
|
@@ -2395,34 +2178,45 @@ var Renderer = class {
|
|
|
2395
2178
|
break;
|
|
2396
2179
|
}
|
|
2397
2180
|
case "ignite":
|
|
2398
|
-
this.
|
|
2181
|
+
this.startFire(ev);
|
|
2399
2182
|
this.activate(ev.object, ev.part);
|
|
2400
|
-
this.particles.burst("fire", ev.point, [0, 1, 0], 6, 1);
|
|
2401
2183
|
this.sound("ignite", ev.point, 0.7);
|
|
2402
2184
|
this.sound("burn_loop", ev.point, 0.5);
|
|
2403
2185
|
break;
|
|
2404
2186
|
case "primed":
|
|
2405
2187
|
if (ev.trigger !== "explosion") {
|
|
2406
|
-
this.
|
|
2188
|
+
this.vfx.fire({ point: ev.point, radius: PRIMED_FIRE_RADIUS, life: ev.delay });
|
|
2407
2189
|
this.activate(ev.object, ev.part);
|
|
2408
|
-
this.particles.burst("fire", ev.point, [0, 1, 0], 6, 1);
|
|
2409
2190
|
this.sound("ignite", ev.point, 0.7);
|
|
2410
2191
|
}
|
|
2411
2192
|
break;
|
|
2193
|
+
case "spall":
|
|
2194
|
+
this.onSpall(ev);
|
|
2195
|
+
break;
|
|
2196
|
+
case "stage":
|
|
2197
|
+
this.onStage(ev);
|
|
2198
|
+
break;
|
|
2199
|
+
case "expose":
|
|
2200
|
+
this.cages.set(`${ev.object}|${ev.part}`, ev.bars);
|
|
2201
|
+
if (ev.bars.length > 0) {
|
|
2202
|
+
const [a, b] = ev.bars[0];
|
|
2203
|
+
this.playEvent({ kind: "expose" }, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2]);
|
|
2204
|
+
}
|
|
2205
|
+
break;
|
|
2412
2206
|
case "extinguish": {
|
|
2413
2207
|
const f = this.fires.get(`${ev.object}|${ev.part}`);
|
|
2414
2208
|
if (f) {
|
|
2415
|
-
this.
|
|
2416
|
-
this.
|
|
2209
|
+
this.stopFire(`${ev.object}|${ev.part}`);
|
|
2210
|
+
this.vfx.burst("steam", f.point, [0, 1, 0], 14, 1.2);
|
|
2417
2211
|
}
|
|
2418
2212
|
this.activate(ev.object, ev.part);
|
|
2419
2213
|
this.sound("extinguish", f?.point ?? this.rootPoint(ev.object), 0.6);
|
|
2420
2214
|
break;
|
|
2421
2215
|
}
|
|
2422
2216
|
case "burnout": {
|
|
2423
|
-
this.
|
|
2424
|
-
this.
|
|
2425
|
-
this.
|
|
2217
|
+
this.stopFire(`${ev.object}|${ev.part}`);
|
|
2218
|
+
this.vfx.burst("ash", ev.point, [0, 1, 0], 16, 1);
|
|
2219
|
+
this.vfx.burst("smoke", ev.point, [0, 1, 0], 6, 0.8, { color: SMOKE_TONES.grey });
|
|
2426
2220
|
const pv = this.partVis(ev.object, ev.part);
|
|
2427
2221
|
if (pv) {
|
|
2428
2222
|
const def = this.world.materials.get(pv.material);
|
|
@@ -2445,7 +2239,7 @@ var Renderer = class {
|
|
|
2445
2239
|
const color = (pv && this.partColor(pv, def)) ?? fallbackColor(ev.material, def);
|
|
2446
2240
|
let tag = this.homeTags.indexOf(ev.object) + 1;
|
|
2447
2241
|
if (tag === 0) tag = this.homeTags.push(ev.object);
|
|
2448
|
-
this.
|
|
2242
|
+
this.vfx.spawn("drips", ev.point, [(this.rng() - 0.5) * 0.1, -0.1, (this.rng() - 0.5) * 0.1], { color, tag, owner: this.owner });
|
|
2449
2243
|
break;
|
|
2450
2244
|
}
|
|
2451
2245
|
case "leak": {
|
|
@@ -2536,7 +2330,7 @@ var Renderer = class {
|
|
|
2536
2330
|
if (!this.visuals.shown(mv)) continue;
|
|
2537
2331
|
this.visuals.applyDent(mv, wp, wn, ev.dent);
|
|
2538
2332
|
}
|
|
2539
|
-
if (def?.tags?.includes("metal")) this.
|
|
2333
|
+
if (def?.tags?.includes("metal")) this.vfx.burst("sparks", ev.worldPoint, [wn.x, wn.y, wn.z], 4 + Math.min(8, ev.dent.depth * 150), 3.2);
|
|
2540
2334
|
this.sound(impactKey(def), ev.worldPoint, clamp(0.3 + ev.dent.depth * 12, 0.3, 1));
|
|
2541
2335
|
}
|
|
2542
2336
|
onHole(ev) {
|
|
@@ -2558,7 +2352,7 @@ var Renderer = class {
|
|
|
2558
2352
|
};
|
|
2559
2353
|
draw(ev.worldPoint, n);
|
|
2560
2354
|
if (ev.hole.through && ev.exitPoint) draw(ev.exitPoint, n.clone().negate());
|
|
2561
|
-
if (def?.tags?.includes("metal")) this.
|
|
2355
|
+
if (def?.tags?.includes("metal")) this.vfx.burst("sparks", ev.worldPoint, [n.x, n.y, n.z], 6, 3.5);
|
|
2562
2356
|
this.sound(impactKey(def), ev.worldPoint, 0.6);
|
|
2563
2357
|
}
|
|
2564
2358
|
onCrack(ev) {
|
|
@@ -2574,6 +2368,59 @@ var Renderer = class {
|
|
|
2574
2368
|
}
|
|
2575
2369
|
this.sound(impactKey(def), ev.worldPoint, 0.5);
|
|
2576
2370
|
}
|
|
2371
|
+
/** Outward normal of the face of `mv` nearest to a world point: the thin axis of its world box (right for a wall; a sloped face is left to the vertex-range binding). */
|
|
2372
|
+
faceNormal(mv, point) {
|
|
2373
|
+
const box = this.visuals.worldBox(mv, this.box);
|
|
2374
|
+
const size = box.getSize(this.v1);
|
|
2375
|
+
const axis = size.x <= size.y && size.x <= size.z ? "x" : size.y <= size.z ? "y" : "z";
|
|
2376
|
+
const side = point[axis] >= (box.min[axis] + box.max[axis]) / 2 ? 1 : -1;
|
|
2377
|
+
return new this.THREE.Vector3(axis === "x" ? side : 0, axis === "y" ? side : 0, axis === "z" ? side : 0);
|
|
2378
|
+
}
|
|
2379
|
+
/** The material an impact effect is chosen by: the tags of the material (`metal`, `glass`, `wood`), else stone and the like are `concrete`. */
|
|
2380
|
+
impactMaterial(def) {
|
|
2381
|
+
for (const tag of IMPACT_TAGS) if (def?.tags?.includes(tag)) return tag;
|
|
2382
|
+
return "concrete";
|
|
2383
|
+
}
|
|
2384
|
+
/**
|
|
2385
|
+
* Plays the effect the event map names for a destruction event (`data/event-effects.json` of `game-vfx`, or `RendererOptions.eventEffects`) at a point.
|
|
2386
|
+
* Answers whether one played, so the caller can leave out the burst it replaces.
|
|
2387
|
+
*/
|
|
2388
|
+
playEvent(event, point) {
|
|
2389
|
+
const id = effectForEvent(event, this.o.eventEffects ?? EVENT_EFFECTS_MAP);
|
|
2390
|
+
if (!id) return false;
|
|
2391
|
+
return this.vfx.play(id, point, { seed: hashNumbers(point[0], point[1], point[2], this.time) }) !== null;
|
|
2392
|
+
}
|
|
2393
|
+
/** A coat came off: a patch in the colour of the core under it, below the bullet holes and cracks. */
|
|
2394
|
+
onSpall(ev) {
|
|
2395
|
+
const pv = this.partVis(ev.object, ev.part);
|
|
2396
|
+
if (!pv) return;
|
|
2397
|
+
const wp = new this.THREE.Vector3(ev.point[0], ev.point[1], ev.point[2]);
|
|
2398
|
+
const mv = this.nearestMesh(pv, wp);
|
|
2399
|
+
if (!mv) return;
|
|
2400
|
+
const def = this.world.materials.get(pv.material);
|
|
2401
|
+
this.playEvent({ kind: "spall", material: this.impactMaterial(def) }, ev.point);
|
|
2402
|
+
this.visuals.addSpall(mv, wp, this.faceNormal(mv, wp), Math.sqrt(ev.area / Math.PI), fallbackColor(pv.material, def));
|
|
2403
|
+
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Stage 2 of a staged part: the unit-sized fragments come out and the part stays drawn (hiding it is what `fracture` does). The opening itself
|
|
2406
|
+
* (cutting the wall's faces) is not drawn yet: it needs the per-vertex binding of the house renderer.
|
|
2407
|
+
*/
|
|
2408
|
+
onStage(ev) {
|
|
2409
|
+
const played = this.playEvent({ kind: "stage", stage: ev.stage }, ev.opening?.center ?? this.rootPoint(ev.object));
|
|
2410
|
+
if (!ev.fragments || !ev.opening) return;
|
|
2411
|
+
const pv = this.partVis(ev.object, ev.part);
|
|
2412
|
+
const def = pv ? this.world.materials.get(pv.material) : void 0;
|
|
2413
|
+
const color = (pv ? this.partColor(pv, def) : null) ?? fallbackColor(pv?.material ?? "", def);
|
|
2414
|
+
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
2415
|
+
const step = this.low ? 2 : 1;
|
|
2416
|
+
for (let i = 0; i < ev.fragments.length; i += step) {
|
|
2417
|
+
const fr = ev.fragments[i];
|
|
2418
|
+
const fdef = this.world.materials.get(fr.material);
|
|
2419
|
+
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, fdef === def || !fdef ? color : fallbackColor(fr.material, fdef), false), ev.object, maxAlive);
|
|
2420
|
+
}
|
|
2421
|
+
if (!played) this.vfx.burst("dust", ev.opening.center, [0, 1, 0], 14, 1.2, { color });
|
|
2422
|
+
this.sound(impactKey(def), ev.opening.center, 0.8);
|
|
2423
|
+
}
|
|
2577
2424
|
onFracture(ev) {
|
|
2578
2425
|
const b = this.bounds.get(ev.object);
|
|
2579
2426
|
const pv = b?.parts.get(ev.part);
|
|
@@ -2601,24 +2448,24 @@ var Renderer = class {
|
|
|
2601
2448
|
const sizeK = clamp(ev.fragments.length / 10, 0.5, 2);
|
|
2602
2449
|
switch (ev.style) {
|
|
2603
2450
|
case "shatter":
|
|
2604
|
-
this.
|
|
2451
|
+
this.vfx.burst("shards", ev.point, up, 18 * sizeK, 3);
|
|
2605
2452
|
break;
|
|
2606
2453
|
case "crumble":
|
|
2607
|
-
this.
|
|
2608
|
-
this.
|
|
2454
|
+
this.vfx.burst("dust", ev.point, up, 16 * sizeK, 1.2, { color: pv ? this.partColor(pv, def) ?? void 0 : void 0 });
|
|
2455
|
+
this.vfx.burst("chips", ev.point, up, 6, 3);
|
|
2609
2456
|
break;
|
|
2610
2457
|
case "splinter":
|
|
2611
|
-
this.
|
|
2612
|
-
this.
|
|
2458
|
+
this.vfx.burst("splinters", ev.point, up, 14 * sizeK, 3);
|
|
2459
|
+
this.vfx.burst("dust", ev.point, up, 5, 0.8, { color: 11901556 });
|
|
2613
2460
|
break;
|
|
2614
2461
|
case "tear":
|
|
2615
|
-
this.
|
|
2462
|
+
this.vfx.burst("fibers", ev.point, up, 12 * sizeK, 1.6, { color });
|
|
2616
2463
|
break;
|
|
2617
2464
|
case "snap":
|
|
2618
|
-
this.
|
|
2465
|
+
this.vfx.burst("chips", ev.point, up, 8 * sizeK, 3);
|
|
2619
2466
|
break;
|
|
2620
2467
|
default:
|
|
2621
|
-
this.
|
|
2468
|
+
this.vfx.burst("sparks", ev.point, up, 10, 4);
|
|
2622
2469
|
}
|
|
2623
2470
|
this.sound(fractureKey(def, ev.style), ev.point, clamp(0.4 + ev.fragments.length * 0.03, 0.4, 1));
|
|
2624
2471
|
}
|
|
@@ -2697,27 +2544,10 @@ var Renderer = class {
|
|
|
2697
2544
|
nb.piece = piece;
|
|
2698
2545
|
this.o.onPiece?.(piece);
|
|
2699
2546
|
}
|
|
2700
|
-
onExplode(ev) {
|
|
2547
|
+
onExplode(ev, options = {}) {
|
|
2701
2548
|
const p = ev.point;
|
|
2702
|
-
|
|
2703
|
-
this.
|
|
2704
|
-
this.particles.burst("fire", p, [0, 1, 0], 34 * k, 4 * k, { size: 1 + k * 0.5 });
|
|
2705
|
-
this.particles.burst("smoke", p, [0, 1, 0], 24 * k, 2.2, { color: SMOKE_TONES.black, size: 1 + k * 0.6 });
|
|
2706
|
-
this.particles.burst("dust", p, [0, 1, 0], 22 * k, 3.5 * k);
|
|
2707
|
-
this.particles.burst("sparks", p, [0, 1, 0], 16 * k, 8);
|
|
2708
|
-
this.particles.push(p, ev.radius, 4);
|
|
2709
|
-
if (this.o.explosionLight !== false) {
|
|
2710
|
-
if (!this.light) {
|
|
2711
|
-
this.light = new this.THREE.PointLight(16754784, 0, 10, 2);
|
|
2712
|
-
this.light.name = "destruction-explosion-light";
|
|
2713
|
-
}
|
|
2714
|
-
if (!this.light.parent) this.scene.add(this.light);
|
|
2715
|
-
this.light.position.set(p[0], p[1] + 0.5, p[2]);
|
|
2716
|
-
this.light.distance = Math.max(6, ev.radius * 3);
|
|
2717
|
-
this.lightPeak = clamp(Math.sqrt(Math.max(1, ev.energy)) * 0.6, 10, 400);
|
|
2718
|
-
this.lightDur = 0.35;
|
|
2719
|
-
this.lightT = this.lightDur;
|
|
2720
|
-
}
|
|
2549
|
+
if (options.visual !== false) this.playExplosion(ev, options);
|
|
2550
|
+
this.vfx.push(p, ev.radius, 4);
|
|
2721
2551
|
const c = this.v1;
|
|
2722
2552
|
for (const pc of this.pieceSys.list) {
|
|
2723
2553
|
this.pieceSys.centerOf(pc, c);
|
|
@@ -2736,11 +2566,23 @@ var Renderer = class {
|
|
|
2736
2566
|
this.fragments.blast(p, ev.radius, Math.min(20, Math.sqrt(ev.energy) * 0.08));
|
|
2737
2567
|
this.sound("explode", p, clamp(0.5 + ev.radius / 10, 0.5, 1));
|
|
2738
2568
|
}
|
|
2739
|
-
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
const
|
|
2743
|
-
this.
|
|
2569
|
+
playExplosion(ev, options) {
|
|
2570
|
+
const named = options.effect ?? ev.effect;
|
|
2571
|
+
const direction = options.direction;
|
|
2572
|
+
const scale = clamp(ev.radius / GENERIC_REFERENCE_RADIUS, 0.2, 3);
|
|
2573
|
+
if (named !== void 0 && this.vfx.explosion(named, ev.point, direction ? { direction } : {}) !== null) return;
|
|
2574
|
+
this.vfx.explosion("generic", ev.point, { scale, ...direction ? { direction } : {} });
|
|
2575
|
+
}
|
|
2576
|
+
startFire(ev) {
|
|
2577
|
+
const key = `${ev.object}|${ev.part}`;
|
|
2578
|
+
this.stopFire(key);
|
|
2579
|
+
this.fires.set(key, { object: ev.object, part: ev.part, point: ev.point, follow: { x: ev.point[0], y: ev.point[1], z: ev.point[2] } });
|
|
2580
|
+
}
|
|
2581
|
+
stopFire(key) {
|
|
2582
|
+
const fire = this.fires.get(key);
|
|
2583
|
+
if (!fire) return;
|
|
2584
|
+
if (fire.handle !== void 0) this.vfx.stop(fire.handle);
|
|
2585
|
+
this.fires.delete(key);
|
|
2744
2586
|
}
|
|
2745
2587
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
2746
2588
|
// Fire, heat, char, melt, leaks
|
|
@@ -2778,8 +2620,8 @@ var Renderer = class {
|
|
|
2778
2620
|
}
|
|
2779
2621
|
const fire = this.fires.get(key);
|
|
2780
2622
|
if (fire) {
|
|
2781
|
-
if (ps.burning && ps.status !== "broken" && ps.status !== "ashes" && ps.status !== "melted") this.emitFire(fire, pv, def
|
|
2782
|
-
else this.
|
|
2623
|
+
if (ps.burning && ps.status !== "broken" && ps.status !== "ashes" && ps.status !== "melted") this.emitFire(fire, pv, def);
|
|
2624
|
+
else this.stopFire(key);
|
|
2783
2625
|
}
|
|
2784
2626
|
const hot = def?.visual?.glows === true && ps.temperature > GLOW_FROM_C;
|
|
2785
2627
|
if (!ps.burning && !hot && Math.abs(ps.char - pv.charApplied) < 0.01 && Math.abs(ps.melt - pv.meltApplied) < 0.01) {
|
|
@@ -2800,43 +2642,25 @@ var Renderer = class {
|
|
|
2800
2642
|
this.ground.add("puddle", c.x, c.z, clamp(footprint * 0.55, 0.05, 1), this.partColor(pv, def) ?? fallbackColor(pv.material, def), objectId);
|
|
2801
2643
|
} else {
|
|
2802
2644
|
this.ground.add("ash", c.x, c.z, clamp(footprint * 0.35, 0.04, 0.5), 1907224, objectId);
|
|
2803
|
-
this.
|
|
2645
|
+
this.vfx.burst("ash", [c.x, bb.max.y, c.z], [0, 1, 0], 10, 0.8);
|
|
2804
2646
|
}
|
|
2805
2647
|
}
|
|
2806
|
-
emitFire(f, pv, def
|
|
2648
|
+
emitFire(f, pv, def) {
|
|
2807
2649
|
const bb = this.box;
|
|
2808
2650
|
bb.makeEmpty();
|
|
2809
2651
|
const tmp = new this.THREE.Box3();
|
|
2810
2652
|
for (const mv of pv.meshes) if (this.visuals.shown(mv)) bb.union(this.visuals.worldBox(mv, tmp));
|
|
2811
|
-
let cx = f.point[0];
|
|
2812
|
-
let cy = f.point[1];
|
|
2813
|
-
let cz = f.point[2];
|
|
2814
|
-
let sx = 0.2;
|
|
2815
|
-
let sy = 0.2;
|
|
2816
|
-
let sz = 0.2;
|
|
2817
2653
|
if (!bb.isEmpty()) {
|
|
2818
2654
|
const c = bb.getCenter(this.v1);
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
cz = c.z;
|
|
2823
|
-
sx = s.x;
|
|
2824
|
-
sy = s.y;
|
|
2825
|
-
sz = s.z;
|
|
2826
|
-
}
|
|
2827
|
-
const vol = clamp(Math.cbrt(Math.max(sx * sy * sz, 1e-5)), 0.05, 1.5);
|
|
2828
|
-
f.accFire += (18 + 70 * vol) * dt;
|
|
2829
|
-
f.accSmoke += (6 + 24 * vol) * dt;
|
|
2830
|
-
const tone = SMOKE_TONES[def?.thermal.smoke ?? "grey"];
|
|
2831
|
-
const rnd = this.rng;
|
|
2832
|
-
while (f.accFire >= 1) {
|
|
2833
|
-
f.accFire -= 1;
|
|
2834
|
-
this.particles.spawn("fire", [cx + (rnd() - 0.5) * sx, cy + sy * (0.3 + 0.7 * rnd()), cz + (rnd() - 0.5) * sz], [(rnd() - 0.5) * 0.4, 0.5 + rnd() * 0.9, (rnd() - 0.5) * 0.4], { size: 0.6 + vol * 0.8 });
|
|
2835
|
-
}
|
|
2836
|
-
while (f.accSmoke >= 1) {
|
|
2837
|
-
f.accSmoke -= 1;
|
|
2838
|
-
this.particles.spawn("smoke", [cx + (rnd() - 0.5) * sx, cy + sy * (0.8 + 0.4 * rnd()), cz + (rnd() - 0.5) * sz], [(rnd() - 0.5) * 0.3 + 0.1, 0.4 + rnd() * 0.5, (rnd() - 0.5) * 0.3], { color: tone, size: 0.6 + vol });
|
|
2655
|
+
f.follow.x = c.x;
|
|
2656
|
+
f.follow.y = bb.min.y;
|
|
2657
|
+
f.follow.z = c.z;
|
|
2839
2658
|
}
|
|
2659
|
+
if (f.handle !== void 0) return;
|
|
2660
|
+
const size = bb.isEmpty() ? this.v2.set(0.2, 0.2, 0.2) : bb.getSize(this.v2);
|
|
2661
|
+
const radius = clamp(Math.max(size.x, size.z) * 0.5, 0.12, 1.2);
|
|
2662
|
+
const smokeColor = SMOKE_TONES[def?.thermal.smoke ?? "grey"];
|
|
2663
|
+
f.handle = this.vfx.fire({ point: [f.follow.x, f.follow.y, f.follow.z], radius, life: FIRE_CEILING_SECONDS, follow: f.follow, ...smokeColor !== void 0 ? { smokeColor } : {} });
|
|
2840
2664
|
}
|
|
2841
2665
|
updateLeaks(dt) {
|
|
2842
2666
|
for (let i = this.leaks.length - 1; i >= 0; i--) {
|
|
@@ -2854,8 +2678,8 @@ var Renderer = class {
|
|
|
2854
2678
|
while (l.acc >= 1) {
|
|
2855
2679
|
l.acc -= 1;
|
|
2856
2680
|
const vel = [d[0] / dl * sp + (this.rng() - 0.5) * 0.25, d[1] / dl * sp + (this.rng() - 0.5) * 0.25, d[2] / dl * sp + (this.rng() - 0.5) * 0.25];
|
|
2857
|
-
if (l.fluid === "gas") this.
|
|
2858
|
-
else this.
|
|
2681
|
+
if (l.fluid === "gas") this.vfx.spawn("steam", l.point, vel, { color, size: 0.5, alpha: 0.5 });
|
|
2682
|
+
else this.vfx.spawn("drips", l.point, vel, { color, tag: this.rng() < 0.03 ? this.tagOf(l.object) : 0, owner: this.owner });
|
|
2859
2683
|
}
|
|
2860
2684
|
}
|
|
2861
2685
|
}
|
|
@@ -2868,10 +2692,10 @@ var Renderer = class {
|
|
|
2868
2692
|
// Emit, clear, restore, stats, dispose
|
|
2869
2693
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
2870
2694
|
emit(kind, point, normal, count, speed) {
|
|
2871
|
-
this.
|
|
2695
|
+
this.vfx.burst(kind, point, normal, count, speed ?? DEFAULT_SPEED[kind] ?? 1, kind === "smoke" ? { color: SMOKE_TONES.grey } : void 0);
|
|
2872
2696
|
}
|
|
2873
|
-
explode(point, energy, radius) {
|
|
2874
|
-
this.onExplode({ type: "explode", object: "", part: "", time: this.time, point, energy, radius });
|
|
2697
|
+
explode(point, energy, radius, options) {
|
|
2698
|
+
this.onExplode({ type: "explode", object: "", part: "", time: this.time, point, energy, radius }, options);
|
|
2875
2699
|
}
|
|
2876
2700
|
restore(objectId) {
|
|
2877
2701
|
const homes = /* @__PURE__ */ new Set([objectId]);
|
|
@@ -2879,11 +2703,12 @@ var Renderer = class {
|
|
|
2879
2703
|
if (self?.isSplit) for (const pv of self.parts.values()) homes.add(pv.home);
|
|
2880
2704
|
for (const home of homes) this.restoreHome(home);
|
|
2881
2705
|
for (const [key, a] of [...this.active]) if (homes.has(a.object)) this.active.delete(key);
|
|
2882
|
-
for (const [key, f] of [...this.fires]) if (homes.has(f.object)) this.
|
|
2706
|
+
for (const [key, f] of [...this.fires]) if (homes.has(f.object)) this.stopFire(key);
|
|
2883
2707
|
for (let i = this.leaks.length - 1; i >= 0; i--) if (homes.has(this.leaks[i].object)) this.leaks.splice(i, 1);
|
|
2884
2708
|
for (const home of homes) {
|
|
2885
2709
|
this.fragments.removeHome(home);
|
|
2886
2710
|
this.ground.removeHome(home);
|
|
2711
|
+
this.cages.removeObject(home);
|
|
2887
2712
|
}
|
|
2888
2713
|
}
|
|
2889
2714
|
restoreHome(home) {
|
|
@@ -2907,7 +2732,7 @@ var Renderer = class {
|
|
|
2907
2732
|
clear() {
|
|
2908
2733
|
this.queue.length = 0;
|
|
2909
2734
|
this.active.clear();
|
|
2910
|
-
this.fires.
|
|
2735
|
+
for (const key of [...this.fires.keys()]) this.stopFire(key);
|
|
2911
2736
|
this.leaks.length = 0;
|
|
2912
2737
|
for (const id of [...this.bounds.keys()]) if (this.bounds.has(id)) this.restore(id);
|
|
2913
2738
|
for (const b of [...this.bounds.values()]) {
|
|
@@ -2920,15 +2745,20 @@ var Renderer = class {
|
|
|
2920
2745
|
}
|
|
2921
2746
|
this.fragments.clear();
|
|
2922
2747
|
this.ground.clear();
|
|
2923
|
-
this.
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
this.lightT = 0;
|
|
2927
|
-
this.scene.remove(this.light);
|
|
2928
|
-
}
|
|
2748
|
+
this.cages.clear();
|
|
2749
|
+
this.vfx.sprites().clear();
|
|
2750
|
+
this.ownVfx?.world.clear();
|
|
2929
2751
|
}
|
|
2930
2752
|
stats() {
|
|
2931
|
-
return { fragments: this.fragments.count, particles: this.
|
|
2753
|
+
return { fragments: this.fragments.count, particles: this.vfx.sprites().live + this.ownedVfxParticles(), pieces: this.pieceSys.list.length, decals: this.visuals.decalCount + this.ground.count };
|
|
2754
|
+
}
|
|
2755
|
+
ownedVfxParticles() {
|
|
2756
|
+
if (!this.ownVfx) return 0;
|
|
2757
|
+
const { puffs, sparks, glows, chunks } = this.vfx.stats();
|
|
2758
|
+
return puffs + sparks + glows + chunks;
|
|
2759
|
+
}
|
|
2760
|
+
rebarCount() {
|
|
2761
|
+
return this.cages.count;
|
|
2932
2762
|
}
|
|
2933
2763
|
dispose() {
|
|
2934
2764
|
if (this.disposed) return;
|
|
@@ -2937,10 +2767,10 @@ var Renderer = class {
|
|
|
2937
2767
|
for (const id of [...this.bounds.keys()]) this.unbind(id);
|
|
2938
2768
|
this.fragments.dispose();
|
|
2939
2769
|
this.ground.dispose();
|
|
2940
|
-
this.
|
|
2770
|
+
this.cages.dispose();
|
|
2771
|
+
this.stopLanding();
|
|
2941
2772
|
this.visuals.dispose();
|
|
2942
|
-
this.
|
|
2943
|
-
this.light = null;
|
|
2773
|
+
this.ownVfx?.dispose();
|
|
2944
2774
|
this.disposed = true;
|
|
2945
2775
|
}
|
|
2946
2776
|
};
|