@adatechnology/game-destruction-three 0.10.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 +556 -29
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +370 -19
- 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
|
}
|
|
@@ -1018,9 +1192,10 @@ function shellDisplacementAt(THREE, worldPoint, shells, out) {
|
|
|
1018
1192
|
const ids = [0, 1, 2].map((corner) => index ? index.getX(t * 3 + corner) : t * 3 + corner);
|
|
1019
1193
|
ids.forEach((id, corner) => corners[corner].set(shell.before[id * 3], shell.before[id * 3 + 1], shell.before[id * 3 + 2]));
|
|
1020
1194
|
triangle.set(corners[0], corners[1], corners[2]);
|
|
1195
|
+
if (triangle.getArea() <= 1e-14) continue;
|
|
1021
1196
|
triangle.closestPointToPoint(local, closest);
|
|
1022
1197
|
const distance = closest.distanceToSquared(local);
|
|
1023
|
-
if (distance
|
|
1198
|
+
if (!(distance < nearest)) continue;
|
|
1024
1199
|
nearest = distance;
|
|
1025
1200
|
nearestScale = shell.mesh.matrixWorld.getMaxScaleOnAxis() || 1;
|
|
1026
1201
|
triangle.getBarycoord(closest, weights);
|
|
@@ -1199,6 +1374,20 @@ var Visuals = class {
|
|
|
1199
1374
|
return out;
|
|
1200
1375
|
}
|
|
1201
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
|
+
}
|
|
1202
1391
|
worldBox(mv, out) {
|
|
1203
1392
|
if (!mv.host) return out.setFromObject(mv.mesh);
|
|
1204
1393
|
this.localBox(mv, out);
|
|
@@ -1947,6 +2136,11 @@ var COLLAPSE_CLOUD_KG = 1e3;
|
|
|
1947
2136
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
1948
2137
|
var GLOW_FROM_C = 430;
|
|
1949
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;
|
|
1950
2144
|
var FIRE_CEILING_SECONDS = 3600;
|
|
1951
2145
|
var GENERIC_REFERENCE_RADIUS = 15;
|
|
1952
2146
|
var PRIMED_FIRE_RADIUS = 0.2;
|
|
@@ -1991,6 +2185,7 @@ function impactKey(def) {
|
|
|
1991
2185
|
if (def.fracture === "tear") return "impact_soft";
|
|
1992
2186
|
return def.fracture === "none" ? "impact_metal" : "impact_wood";
|
|
1993
2187
|
}
|
|
2188
|
+
var dragSoundKey = (def) => impactKey(def).replace("impact_", "drag_");
|
|
1994
2189
|
function fractureKey(def, style) {
|
|
1995
2190
|
switch (style) {
|
|
1996
2191
|
case "shatter":
|
|
@@ -2032,6 +2227,14 @@ var Renderer = class {
|
|
|
2032
2227
|
__publicField(this, "autoSplit");
|
|
2033
2228
|
__publicField(this, "maxFragments");
|
|
2034
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);
|
|
2035
2238
|
__publicField(this, "queue", []);
|
|
2036
2239
|
__publicField(this, "unsubscribe");
|
|
2037
2240
|
__publicField(this, "visuals");
|
|
@@ -2093,6 +2296,7 @@ var Renderer = class {
|
|
|
2093
2296
|
this.cages = new RebarCages(THREE, o.scene);
|
|
2094
2297
|
this.pieceSys = new PieceSystem(THREE, {
|
|
2095
2298
|
groundAt: this.groundAt,
|
|
2299
|
+
staticTopAt: (x, z, y, step) => this.staticTopAt(x, z, y, step),
|
|
2096
2300
|
gravity: this.gravity,
|
|
2097
2301
|
scene: o.scene,
|
|
2098
2302
|
onHardLanding: (p, point, speed) => this.hardLanding(p, point, speed)
|
|
@@ -2105,6 +2309,7 @@ var Renderer = class {
|
|
|
2105
2309
|
// Binding
|
|
2106
2310
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
2107
2311
|
bind(objectId, root, options = {}) {
|
|
2312
|
+
this.staticDirty.add(objectId);
|
|
2108
2313
|
if (this.bounds.has(objectId)) this.unbind(objectId);
|
|
2109
2314
|
const state = this.world.state(objectId);
|
|
2110
2315
|
const partIds = new Set((state?.parts ?? []).map((p) => p.id));
|
|
@@ -2224,6 +2429,7 @@ var Renderer = class {
|
|
|
2224
2429
|
}
|
|
2225
2430
|
}
|
|
2226
2431
|
unbind(objectId) {
|
|
2432
|
+
this.staticDirty.add(objectId);
|
|
2227
2433
|
const b = this.bounds.get(objectId);
|
|
2228
2434
|
if (!b) return;
|
|
2229
2435
|
this.restore(objectId);
|
|
@@ -2263,6 +2469,7 @@ var Renderer = class {
|
|
|
2263
2469
|
return m > 0 ? m : 5;
|
|
2264
2470
|
}
|
|
2265
2471
|
makePiece(objectId, options = {}) {
|
|
2472
|
+
this.staticDirty.add(objectId);
|
|
2266
2473
|
const b = this.bounds.get(objectId);
|
|
2267
2474
|
if (!b) throw new Error(`game-destruction-three: object "${objectId}" is not bound`);
|
|
2268
2475
|
if (b.root.parent && b.root.parent !== this.scene) this.scene.attach(b.root);
|
|
@@ -2375,7 +2582,100 @@ var Renderer = class {
|
|
|
2375
2582
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
2376
2583
|
// Events
|
|
2377
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
|
+
}
|
|
2378
2677
|
apply(ev) {
|
|
2678
|
+
this.staticDirty.add(ev.object);
|
|
2379
2679
|
switch (ev.type) {
|
|
2380
2680
|
case "damage":
|
|
2381
2681
|
if (ev.damageType === "heat" || ev.damageType === "fire" || ev.damageType === "explosive") this.activate(ev.object, ev.part);
|
|
@@ -2712,7 +3012,11 @@ var Renderer = class {
|
|
|
2712
3012
|
const def = pv ? this.world.materials.get(pv.material) : void 0;
|
|
2713
3013
|
const color = (pv ? this.partColor(pv, def) : null) ?? fallbackColor(pv?.material ?? "", def);
|
|
2714
3014
|
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
2715
|
-
for (const fr of ev.fragments)
|
|
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
|
+
}
|
|
2716
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 });
|
|
2717
3021
|
this.sound(impactKey(def), ev.center, 0.7);
|
|
2718
3022
|
}
|
|
@@ -2793,6 +3097,9 @@ var Renderer = class {
|
|
|
2793
3097
|
this.pieceSys.setCenter(old, center);
|
|
2794
3098
|
old.mass = Math.max(0.05, this.massOf(objectId));
|
|
2795
3099
|
old.sleeping = false;
|
|
3100
|
+
} else if (kitOriginOf(b.root)?.kind === "furniture") {
|
|
3101
|
+
this.makePiece(objectId);
|
|
3102
|
+
this.tipIfTopHeavy(b);
|
|
2796
3103
|
}
|
|
2797
3104
|
}
|
|
2798
3105
|
/** The renderer side of `splitGroup`: a new group holding the meshes of the new object, made a piece. */
|
|
@@ -2839,8 +3146,32 @@ var Renderer = class {
|
|
|
2839
3146
|
}
|
|
2840
3147
|
const piece = this.pieceSys.create(newId, group, half, center, this.massOf(newId), vel, ang);
|
|
2841
3148
|
nb.piece = piece;
|
|
3149
|
+
if (kitOriginOf(from.root)?.kind === "furniture") this.tipIfTopHeavy(nb);
|
|
2842
3150
|
this.o.onPiece?.(piece);
|
|
2843
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
|
+
}
|
|
2844
3175
|
onExplode(ev, options = {}) {
|
|
2845
3176
|
const p = ev.point;
|
|
2846
3177
|
if (options.visual !== false) this.playExplosion(ev, options);
|
|
@@ -3091,6 +3422,10 @@ var Renderer = class {
|
|
|
3091
3422
|
this.vfx.sprites().clear();
|
|
3092
3423
|
this.ownVfx?.world.clear();
|
|
3093
3424
|
}
|
|
3425
|
+
wakeRegion(x, z, radius) {
|
|
3426
|
+
this.fragments.wakeRegion(x, z, radius);
|
|
3427
|
+
this.pieceSys.wakeRegion(x, z, radius);
|
|
3428
|
+
}
|
|
3094
3429
|
stats() {
|
|
3095
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 };
|
|
3096
3431
|
}
|
|
@@ -3204,7 +3539,15 @@ var BURN_LOOP = {
|
|
|
3204
3539
|
volume: 0,
|
|
3205
3540
|
sources: ["cena:L1316 fire loop (silent in the page: its gain is never raised)", "sdk: the level while burning"]
|
|
3206
3541
|
};
|
|
3207
|
-
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] };
|
|
3208
3551
|
|
|
3209
3552
|
// src/audio/sound.ts
|
|
3210
3553
|
import { GAME_SFX_BANK } from "@adatechnology/game-audio";
|
|
@@ -3215,6 +3558,11 @@ var DESTRUCTION_SOUND_KEYS = {
|
|
|
3215
3558
|
impact_glass: { sound: "ping" },
|
|
3216
3559
|
impact_masonry: { sound: "chip" },
|
|
3217
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" },
|
|
3218
3566
|
shatter_glass: { sound: "glass_shatter" },
|
|
3219
3567
|
crumble: { sound: "crumble" },
|
|
3220
3568
|
splinter: { sound: "crack" },
|
|
@@ -3287,11 +3635,14 @@ export {
|
|
|
3287
3635
|
DEFAULT_FIRE,
|
|
3288
3636
|
DESTRUCTION_SOUND_BANK,
|
|
3289
3637
|
DESTRUCTION_SOUND_KEYS,
|
|
3638
|
+
DRAG_SOUNDS,
|
|
3290
3639
|
EXPLOSION_BOOM,
|
|
3291
3640
|
SCENE_SOUNDS,
|
|
3292
3641
|
createDestructionRenderer,
|
|
3293
3642
|
createDestructionSound,
|
|
3643
|
+
dragSoundKey,
|
|
3294
3644
|
heatColor,
|
|
3645
|
+
impactKey,
|
|
3295
3646
|
specFromObject3D
|
|
3296
3647
|
};
|
|
3297
3648
|
//# sourceMappingURL=index.js.map
|