@adatechnology/game-destruction-three 0.9.0 → 0.11.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 +34 -3
- package/dist/index.global.js +585 -30
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +395 -20
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -7,6 +7,44 @@ import { blastShove } from "@adatechnology/game-contracts";
|
|
|
7
7
|
import { createVfx, effectForEvent, EVENT_EFFECTS_MAP } from "@adatechnology/game-vfx";
|
|
8
8
|
import { createVfxRenderer } from "@adatechnology/game-vfx-three";
|
|
9
9
|
|
|
10
|
+
// src/fitted-boxes.ts
|
|
11
|
+
var SLICE_LENGTH = 0.12;
|
|
12
|
+
var MAX_SLICES = 12;
|
|
13
|
+
var SNUG_RATIO = 1.1;
|
|
14
|
+
function pushFittedBoxes(local, matrix, out) {
|
|
15
|
+
const e = matrix.elements;
|
|
16
|
+
const sizes = [0, 1, 2].map((k) => k === 0 ? local.max.x - local.min.x : k === 1 ? local.max.y - local.min.y : local.max.z - local.min.z);
|
|
17
|
+
const lengths = [0, 1, 2].map((k) => sizes[k] * Math.hypot(e[k * 4], e[k * 4 + 1], e[k * 4 + 2]));
|
|
18
|
+
const longest = lengths.indexOf(Math.max(...lengths));
|
|
19
|
+
const whole = worldBoxOf(local, matrix, longest, 0, 1);
|
|
20
|
+
const span = (whole[3] - whole[0]) * (whole[4] - whole[1]) * (whole[5] - whole[2]);
|
|
21
|
+
const volume = lengths[0] * lengths[1] * lengths[2];
|
|
22
|
+
const slices = span <= volume * SNUG_RATIO ? 1 : Math.min(MAX_SLICES, Math.ceil(lengths[longest] / SLICE_LENGTH));
|
|
23
|
+
if (slices <= 1) {
|
|
24
|
+
out.push(...whole);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
for (let i = 0; i < slices; i++) out.push(...worldBoxOf(local, matrix, longest, i / slices, (i + 1) / slices));
|
|
28
|
+
}
|
|
29
|
+
function worldBoxOf(local, matrix, axis, from, to) {
|
|
30
|
+
const e = matrix.elements;
|
|
31
|
+
const low = [local.min.x, local.min.y, local.min.z];
|
|
32
|
+
const high = [local.max.x, local.max.y, local.max.z];
|
|
33
|
+
const start = low[axis];
|
|
34
|
+
const size = high[axis] - start;
|
|
35
|
+
low[axis] = start + size * from;
|
|
36
|
+
high[axis] = start + size * to;
|
|
37
|
+
const box = [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];
|
|
38
|
+
for (const x of [low[0], high[0]]) for (const y of [low[1], high[1]]) for (const z of [low[2], high[2]]) {
|
|
39
|
+
for (let k = 0; k < 3; k++) {
|
|
40
|
+
const v = e[k] * x + e[k + 4] * y + e[k + 8] * z + e[k + 12];
|
|
41
|
+
box[k] = Math.min(box[k], v);
|
|
42
|
+
box[k + 3] = Math.max(box[k + 3], v);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return box;
|
|
46
|
+
}
|
|
47
|
+
|
|
10
48
|
// src/math.ts
|
|
11
49
|
function createRng(seed) {
|
|
12
50
|
let a = seed >>> 0;
|
|
@@ -48,12 +86,20 @@ function mixHex(a, b, t) {
|
|
|
48
86
|
var SHEET_MAX = 4e-3;
|
|
49
87
|
var SHEET_AIR = 2.5;
|
|
50
88
|
var SHEET_SWAY = 3;
|
|
89
|
+
var BIG_LENGTH = 0.2;
|
|
90
|
+
var BIG_WIDTH = 0.03;
|
|
91
|
+
var isBig = (size) => {
|
|
92
|
+
const [, middle, longest] = [...size].sort((a, b) => a - b);
|
|
93
|
+
return longest >= BIG_LENGTH && middle >= BIG_WIDTH;
|
|
94
|
+
};
|
|
51
95
|
var FragmentSystem = class {
|
|
52
96
|
constructor(THREE, o) {
|
|
53
97
|
__publicField(this, "THREE", THREE);
|
|
54
98
|
__publicField(this, "o", o);
|
|
55
99
|
__publicField(this, "list", []);
|
|
56
100
|
__publicField(this, "box");
|
|
101
|
+
/** The mesh box of a fragment before its scale. */
|
|
102
|
+
__publicField(this, "unitBox", { min: { x: -0.5, y: -0.5, z: -0.5 }, max: { x: 0.5, y: 0.5, z: 0.5 } });
|
|
57
103
|
__publicField(this, "shardGeos", []);
|
|
58
104
|
__publicField(this, "materials", /* @__PURE__ */ new Map());
|
|
59
105
|
__publicField(this, "q");
|
|
@@ -62,6 +108,8 @@ var FragmentSystem = class {
|
|
|
62
108
|
__publicField(this, "v");
|
|
63
109
|
__publicField(this, "w");
|
|
64
110
|
__publicField(this, "rng", createRng(1374496523));
|
|
111
|
+
/** Count of `update` calls: what makes the fragments of one burst tell apart from older ones. */
|
|
112
|
+
__publicField(this, "tick", 0);
|
|
65
113
|
__publicField(this, "markGeometry", null);
|
|
66
114
|
__publicField(this, "markMaterial", null);
|
|
67
115
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
@@ -129,8 +177,28 @@ var FragmentSystem = class {
|
|
|
129
177
|
mesh.add(disc);
|
|
130
178
|
}
|
|
131
179
|
}
|
|
132
|
-
/**
|
|
180
|
+
/**
|
|
181
|
+
* Create the mesh of a fragment and start its motion. `maxAllowed` is the live budget: over it the oldest fragment still showing fades out to make room, and a burst
|
|
182
|
+
* 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
|
|
183
|
+
* out. Returns null when the fragment was not made.
|
|
184
|
+
*/
|
|
133
185
|
spawn(f, material, home, maxAllowed) {
|
|
186
|
+
if (this.list.length >= maxAllowed) {
|
|
187
|
+
let alive = 0;
|
|
188
|
+
let oldest;
|
|
189
|
+
let oldestSmall;
|
|
190
|
+
for (const l of this.list) {
|
|
191
|
+
if (l.dying) continue;
|
|
192
|
+
alive++;
|
|
193
|
+
oldest ?? (oldest = l);
|
|
194
|
+
if (!l.body) oldestSmall ?? (oldestSmall = l);
|
|
195
|
+
}
|
|
196
|
+
oldest = oldestSmall ?? oldest;
|
|
197
|
+
if (alive >= maxAllowed) {
|
|
198
|
+
if (!oldest || oldest.born === this.tick) return null;
|
|
199
|
+
oldest.dying = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
134
202
|
const geo = f.shape === "shard" ? this.shardGeometry(hashString(f.id)) : this.box;
|
|
135
203
|
const mesh = new this.THREE.Mesh(geo, material);
|
|
136
204
|
mesh.position.set(f.center[0], f.center[1], f.center[2]);
|
|
@@ -154,19 +222,42 @@ var FragmentSystem = class {
|
|
|
154
222
|
rest: 0,
|
|
155
223
|
fade: 1,
|
|
156
224
|
dying: false,
|
|
157
|
-
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0
|
|
225
|
+
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0,
|
|
226
|
+
body: null,
|
|
227
|
+
born: this.tick
|
|
158
228
|
};
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
229
|
+
if (f.shape !== "shard" && isBig(f.size)) {
|
|
230
|
+
live.body = {
|
|
231
|
+
mass: f.mass,
|
|
232
|
+
push: (x, z) => {
|
|
233
|
+
live.vel.x = x;
|
|
234
|
+
live.vel.z = z;
|
|
235
|
+
live.vel.y = Math.max(live.vel.y, 0.3);
|
|
236
|
+
live.asleep = false;
|
|
237
|
+
live.rest = 0;
|
|
238
|
+
}
|
|
239
|
+
};
|
|
166
240
|
}
|
|
241
|
+
this.list.push(live);
|
|
167
242
|
this.addMarks(mesh, f);
|
|
168
243
|
return mesh;
|
|
169
244
|
}
|
|
245
|
+
/** The boxes of the big fragments around (x, z). */
|
|
246
|
+
solidsNear(x, z, radius, found) {
|
|
247
|
+
const boxes = [];
|
|
248
|
+
for (const f of this.list) {
|
|
249
|
+
if (!f.body || f.dying) continue;
|
|
250
|
+
const reach = f.half.length() + radius;
|
|
251
|
+
if (Math.abs(f.mesh.position.x - x) > reach || Math.abs(f.mesh.position.z - z) > reach) continue;
|
|
252
|
+
f.mesh.updateMatrixWorld();
|
|
253
|
+
boxes.length = 0;
|
|
254
|
+
pushFittedBoxes(this.unitBox, f.mesh.matrixWorld, boxes);
|
|
255
|
+
for (let i = 0; i < boxes.length; i += 6) {
|
|
256
|
+
if (x < boxes[i] - radius || x > boxes[i + 3] + radius || z < boxes[i + 2] - radius || z > boxes[i + 5] + radius) continue;
|
|
257
|
+
found(f.home, [boxes[i], boxes[i + 1], boxes[i + 2]], [boxes[i + 3], boxes[i + 4], boxes[i + 5]], f.body);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
170
261
|
/** Add velocity to every fragment within `radius` of a point (blasts). */
|
|
171
262
|
blast(point, radius, speed) {
|
|
172
263
|
for (const f of this.list) {
|
|
@@ -182,7 +273,16 @@ var FragmentSystem = class {
|
|
|
182
273
|
f.rest = 0;
|
|
183
274
|
}
|
|
184
275
|
}
|
|
276
|
+
/** What lies inside the disc looks at the ground again: it was dug or heaped under it. */
|
|
277
|
+
wakeRegion(x, z, radius) {
|
|
278
|
+
for (const f of this.list) {
|
|
279
|
+
if (!f.asleep || Math.hypot(f.mesh.position.x - x, f.mesh.position.z - z) > radius) continue;
|
|
280
|
+
f.asleep = false;
|
|
281
|
+
f.rest = 0;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
185
284
|
update(dt) {
|
|
285
|
+
this.tick++;
|
|
186
286
|
const g = this.o.gravity;
|
|
187
287
|
for (let i = this.list.length - 1; i >= 0; i--) {
|
|
188
288
|
const f = this.list[i];
|
|
@@ -197,7 +297,7 @@ var FragmentSystem = class {
|
|
|
197
297
|
}
|
|
198
298
|
if (f.asleep) {
|
|
199
299
|
f.rest += dt;
|
|
200
|
-
if (this.o.life > 0 && f.rest > this.o.life) f.dying = true;
|
|
300
|
+
if (this.o.life > 0 && f.rest > this.o.life && !f.body) f.dying = true;
|
|
201
301
|
continue;
|
|
202
302
|
}
|
|
203
303
|
this.step(f, dt, g);
|
|
@@ -271,7 +371,7 @@ var FragmentSystem = class {
|
|
|
271
371
|
let small = 0;
|
|
272
372
|
for (let i = 1; i < 3; i++) if (half[i] < half[small]) small = i;
|
|
273
373
|
let axis = small;
|
|
274
|
-
if (Math.abs(up[small]) < 0.35) {
|
|
374
|
+
if (Math.abs(up[small]) < 0.35 && !f.body) {
|
|
275
375
|
axis = 0;
|
|
276
376
|
for (let i = 1; i < 3; i++) if (Math.abs(up[i]) > Math.abs(up[axis])) axis = i;
|
|
277
377
|
}
|
|
@@ -397,6 +497,30 @@ var HARD_LANDING = 3;
|
|
|
397
497
|
var SLOP = 2e-3;
|
|
398
498
|
var NARROW = 0.3;
|
|
399
499
|
var TOPPLE = 3;
|
|
500
|
+
var TIP_ACCELERATION = 30;
|
|
501
|
+
var TIP_LYING = 0.25;
|
|
502
|
+
var TIP_SECONDS = 3;
|
|
503
|
+
var STEP = 0.03;
|
|
504
|
+
var boxTopAt = (s, x, z) => {
|
|
505
|
+
const e = s.e;
|
|
506
|
+
const dx = x - s.c[0], dz = z - s.c[2];
|
|
507
|
+
let t0 = -Infinity, t1 = Infinity;
|
|
508
|
+
for (let k = 0; k < 3; k++) {
|
|
509
|
+
const p = e[k * 4] * dx + e[k * 4 + 2] * dz;
|
|
510
|
+
const d = e[k * 4 + 1];
|
|
511
|
+
const h = s.half[k];
|
|
512
|
+
if (Math.abs(d) < 1e-9) {
|
|
513
|
+
if (Math.abs(p) > h) return -Infinity;
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
let a = (-h - p) / d, b = (h - p) / d;
|
|
517
|
+
if (a > b) [a, b] = [b, a];
|
|
518
|
+
t0 = Math.max(t0, a);
|
|
519
|
+
t1 = Math.min(t1, b);
|
|
520
|
+
if (t0 > t1) return -Infinity;
|
|
521
|
+
}
|
|
522
|
+
return s.c[1] + t1;
|
|
523
|
+
};
|
|
400
524
|
var PieceSystem = class {
|
|
401
525
|
constructor(THREE, o) {
|
|
402
526
|
__publicField(this, "THREE", THREE);
|
|
@@ -408,6 +532,7 @@ var PieceSystem = class {
|
|
|
408
532
|
__publicField(this, "dq");
|
|
409
533
|
__publicField(this, "m");
|
|
410
534
|
__publicField(this, "ax");
|
|
535
|
+
__publicField(this, "solids", []);
|
|
411
536
|
this.p = new THREE.Vector3();
|
|
412
537
|
this.q = new THREE.Quaternion();
|
|
413
538
|
this.dq = new THREE.Quaternion();
|
|
@@ -417,6 +542,11 @@ var PieceSystem = class {
|
|
|
417
542
|
get(objectId) {
|
|
418
543
|
return this.list.find((b) => b.objectId === objectId) ?? null;
|
|
419
544
|
}
|
|
545
|
+
/** Sets a piece falling over, about the world axes x and z (a unit axis): its feet are thinner than the box that stands for it, so the box alone would hold it up. */
|
|
546
|
+
tip(body, x, z) {
|
|
547
|
+
body.tipping = { x, z, seconds: TIP_SECONDS };
|
|
548
|
+
body.sleeping = false;
|
|
549
|
+
}
|
|
420
550
|
create(objectId, root, halfExtents, localCenter, mass, velocity, angular) {
|
|
421
551
|
const existing = this.get(objectId);
|
|
422
552
|
if (existing) {
|
|
@@ -430,6 +560,7 @@ var PieceSystem = class {
|
|
|
430
560
|
}
|
|
431
561
|
const body = {
|
|
432
562
|
objectId,
|
|
563
|
+
tipping: null,
|
|
433
564
|
root,
|
|
434
565
|
velocity: velocity ? [...velocity] : [0, 0, 0],
|
|
435
566
|
angularVelocity: angular ? [...angular] : [0, 0, 0],
|
|
@@ -499,8 +630,21 @@ var PieceSystem = class {
|
|
|
499
630
|
const lz = (e[8] * x + e[9] * y + e[10] * z) * iz;
|
|
500
631
|
return [e[0] * lx + e[4] * ly + e[8] * lz, e[1] * lx + e[5] * ly + e[9] * lz, e[2] * lx + e[6] * ly + e[10] * lz];
|
|
501
632
|
}
|
|
633
|
+
/** What lies inside the disc looks at the ground again: it was dug or heaped under it. */
|
|
634
|
+
wakeRegion(x, z, radius) {
|
|
635
|
+
for (const b of this.list) {
|
|
636
|
+
if (!b.sleeping || b.held || Math.hypot(b.root.position.x - x, b.root.position.z - z) > radius) continue;
|
|
637
|
+
b.sleeping = false;
|
|
638
|
+
b.still = 0;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
502
641
|
update(dt) {
|
|
503
642
|
this.time += dt;
|
|
643
|
+
this.solids = this.list.map((body) => {
|
|
644
|
+
const c = this.centerOf(body, new this.THREE.Vector3());
|
|
645
|
+
const h = body.halfExtents;
|
|
646
|
+
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]) };
|
|
647
|
+
});
|
|
504
648
|
for (const b of this.list) {
|
|
505
649
|
if (b.held) {
|
|
506
650
|
b.velocity = [0, 0, 0];
|
|
@@ -514,6 +658,17 @@ var PieceSystem = class {
|
|
|
514
658
|
}
|
|
515
659
|
this.separate(dt);
|
|
516
660
|
}
|
|
661
|
+
/** Where a corner of `self` at `(x, y, z)` lands: the ground, the top of another piece or of something fixed under it. */
|
|
662
|
+
supportAt(self, x, y, z, step) {
|
|
663
|
+
let top = this.o.groundAt(x, z);
|
|
664
|
+
for (const s of this.solids) {
|
|
665
|
+
if (s.body === self || s.body.held || Math.abs(x - s.c[0]) > s.r || Math.abs(z - s.c[2]) > s.r) continue;
|
|
666
|
+
const t = boxTopAt(s, x, z);
|
|
667
|
+
if (t > top && t <= y + step) top = t;
|
|
668
|
+
}
|
|
669
|
+
const fixed = this.o.staticTopAt?.(x, z, y, step) ?? -Infinity;
|
|
670
|
+
return Math.max(top, fixed);
|
|
671
|
+
}
|
|
517
672
|
integrate(b, dt) {
|
|
518
673
|
const c = this.centerOf(b, this.p);
|
|
519
674
|
const pos = [c.x, c.y, c.z];
|
|
@@ -552,8 +707,9 @@ var PieceSystem = class {
|
|
|
552
707
|
const sy = i & 2 ? hy : -hy;
|
|
553
708
|
const sz = i & 4 ? hz : -hz;
|
|
554
709
|
const r = [e[0] * sx + e[4] * sy + e[8] * sz, e[1] * sx + e[5] * sy + e[9] * sz, e[2] * sx + e[6] * sy + e[10] * sz];
|
|
555
|
-
const
|
|
556
|
-
const
|
|
710
|
+
const cy = pos[1] + r[1];
|
|
711
|
+
const gy = this.supportAt(b, pos[0] + r[0], cy, pos[2] + r[2], STEP + Math.max(0, -v[1]) * h * 2);
|
|
712
|
+
const pen = gy - cy;
|
|
557
713
|
if (pen > -SLOP) {
|
|
558
714
|
rs.push(r);
|
|
559
715
|
if (pen > maxPen) maxPen = pen;
|
|
@@ -576,7 +732,7 @@ var PieceSystem = class {
|
|
|
576
732
|
}
|
|
577
733
|
const lx = pos[0] + sx / rs.length;
|
|
578
734
|
const lz = pos[2] + sz / rs.length;
|
|
579
|
-
landPoint = [lx,
|
|
735
|
+
landPoint = [lx, pos[1] - Math.max(...rs.map((r) => -r[1])), lz];
|
|
580
736
|
}
|
|
581
737
|
for (let it = 0; it < 4; it++) {
|
|
582
738
|
for (const r of rs) {
|
|
@@ -629,6 +785,15 @@ var PieceSystem = class {
|
|
|
629
785
|
const wx = x1 - x0;
|
|
630
786
|
const wz = z1 - z0;
|
|
631
787
|
unstable = Math.min(wx, wz) < NARROW * height || x0 > 0 || x1 < 0 || z0 > 0 || z1 < 0;
|
|
788
|
+
if (b.tipping) {
|
|
789
|
+
b.tipping.seconds -= h;
|
|
790
|
+
if (height < TIP_LYING || b.tipping.seconds <= 0) b.tipping = null;
|
|
791
|
+
else {
|
|
792
|
+
w[0] += b.tipping.x * TIP_ACCELERATION * h;
|
|
793
|
+
w[2] += b.tipping.z * TIP_ACCELERATION * h;
|
|
794
|
+
unstable = true;
|
|
795
|
+
}
|
|
796
|
+
}
|
|
632
797
|
if (unstable) {
|
|
633
798
|
const dirX = x0 + x1 > 0 ? -1 : 1;
|
|
634
799
|
const dirZ = z0 + z1 > 0 ? -1 : 1;
|
|
@@ -665,6 +830,11 @@ var PieceSystem = class {
|
|
|
665
830
|
b.still = 0;
|
|
666
831
|
}
|
|
667
832
|
}
|
|
833
|
+
/** Half the height of the box of a piece as it is turned now. */
|
|
834
|
+
halfHeight(b) {
|
|
835
|
+
const e = this.m.makeRotationFromQuaternion(b.root.quaternion).elements;
|
|
836
|
+
return Math.abs(e[1]) * b.halfExtents[0] + Math.abs(e[5]) * b.halfExtents[1] + Math.abs(e[9]) * b.halfExtents[2];
|
|
837
|
+
}
|
|
668
838
|
/** Push overlapping pieces apart (horizontal bounding circles), gently, so they do not interpenetrate forever. */
|
|
669
839
|
separate(dt) {
|
|
670
840
|
const n = this.list.length;
|
|
@@ -683,6 +853,7 @@ var PieceSystem = class {
|
|
|
683
853
|
this.centerOf(a, ca);
|
|
684
854
|
this.centerOf(b, cb);
|
|
685
855
|
if (Math.abs(ca.y - cb.y) > ra + rb) continue;
|
|
856
|
+
if (Math.abs(ca.y - cb.y) >= (this.halfHeight(a) + this.halfHeight(b)) * 0.9) continue;
|
|
686
857
|
const rh = (Math.max(a.halfExtents[0], a.halfExtents[2]) + Math.max(b.halfExtents[0], b.halfExtents[2])) * 0.8;
|
|
687
858
|
let dx = cb.x - ca.x;
|
|
688
859
|
let dz = cb.z - ca.z;
|
|
@@ -843,6 +1014,7 @@ function specFromObject3D(THREE, root, options = {}) {
|
|
|
843
1014
|
const fill = options.fillOf?.(o, material);
|
|
844
1015
|
const tags = Array.isArray(ud.destructionTags) ? ud.destructionTags : void 0;
|
|
845
1016
|
const contains = containsOf(ud.destructionContains);
|
|
1017
|
+
const thickness = typeof ud.destructionThickness === "number" && ud.destructionThickness > 0 ? ud.destructionThickness : void 0;
|
|
846
1018
|
const existing = explicit ? byKey.get(explicit) : void 0;
|
|
847
1019
|
if (existing) {
|
|
848
1020
|
const before = sizeOf(existing.box);
|
|
@@ -855,8 +1027,9 @@ function specFromObject3D(THREE, root, options = {}) {
|
|
|
855
1027
|
existing.skin = existing.skin && skin;
|
|
856
1028
|
if (fill !== void 0) existing.fill = fill;
|
|
857
1029
|
if (contains) existing.contains = contains;
|
|
1030
|
+
if (thickness !== void 0) existing.thickness = Math.min(existing.thickness ?? thickness, thickness);
|
|
858
1031
|
} else {
|
|
859
|
-
const e = { key: explicit ?? `m${entries.length}`, meshes: [o], box, material, explicitId: explicit, fill, hollow, skin, tags, fixedId: !!explicit, ...contains ? { contains } : {}, name: o.name, parent: hint?.parent, jointKind: hint?.kind };
|
|
1032
|
+
const e = { key: explicit ?? `m${entries.length}`, meshes: [o], box, material, explicitId: explicit, fill, hollow, skin, tags, fixedId: !!explicit, ...contains ? { contains } : {}, ...thickness !== void 0 ? { thickness } : {}, name: o.name, parent: hint?.parent, jointKind: hint?.kind };
|
|
860
1033
|
entries.push(e);
|
|
861
1034
|
if (explicit) byKey.set(explicit, e);
|
|
862
1035
|
}
|
|
@@ -924,7 +1097,8 @@ function specFromObject3D(THREE, root, options = {}) {
|
|
|
924
1097
|
const minSide = Math.min(...size);
|
|
925
1098
|
if (e.fill !== void 0) part.fill = e.fill;
|
|
926
1099
|
else if (e.hollow) part.fill = 0.05;
|
|
927
|
-
if (
|
|
1100
|
+
if (e.thickness !== void 0) part.thickness = e.thickness;
|
|
1101
|
+
else if (minSide < 0.01 && METAL.test(e.material)) {
|
|
928
1102
|
part.fill = e.fill ?? 1;
|
|
929
1103
|
part.thickness = Math.max(minSide, 5e-4);
|
|
930
1104
|
}
|
|
@@ -970,6 +1144,9 @@ function specFromObject3D(THREE, root, options = {}) {
|
|
|
970
1144
|
return { spec, parts: meshesOf };
|
|
971
1145
|
}
|
|
972
1146
|
|
|
1147
|
+
// src/renderer.ts
|
|
1148
|
+
import { kitOriginOf } from "@adatechnology/game-contracts";
|
|
1149
|
+
|
|
973
1150
|
// src/heat.ts
|
|
974
1151
|
var STOPS = [
|
|
975
1152
|
[450, 0],
|
|
@@ -1015,9 +1192,10 @@ function shellDisplacementAt(THREE, worldPoint, shells, out) {
|
|
|
1015
1192
|
const ids = [0, 1, 2].map((corner) => index ? index.getX(t * 3 + corner) : t * 3 + corner);
|
|
1016
1193
|
ids.forEach((id, corner) => corners[corner].set(shell.before[id * 3], shell.before[id * 3 + 1], shell.before[id * 3 + 2]));
|
|
1017
1194
|
triangle.set(corners[0], corners[1], corners[2]);
|
|
1195
|
+
if (triangle.getArea() <= 1e-14) continue;
|
|
1018
1196
|
triangle.closestPointToPoint(local, closest);
|
|
1019
1197
|
const distance = closest.distanceToSquared(local);
|
|
1020
|
-
if (distance
|
|
1198
|
+
if (!(distance < nearest)) continue;
|
|
1021
1199
|
nearest = distance;
|
|
1022
1200
|
nearestScale = shell.mesh.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
1023
1201
|
triangle.getBarycoord(closest, weights);
|
|
@@ -1196,6 +1374,20 @@ var Visuals = class {
|
|
|
1196
1374
|
return out;
|
|
1197
1375
|
}
|
|
1198
1376
|
/** Box of the part in world space. */
|
|
1377
|
+
/** Appends the boxes that follow the mesh in the world (`pushFittedBoxes`) to `out`. */
|
|
1378
|
+
fittedBoxes(mv, out) {
|
|
1379
|
+
const box = new this.THREE.Box3();
|
|
1380
|
+
if (mv.host) this.localBox(mv, box);
|
|
1381
|
+
else {
|
|
1382
|
+
const geometry = mv.mesh.geometry;
|
|
1383
|
+
if (!geometry.boundingBox) geometry.computeBoundingBox();
|
|
1384
|
+
if (geometry.boundingBox) box.copy(geometry.boundingBox);
|
|
1385
|
+
else box.makeEmpty();
|
|
1386
|
+
}
|
|
1387
|
+
if (box.isEmpty()) return;
|
|
1388
|
+
mv.mesh.updateWorldMatrix(true, false);
|
|
1389
|
+
pushFittedBoxes(box, mv.mesh.matrixWorld, out);
|
|
1390
|
+
}
|
|
1199
1391
|
worldBox(mv, out) {
|
|
1200
1392
|
if (!mv.host) return out.setFromObject(mv.mesh);
|
|
1201
1393
|
this.localBox(mv, out);
|
|
@@ -1940,9 +2132,15 @@ var Visuals = class {
|
|
|
1940
2132
|
|
|
1941
2133
|
// src/renderer.ts
|
|
1942
2134
|
var rendererCount = 0;
|
|
2135
|
+
var COLLAPSE_CLOUD_KG = 1e3;
|
|
1943
2136
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
1944
2137
|
var GLOW_FROM_C = 430;
|
|
1945
2138
|
var QUEUE_CAP = 4e3;
|
|
2139
|
+
var STATIC_REFRESH = 0.5;
|
|
2140
|
+
var STATIC_SPAN = 3;
|
|
2141
|
+
var FEET_BAND = 0.03;
|
|
2142
|
+
var TIP_MIN_HEIGHT = 0.3;
|
|
2143
|
+
var TIP_NARROW = 0.3;
|
|
1946
2144
|
var FIRE_CEILING_SECONDS = 3600;
|
|
1947
2145
|
var GENERIC_REFERENCE_RADIUS = 15;
|
|
1948
2146
|
var PRIMED_FIRE_RADIUS = 0.2;
|
|
@@ -1987,6 +2185,7 @@ function impactKey(def) {
|
|
|
1987
2185
|
if (def.fracture === "tear") return "impact_soft";
|
|
1988
2186
|
return def.fracture === "none" ? "impact_metal" : "impact_wood";
|
|
1989
2187
|
}
|
|
2188
|
+
var dragSoundKey = (def) => impactKey(def).replace("impact_", "drag_");
|
|
1990
2189
|
function fractureKey(def, style) {
|
|
1991
2190
|
switch (style) {
|
|
1992
2191
|
case "shatter":
|
|
@@ -2028,6 +2227,14 @@ var Renderer = class {
|
|
|
2028
2227
|
__publicField(this, "autoSplit");
|
|
2029
2228
|
__publicField(this, "maxFragments");
|
|
2030
2229
|
__publicField(this, "bounds", /* @__PURE__ */ new Map());
|
|
2230
|
+
/** World boxes (min xyz, max xyz) of the drawn meshes of the bound objects that are not pieces, for the pieces to land on. */
|
|
2231
|
+
__publicField(this, "staticBoxes", /* @__PURE__ */ new Map());
|
|
2232
|
+
/** Objects to measure again; `staticAll` measures every one. */
|
|
2233
|
+
__publicField(this, "staticDirty", /* @__PURE__ */ new Set());
|
|
2234
|
+
__publicField(this, "staticAll", true);
|
|
2235
|
+
/** Boxes of the pieces' meshes (as `staticBoxes`), with the time they were measured. */
|
|
2236
|
+
__publicField(this, "pieceBoxes", /* @__PURE__ */ new WeakMap());
|
|
2237
|
+
__publicField(this, "staticAt", 0);
|
|
2031
2238
|
__publicField(this, "queue", []);
|
|
2032
2239
|
__publicField(this, "unsubscribe");
|
|
2033
2240
|
__publicField(this, "visuals");
|
|
@@ -2089,6 +2296,7 @@ var Renderer = class {
|
|
|
2089
2296
|
this.cages = new RebarCages(THREE, o.scene);
|
|
2090
2297
|
this.pieceSys = new PieceSystem(THREE, {
|
|
2091
2298
|
groundAt: this.groundAt,
|
|
2299
|
+
staticTopAt: (x, z, y, step) => this.staticTopAt(x, z, y, step),
|
|
2092
2300
|
gravity: this.gravity,
|
|
2093
2301
|
scene: o.scene,
|
|
2094
2302
|
onHardLanding: (p, point, speed) => this.hardLanding(p, point, speed)
|
|
@@ -2101,6 +2309,7 @@ var Renderer = class {
|
|
|
2101
2309
|
// Binding
|
|
2102
2310
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
2103
2311
|
bind(objectId, root, options = {}) {
|
|
2312
|
+
this.staticDirty.add(objectId);
|
|
2104
2313
|
if (this.bounds.has(objectId)) this.unbind(objectId);
|
|
2105
2314
|
const state = this.world.state(objectId);
|
|
2106
2315
|
const partIds = new Set((state?.parts ?? []).map((p) => p.id));
|
|
@@ -2220,6 +2429,7 @@ var Renderer = class {
|
|
|
2220
2429
|
}
|
|
2221
2430
|
}
|
|
2222
2431
|
unbind(objectId) {
|
|
2432
|
+
this.staticDirty.add(objectId);
|
|
2223
2433
|
const b = this.bounds.get(objectId);
|
|
2224
2434
|
if (!b) return;
|
|
2225
2435
|
this.restore(objectId);
|
|
@@ -2259,6 +2469,7 @@ var Renderer = class {
|
|
|
2259
2469
|
return m > 0 ? m : 5;
|
|
2260
2470
|
}
|
|
2261
2471
|
makePiece(objectId, options = {}) {
|
|
2472
|
+
this.staticDirty.add(objectId);
|
|
2262
2473
|
const b = this.bounds.get(objectId);
|
|
2263
2474
|
if (!b) throw new Error(`game-destruction-three: object "${objectId}" is not bound`);
|
|
2264
2475
|
if (b.root.parent && b.root.parent !== this.scene) this.scene.attach(b.root);
|
|
@@ -2371,7 +2582,100 @@ var Renderer = class {
|
|
|
2371
2582
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
2372
2583
|
// Events
|
|
2373
2584
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
2585
|
+
/**
|
|
2586
|
+
* The highest top of a bound object that is not a piece under (x, z) a corner at height `y` can land on (at most `step` above it). The world boxes
|
|
2587
|
+
* of their drawn meshes are kept until an event or a moved root changes them; parts drawn inside a merged mesh and meshes wider than `STATIC_SPAN`
|
|
2588
|
+
* are left out (walls, floors, roofs: the terrain under them is the host's `groundAt`).
|
|
2589
|
+
*/
|
|
2590
|
+
staticTopAt(x, z, y, step) {
|
|
2591
|
+
let top = -Infinity;
|
|
2592
|
+
for (const a of this.staticNow().values()) {
|
|
2593
|
+
if (a.length === 0 || x < a[0] || x > a[3] || z < a[2] || z > a[5]) continue;
|
|
2594
|
+
for (let i = 6; i < a.length; i += 6) {
|
|
2595
|
+
if (x < a[i] || x > a[i + 3] || z < a[i + 2] || z > a[i + 5]) continue;
|
|
2596
|
+
const t = a[i + 4];
|
|
2597
|
+
if (t > top && t <= y + step) top = t;
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
return top;
|
|
2601
|
+
}
|
|
2602
|
+
/** The boxes of the fixed objects (per object: the box around them all, then each one), each object measured again when an event touched it (all of them every `STATIC_REFRESH` s). */
|
|
2603
|
+
staticNow() {
|
|
2604
|
+
if (this.time - this.staticAt > STATIC_REFRESH) {
|
|
2605
|
+
this.staticAt = this.time;
|
|
2606
|
+
this.staticAll = true;
|
|
2607
|
+
}
|
|
2608
|
+
if (!this.staticAll && this.staticDirty.size === 0) return this.staticBoxes;
|
|
2609
|
+
const box = new this.THREE.Box3();
|
|
2610
|
+
const measure = (b) => {
|
|
2611
|
+
if (b.piece || this.pieceSys.get(b.id)) {
|
|
2612
|
+
this.staticBoxes.delete(b.id);
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
const out = [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];
|
|
2616
|
+
for (const mv of this.meshesOf(b)) {
|
|
2617
|
+
if (mv.host || mv.loose || !this.visuals.shown(mv)) continue;
|
|
2618
|
+
this.visuals.worldBox(mv, box);
|
|
2619
|
+
if (box.isEmpty() || box.max.x - box.min.x > STATIC_SPAN || box.max.z - box.min.z > STATIC_SPAN) continue;
|
|
2620
|
+
this.visuals.fittedBoxes(mv, out);
|
|
2621
|
+
for (let k = 0; k < 3; k++) {
|
|
2622
|
+
out[k] = Math.min(out[k], box.min.getComponent(k));
|
|
2623
|
+
out[k + 3] = Math.max(out[k + 3], box.max.getComponent(k));
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
this.staticBoxes.set(b.id, out.length > 6 ? out : []);
|
|
2627
|
+
};
|
|
2628
|
+
if (this.staticAll) {
|
|
2629
|
+
this.staticBoxes.clear();
|
|
2630
|
+
for (const b of this.bounds.values()) measure(b);
|
|
2631
|
+
} else {
|
|
2632
|
+
for (const id of this.staticDirty) {
|
|
2633
|
+
const b = this.bounds.get(id);
|
|
2634
|
+
if (b) measure(b);
|
|
2635
|
+
else this.staticBoxes.delete(id);
|
|
2636
|
+
}
|
|
2637
|
+
}
|
|
2638
|
+
this.staticAll = false;
|
|
2639
|
+
this.staticDirty.clear();
|
|
2640
|
+
return this.staticBoxes;
|
|
2641
|
+
}
|
|
2642
|
+
/** The boxes of a piece's drawn meshes, measured once per frame. */
|
|
2643
|
+
pieceBoxesOf(pc) {
|
|
2644
|
+
const cached = this.pieceBoxes.get(pc);
|
|
2645
|
+
if (cached && cached.at === this.time) return cached.boxes;
|
|
2646
|
+
const boxes = [];
|
|
2647
|
+
const b = this.bounds.get(pc.objectId);
|
|
2648
|
+
if (b) {
|
|
2649
|
+
for (const mv of this.meshesOf(b)) {
|
|
2650
|
+
if (this.visuals.shown(mv)) this.visuals.fittedBoxes(mv, boxes);
|
|
2651
|
+
}
|
|
2652
|
+
}
|
|
2653
|
+
this.pieceBoxes.set(pc, { at: this.time, boxes });
|
|
2654
|
+
return boxes;
|
|
2655
|
+
}
|
|
2656
|
+
solidsNear(x, z, radius) {
|
|
2657
|
+
const found = [];
|
|
2658
|
+
const scan = (a, id, piece, from) => {
|
|
2659
|
+
if (from > 0 && (a.length === 0 || x < a[0] - radius || x > a[3] + radius || z < a[2] - radius || z > a[5] + radius)) return;
|
|
2660
|
+
for (let i = from; i < a.length; i += 6) {
|
|
2661
|
+
if (x < a[i] - radius || x > a[i + 3] + radius || z < a[i + 2] - radius || z > a[i + 5] + radius) continue;
|
|
2662
|
+
found.push({ objectId: id, min: [a[i], a[i + 1], a[i + 2]], max: [a[i + 3], a[i + 4], a[i + 5]], piece });
|
|
2663
|
+
}
|
|
2664
|
+
};
|
|
2665
|
+
for (const [id, a] of this.staticNow()) scan(a, id, null, 6);
|
|
2666
|
+
const c = this.v1;
|
|
2667
|
+
for (const pc of this.pieceSys.list) {
|
|
2668
|
+
if (pc.held) continue;
|
|
2669
|
+
this.pieceSys.centerOf(pc, c);
|
|
2670
|
+
const reach = Math.hypot(pc.halfExtents[0], pc.halfExtents[1], pc.halfExtents[2]) + radius;
|
|
2671
|
+
if (Math.abs(c.x - x) > reach || Math.abs(c.z - z) > reach) continue;
|
|
2672
|
+
scan(this.pieceBoxesOf(pc), pc.objectId, pc, 0);
|
|
2673
|
+
}
|
|
2674
|
+
this.fragments.solidsNear(x, z, radius, (home, min, max, loose) => found.push({ objectId: home, min, max, piece: null, loose }));
|
|
2675
|
+
return found;
|
|
2676
|
+
}
|
|
2374
2677
|
apply(ev) {
|
|
2678
|
+
this.staticDirty.add(ev.object);
|
|
2375
2679
|
switch (ev.type) {
|
|
2376
2680
|
case "damage":
|
|
2377
2681
|
if (ev.damageType === "heat" || ev.damageType === "fire" || ev.damageType === "explosive") this.activate(ev.object, ev.part);
|
|
@@ -2403,10 +2707,14 @@ var Renderer = class {
|
|
|
2403
2707
|
case "settle":
|
|
2404
2708
|
this.onSettle(ev.object);
|
|
2405
2709
|
break;
|
|
2406
|
-
case "collapse":
|
|
2710
|
+
case "collapse": {
|
|
2711
|
+
const root = this.bounds.get(ev.object)?.root;
|
|
2712
|
+
const furniture = !!root && kitOriginOf(root)?.kind === "furniture";
|
|
2407
2713
|
this.splitLoose(ev.object);
|
|
2408
|
-
this.playEvent({ kind: "collapse" }, this.rootPoint(ev.object));
|
|
2714
|
+
if (!furniture || ev.mass >= COLLAPSE_CLOUD_KG) this.playEvent({ kind: "collapse" }, this.rootPoint(ev.object));
|
|
2715
|
+
else this.vfx.burst("dust", this.rootPoint(ev.object), [0, 1, 0], clamp(Math.round(ev.mass / 4), 4, 24), 0.8);
|
|
2409
2716
|
break;
|
|
2717
|
+
}
|
|
2410
2718
|
case "disassemble_progress": {
|
|
2411
2719
|
const k = `unscrew:${ev.joint}`;
|
|
2412
2720
|
if (this.time - (this.throttles.get(k) ?? -1) > 0.25) {
|
|
@@ -2436,6 +2744,9 @@ var Renderer = class {
|
|
|
2436
2744
|
case "stage":
|
|
2437
2745
|
this.onStage(ev);
|
|
2438
2746
|
break;
|
|
2747
|
+
case "chip":
|
|
2748
|
+
this.onChip(ev);
|
|
2749
|
+
break;
|
|
2439
2750
|
case "expose":
|
|
2440
2751
|
this.cages.set(`${ev.object}|${ev.part}`, ev.bars);
|
|
2441
2752
|
if (ev.bars.length > 0) {
|
|
@@ -2692,6 +3003,23 @@ var Renderer = class {
|
|
|
2692
3003
|
if (!played) this.vfx.burst("dust", ev.opening.center, [0, 1, 0], 14, 1.2, { color });
|
|
2693
3004
|
this.sound(impactKey(def), ev.opening.center, 0.8);
|
|
2694
3005
|
}
|
|
3006
|
+
/**
|
|
3007
|
+
* A unit came out of a wall of units: the unit flies as a fragment with a puff of its dust. The gap itself is the drawing's (the house renderer redraws
|
|
3008
|
+
* the wall as its courses without that unit); a part drawn as one box keeps its box.
|
|
3009
|
+
*/
|
|
3010
|
+
onChip(ev) {
|
|
3011
|
+
const pv = this.partVis(ev.object, ev.part);
|
|
3012
|
+
const def = pv ? this.world.materials.get(pv.material) : void 0;
|
|
3013
|
+
const color = (pv ? this.partColor(pv, def) : null) ?? fallbackColor(pv?.material ?? "", def);
|
|
3014
|
+
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
3015
|
+
for (const fr of ev.fragments) {
|
|
3016
|
+
const fdef = this.world.materials.get(fr.material);
|
|
3017
|
+
const tone2 = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === def ? color : fallbackColor(fr.material, fdef);
|
|
3018
|
+
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, tone2, false), ev.object, maxAlive);
|
|
3019
|
+
}
|
|
3020
|
+
if (!this.playEvent({ kind: "spall", material: this.impactMaterial(def) }, ev.center)) this.vfx.burst("dust", ev.center, [0, 1, 0], 6, 0.8, { color });
|
|
3021
|
+
this.sound(impactKey(def), ev.center, 0.7);
|
|
3022
|
+
}
|
|
2695
3023
|
onFracture(ev) {
|
|
2696
3024
|
const b = this.bounds.get(ev.object);
|
|
2697
3025
|
const pv = b?.parts.get(ev.part);
|
|
@@ -2769,6 +3097,9 @@ var Renderer = class {
|
|
|
2769
3097
|
this.pieceSys.setCenter(old, center);
|
|
2770
3098
|
old.mass = Math.max(0.05, this.massOf(objectId));
|
|
2771
3099
|
old.sleeping = false;
|
|
3100
|
+
} else if (kitOriginOf(b.root)?.kind === "furniture") {
|
|
3101
|
+
this.makePiece(objectId);
|
|
3102
|
+
this.tipIfTopHeavy(b);
|
|
2772
3103
|
}
|
|
2773
3104
|
}
|
|
2774
3105
|
/** The renderer side of `splitGroup`: a new group holding the meshes of the new object, made a piece. */
|
|
@@ -2815,8 +3146,32 @@ var Renderer = class {
|
|
|
2815
3146
|
}
|
|
2816
3147
|
const piece = this.pieceSys.create(newId, group, half, center, this.massOf(newId), vel, ang);
|
|
2817
3148
|
nb.piece = piece;
|
|
3149
|
+
if (kitOriginOf(from.root)?.kind === "furniture") this.tipIfTopHeavy(nb);
|
|
2818
3150
|
this.o.onPiece?.(piece);
|
|
2819
3151
|
}
|
|
3152
|
+
/**
|
|
3153
|
+
* 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
|
|
3154
|
+
* high and its feet are thin, so it gets the start of the fall, across the line of its feet.
|
|
3155
|
+
*/
|
|
3156
|
+
tipIfTopHeavy(b) {
|
|
3157
|
+
const piece = b.piece;
|
|
3158
|
+
if (!piece) return;
|
|
3159
|
+
const box = new this.THREE.Box3();
|
|
3160
|
+
const boxes = this.meshesOf(b).filter((mv) => this.visuals.shown(mv)).map((mv) => this.visuals.worldBox(mv, new this.THREE.Box3()));
|
|
3161
|
+
if (boxes.length === 0) return;
|
|
3162
|
+
for (const one of boxes) box.union(one);
|
|
3163
|
+
const height = box.max.y - box.min.y;
|
|
3164
|
+
const feet = new this.THREE.Box3();
|
|
3165
|
+
for (const one of boxes) if (one.min.y < box.min.y + FEET_BAND) feet.union(one);
|
|
3166
|
+
const spanX = feet.max.x - feet.min.x;
|
|
3167
|
+
const spanZ = feet.max.z - feet.min.z;
|
|
3168
|
+
if (height < TIP_MIN_HEIGHT || Math.min(spanX, spanZ) >= TIP_NARROW * height) return;
|
|
3169
|
+
const across = spanX <= spanZ ? 0 : 2;
|
|
3170
|
+
const lean = across === 0 ? box.min.x + box.max.x - feet.min.x - feet.max.x : box.min.z + box.max.z - feet.min.z - feet.max.z;
|
|
3171
|
+
const away = lean < 0 ? -1 : 1;
|
|
3172
|
+
if (across === 0) this.pieceSys.tip(piece, 0, -away);
|
|
3173
|
+
else this.pieceSys.tip(piece, away, 0);
|
|
3174
|
+
}
|
|
2820
3175
|
onExplode(ev, options = {}) {
|
|
2821
3176
|
const p = ev.point;
|
|
2822
3177
|
if (options.visual !== false) this.playExplosion(ev, options);
|
|
@@ -3067,6 +3422,10 @@ var Renderer = class {
|
|
|
3067
3422
|
this.vfx.sprites().clear();
|
|
3068
3423
|
this.ownVfx?.world.clear();
|
|
3069
3424
|
}
|
|
3425
|
+
wakeRegion(x, z, radius) {
|
|
3426
|
+
this.fragments.wakeRegion(x, z, radius);
|
|
3427
|
+
this.pieceSys.wakeRegion(x, z, radius);
|
|
3428
|
+
}
|
|
3070
3429
|
stats() {
|
|
3071
3430
|
return { fragments: this.fragments.count, particles: this.vfx.sprites().live + this.ownedVfxParticles(), pieces: this.pieceSys.list.length, decals: this.visuals.decalCount + this.ground.count };
|
|
3072
3431
|
}
|
|
@@ -3180,7 +3539,15 @@ var BURN_LOOP = {
|
|
|
3180
3539
|
volume: 0,
|
|
3181
3540
|
sources: ["cena:L1316 fire loop (silent in the page: its gain is never raised)", "sdk: the level while burning"]
|
|
3182
3541
|
};
|
|
3183
|
-
var
|
|
3542
|
+
var drag = (id, layers) => ({ ...sceneRecipe(id, layers, ""), sources: ["sdk: a base dragged on a floor, one grain per stretch"] });
|
|
3543
|
+
var DRAG_SOUNDS = [
|
|
3544
|
+
drag("drag_wood", [noise({ type: "bandpass", f: 380, q: 1.4, d: 0.28, vol: 0.5 }), noise({ type: "bandpass", f: [900, 1300], q: 3, d: 0.08, vol: 0.14 })]),
|
|
3545
|
+
drag("drag_metal", [noise({ type: "bandpass", f: 1500, q: 4, d: 0.26, vol: 0.32 }), tone({ f: [880, 1240], f2: 760, d: 0.18, vol: 0.05 })]),
|
|
3546
|
+
drag("drag_glass", [noise({ type: "bandpass", f: 2600, q: 5, d: 0.2, vol: 0.22 })]),
|
|
3547
|
+
drag("drag_masonry", [noise({ type: "bandpass", f: 260, q: 0.9, d: 0.3, vol: 0.62 }), ...many(3, () => noise({ type: "bandpass", f: [600, 1200], q: 2, d: 0.04, vol: 0.14, delay: [0, 0.2] }))]),
|
|
3548
|
+
drag("drag_soft", [noise({ type: "lowpass", f: 420, d: 0.28, vol: 0.3 })])
|
|
3549
|
+
];
|
|
3550
|
+
var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, EXPLOSION_BOOM], loops: [BURN_LOOP] };
|
|
3184
3551
|
|
|
3185
3552
|
// src/audio/sound.ts
|
|
3186
3553
|
import { GAME_SFX_BANK } from "@adatechnology/game-audio";
|
|
@@ -3191,6 +3558,11 @@ var DESTRUCTION_SOUND_KEYS = {
|
|
|
3191
3558
|
impact_glass: { sound: "ping" },
|
|
3192
3559
|
impact_masonry: { sound: "chip" },
|
|
3193
3560
|
impact_soft: { sound: "thud" },
|
|
3561
|
+
drag_wood: { sound: "drag_wood" },
|
|
3562
|
+
drag_metal: { sound: "drag_metal" },
|
|
3563
|
+
drag_glass: { sound: "drag_glass" },
|
|
3564
|
+
drag_masonry: { sound: "drag_masonry" },
|
|
3565
|
+
drag_soft: { sound: "drag_soft" },
|
|
3194
3566
|
shatter_glass: { sound: "glass_shatter" },
|
|
3195
3567
|
crumble: { sound: "crumble" },
|
|
3196
3568
|
splinter: { sound: "crack" },
|
|
@@ -3263,11 +3635,14 @@ export {
|
|
|
3263
3635
|
DEFAULT_FIRE,
|
|
3264
3636
|
DESTRUCTION_SOUND_BANK,
|
|
3265
3637
|
DESTRUCTION_SOUND_KEYS,
|
|
3638
|
+
DRAG_SOUNDS,
|
|
3266
3639
|
EXPLOSION_BOOM,
|
|
3267
3640
|
SCENE_SOUNDS,
|
|
3268
3641
|
createDestructionRenderer,
|
|
3269
3642
|
createDestructionSound,
|
|
3643
|
+
dragSoundKey,
|
|
3270
3644
|
heatColor,
|
|
3645
|
+
impactKey,
|
|
3271
3646
|
specFromObject3D
|
|
3272
3647
|
};
|
|
3273
3648
|
//# sourceMappingURL=index.js.map
|