@adatechnology/game-destruction-three 0.12.1 → 0.14.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 +1 -1
- package/dist/index.d.ts +161 -14
- package/dist/index.global.js +2371 -320
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +728 -110
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -2,8 +2,45 @@ 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/audio/impact-keys.ts
|
|
6
|
+
var IMPACT_FAMILY_TAGS = [
|
|
7
|
+
["metal", "metal"],
|
|
8
|
+
["glass", "glass"],
|
|
9
|
+
["ceramic", "ceramic"],
|
|
10
|
+
["soil", "soil"],
|
|
11
|
+
["masonry", "masonry"],
|
|
12
|
+
["wood", "wood"],
|
|
13
|
+
["plant", "plant"],
|
|
14
|
+
["plastic", "plastic"],
|
|
15
|
+
["rubber", "rubber"],
|
|
16
|
+
["wax", "wax"],
|
|
17
|
+
["fabric", "soft"]
|
|
18
|
+
];
|
|
19
|
+
function impactFamily(def) {
|
|
20
|
+
const tags = def?.tags ?? [];
|
|
21
|
+
for (const [tag, family] of IMPACT_FAMILY_TAGS) if (tags.includes(tag)) return family;
|
|
22
|
+
return void 0;
|
|
23
|
+
}
|
|
24
|
+
var ordered = (prefix, material, family, blow) => {
|
|
25
|
+
const keys = [];
|
|
26
|
+
if (material && blow) keys.push(`${prefix}_${material}_${blow}`);
|
|
27
|
+
if (material) keys.push(`${prefix}_${material}`);
|
|
28
|
+
if (family && blow) keys.push(`${prefix}_${family}_${blow}`);
|
|
29
|
+
if (family) keys.push(`${prefix}_${family}`);
|
|
30
|
+
return keys;
|
|
31
|
+
};
|
|
32
|
+
function impactSoundKeys(material, def, blow) {
|
|
33
|
+
return { keys: ordered("impact", material, impactFamily(def), blow) };
|
|
34
|
+
}
|
|
35
|
+
function breakSoundKeys(material, def, blow) {
|
|
36
|
+
return { keys: ordered("break", material, impactFamily(def), blow) };
|
|
37
|
+
}
|
|
38
|
+
function ricochetSoundKeys(material, def) {
|
|
39
|
+
return { keys: ordered("ricochet", material, impactFamily(def), void 0) };
|
|
40
|
+
}
|
|
41
|
+
|
|
5
42
|
// src/renderer.ts
|
|
6
|
-
import { blastShove } from "@adatechnology/game-contracts";
|
|
43
|
+
import { DEFERRED_QUEUE_ID, blastShove, warnOnce } from "@adatechnology/game-contracts";
|
|
7
44
|
import { createVfx, effectForEvent, EVENT_EFFECTS_MAP } from "@adatechnology/game-vfx";
|
|
8
45
|
import { createVfxRenderer } from "@adatechnology/game-vfx-three";
|
|
9
46
|
|
|
@@ -92,6 +129,7 @@ var SHEET_SWAY = 3;
|
|
|
92
129
|
var BIG_LENGTH = 0.2;
|
|
93
130
|
var BIG_WIDTH = 0.03;
|
|
94
131
|
var FRAGMENT_SEED = 1374496523;
|
|
132
|
+
var SHARD_SHAPES = 6;
|
|
95
133
|
var isBig = (size) => {
|
|
96
134
|
const [, middle, longest] = [...size].sort((a, b) => a - b);
|
|
97
135
|
return longest >= BIG_LENGTH && middle >= BIG_WIDTH;
|
|
@@ -141,7 +179,7 @@ var FragmentSystem = class {
|
|
|
141
179
|
return m;
|
|
142
180
|
}
|
|
143
181
|
shardGeometry(seed) {
|
|
144
|
-
const idx = seed %
|
|
182
|
+
const idx = seed % SHARD_SHAPES;
|
|
145
183
|
let g = this.shardGeos[idx];
|
|
146
184
|
if (!g) {
|
|
147
185
|
const r = createRng(1e3 + idx);
|
|
@@ -162,17 +200,32 @@ var FragmentSystem = class {
|
|
|
162
200
|
}
|
|
163
201
|
return g;
|
|
164
202
|
}
|
|
203
|
+
markParts() {
|
|
204
|
+
this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
|
|
205
|
+
this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
|
|
206
|
+
return { geometry: this.markGeometry, material: this.markMaterial };
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Meshes, never put in a scene by this class, that draw every shared piece of a fragment once: the box, the six shards, the bullet-hole disc, and the opaque and
|
|
210
|
+
* glass materials. A host draws them at load so the first blast does not upload these buffers in the middle of a frame.
|
|
211
|
+
*/
|
|
212
|
+
warmMeshes() {
|
|
213
|
+
const opaque = this.materialFor("warm", 8421504, false);
|
|
214
|
+
const glass2 = this.materialFor("warm", 8421504, true);
|
|
215
|
+
const geometries = [this.box, ...Array.from({ length: SHARD_SHAPES }, (_, index) => this.shardGeometry(index))];
|
|
216
|
+
const { geometry, material } = this.markParts();
|
|
217
|
+
return [...geometries.flatMap((shared) => [new this.THREE.Mesh(shared, opaque), new this.THREE.Mesh(shared, glass2)]), new this.THREE.Mesh(geometry, material)];
|
|
218
|
+
}
|
|
165
219
|
/**
|
|
166
220
|
* 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
|
|
167
221
|
* 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.
|
|
168
222
|
*/
|
|
169
223
|
addMarks(mesh, f) {
|
|
170
224
|
if (!f.marks?.length) return;
|
|
171
|
-
|
|
172
|
-
this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
|
|
225
|
+
const { geometry, material } = this.markParts();
|
|
173
226
|
const sc = mesh.scale;
|
|
174
227
|
for (const mark of f.marks) {
|
|
175
|
-
const disc = new this.THREE.Mesh(
|
|
228
|
+
const disc = new this.THREE.Mesh(geometry, material);
|
|
176
229
|
const radius = Math.max(4e-3, mark.diameter) * 0.6;
|
|
177
230
|
const n = this.v.set(mark.normal[0], mark.normal[1], mark.normal[2]).normalize();
|
|
178
231
|
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);
|
|
@@ -512,6 +565,9 @@ var GroundSpots = class {
|
|
|
512
565
|
};
|
|
513
566
|
|
|
514
567
|
// src/pieces.ts
|
|
568
|
+
var SLAM_MIN_SPEED = 2;
|
|
569
|
+
var SLAM_BOUNCE = 0.2;
|
|
570
|
+
var SLAM_FLOOR_NORMAL = 0.7;
|
|
515
571
|
var SUB_DT = 1 / 120;
|
|
516
572
|
var MU = 0.7;
|
|
517
573
|
var SLEEP_AFTER = 0.5;
|
|
@@ -746,9 +802,34 @@ var PieceSystem = class {
|
|
|
746
802
|
const fixed = this.o.staticTopAt?.(x, z, y, step) ?? -Infinity;
|
|
747
803
|
return Math.max(top, fixed);
|
|
748
804
|
}
|
|
805
|
+
/** Stops a piece that ran into something on its way from `start` to `pos` (`sweep`): moves it back to where it met it and takes the speed into it. */
|
|
806
|
+
sweepOn(b, start, pos) {
|
|
807
|
+
const travel = Math.hypot(pos[0] - start[0], pos[1] - start[1], pos[2] - start[2]);
|
|
808
|
+
if (travel < 1e-3) return;
|
|
809
|
+
const hit = this.o.sweep?.(b, start, pos);
|
|
810
|
+
if (!hit || hit.normal[1] > SLAM_FLOOR_NORMAL) return;
|
|
811
|
+
const v = b.velocity;
|
|
812
|
+
const n = hit.normal;
|
|
813
|
+
const into = -(v[0] * n[0] + v[1] * n[1] + v[2] * n[2]);
|
|
814
|
+
if (into <= 0) return;
|
|
815
|
+
const k = Math.min(1, hit.distance / travel);
|
|
816
|
+
for (let i = 0; i < 3; i++) {
|
|
817
|
+
pos[i] = start[i] + (pos[i] - start[i]) * k;
|
|
818
|
+
v[i] += (1 + SLAM_BOUNCE) * into * n[i];
|
|
819
|
+
}
|
|
820
|
+
const w = b.angularVelocity;
|
|
821
|
+
w[0] *= 0.5;
|
|
822
|
+
w[1] *= 0.5;
|
|
823
|
+
w[2] *= 0.5;
|
|
824
|
+
if (into >= SLAM_MIN_SPEED && this.time - b.lastHit > 0.15) {
|
|
825
|
+
b.lastHit = this.time;
|
|
826
|
+
this.o.onSlam?.(b, hit, into);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
749
829
|
integrate(b, dt) {
|
|
750
830
|
const c = this.centerOf(b, this.p);
|
|
751
831
|
const pos = [c.x, c.y, c.z];
|
|
832
|
+
const start = [c.x, c.y, c.z];
|
|
752
833
|
const q = this.q.copy(b.root.quaternion);
|
|
753
834
|
const v = b.velocity;
|
|
754
835
|
const w = b.angularVelocity;
|
|
@@ -858,11 +939,11 @@ var PieceSystem = class {
|
|
|
858
939
|
if (contactedLast) touched = true;
|
|
859
940
|
if (!contactedLast) continue;
|
|
860
941
|
pos[1] += Math.max(0, maxPen - SLOP * 0.5);
|
|
861
|
-
let
|
|
862
|
-
for (const r of rs)
|
|
863
|
-
const restitution =
|
|
864
|
-
if (
|
|
865
|
-
landing =
|
|
942
|
+
let impact2 = 0;
|
|
943
|
+
for (const r of rs) impact2 = Math.max(impact2, -(v[1] + w[2] * r[0] - w[0] * r[2]));
|
|
944
|
+
const restitution = impact2 > 2 ? 0.25 : 0;
|
|
945
|
+
if (impact2 > landing) {
|
|
946
|
+
landing = impact2;
|
|
866
947
|
let sx = 0;
|
|
867
948
|
let sz = 0;
|
|
868
949
|
for (const r of rs) {
|
|
@@ -993,6 +1074,7 @@ var PieceSystem = class {
|
|
|
993
1074
|
v[0] *= fd;
|
|
994
1075
|
v[2] *= fd;
|
|
995
1076
|
}
|
|
1077
|
+
if (this.o.sweep && !b.held) this.sweepOn(b, start, pos);
|
|
996
1078
|
b.root.quaternion.copy(q);
|
|
997
1079
|
const off = this.ax.copy(b.centerV).applyQuaternion(q);
|
|
998
1080
|
b.root.position.set(pos[0] - off.x, pos[1] - off.y, pos[2] - off.z);
|
|
@@ -1517,6 +1599,7 @@ var Visuals = class {
|
|
|
1517
1599
|
this.b = tmpV(THREE);
|
|
1518
1600
|
this.m4 = new THREE.Matrix4();
|
|
1519
1601
|
this.ray = new THREE.Raycaster();
|
|
1602
|
+
this.ray.layers.enableAll();
|
|
1520
1603
|
this.box = new THREE.Box3();
|
|
1521
1604
|
this.t = tmpV(THREE);
|
|
1522
1605
|
this.lbox = new THREE.Box3();
|
|
@@ -2151,10 +2234,10 @@ var Visuals = class {
|
|
|
2151
2234
|
const sy = lerp(1, 0.25, melt);
|
|
2152
2235
|
const sxz = 1 + 0.18 * melt;
|
|
2153
2236
|
for (let i = range[0] * 3, v = 0; i < range[1] * 3; i += 3, v++) {
|
|
2154
|
-
const
|
|
2237
|
+
const noise3 = (Math.imul(v + 1, 2654435761) >>> 0) % 1e3 / 1e3;
|
|
2155
2238
|
arr[i] = cx + (work[i] - cx) * sxz;
|
|
2156
2239
|
arr[i + 2] = cz + (work[i + 2] - cz) * sxz;
|
|
2157
|
-
arr[i + 1] = Math.max(minY, minY + (work[i + 1] - minY) * sy -
|
|
2240
|
+
arr[i + 1] = Math.max(minY, minY + (work[i + 1] - minY) * sy - noise3 * melt * h * 0.04);
|
|
2158
2241
|
}
|
|
2159
2242
|
}
|
|
2160
2243
|
/** The colour attribute of the host's current geometry (created white when there is none) with vertex colours switched on in its material. */
|
|
@@ -2404,12 +2487,14 @@ var GLOW_FROM_C = 430;
|
|
|
2404
2487
|
var QUEUE_CAP = 4e3;
|
|
2405
2488
|
var MIN_FRAGMENTS_PER_SLICE = 8;
|
|
2406
2489
|
var STATIC_REFRESH = 0.5;
|
|
2490
|
+
var STATIC_REFRESH_SLICE_MS = 1;
|
|
2407
2491
|
var STATIC_SPAN = 3;
|
|
2408
2492
|
var FEET_BAND = 0.03;
|
|
2409
2493
|
var TIP_MIN_HEIGHT = 0.3;
|
|
2410
2494
|
var TIP_NARROW = 0.3;
|
|
2411
2495
|
var FIRE_CEILING_SECONDS = 3600;
|
|
2412
2496
|
var GENERIC_REFERENCE_RADIUS = 15;
|
|
2497
|
+
var SWEEP_CORE = 0.15;
|
|
2413
2498
|
var PRIMED_FIRE_RADIUS = 0.2;
|
|
2414
2499
|
var FLUID_COLORS = {
|
|
2415
2500
|
water: 9226495,
|
|
@@ -2452,6 +2537,7 @@ function impactKey(def) {
|
|
|
2452
2537
|
if (def.fracture === "tear") return "impact_soft";
|
|
2453
2538
|
return def.fracture === "none" ? "impact_metal" : "impact_wood";
|
|
2454
2539
|
}
|
|
2540
|
+
var shotKey = (def) => impactKey(def).replace("impact_", "shot_");
|
|
2455
2541
|
var dragSoundKey = (def) => impactKey(def).replace("impact_", "drag_");
|
|
2456
2542
|
function fractureKey(def, style) {
|
|
2457
2543
|
switch (style) {
|
|
@@ -2501,14 +2587,34 @@ var Renderer = class {
|
|
|
2501
2587
|
/** Objects to measure again; `staticAll` measures every one. */
|
|
2502
2588
|
__publicField(this, "staticDirty", /* @__PURE__ */ new Set());
|
|
2503
2589
|
__publicField(this, "staticAll", true);
|
|
2590
|
+
/** The periodic refresh: the objects still to measure again in this round, a slice per call. */
|
|
2591
|
+
__publicField(this, "staticRound", []);
|
|
2504
2592
|
/** Boxes of the pieces' meshes (as `staticBoxes`), with the time they were measured. */
|
|
2505
2593
|
__publicField(this, "pieceBoxes", /* @__PURE__ */ new WeakMap());
|
|
2506
2594
|
__publicField(this, "staticAt", 0);
|
|
2507
2595
|
__publicField(this, "queue", []);
|
|
2508
2596
|
/** Presentation work an event left for later frames (fragments still to spawn, loose groups still to split), run in order within the frame budget. */
|
|
2509
2597
|
__publicField(this, "slices", []);
|
|
2510
|
-
/** When the work of this frame must stop; `Infinity` outside
|
|
2598
|
+
/** When the work of this frame must stop; `Infinity` outside a drain, where a call finishes what it started. */
|
|
2511
2599
|
__publicField(this, "sliceDeadline", Infinity);
|
|
2600
|
+
/** The clock the drain reads; a budget lends its own while it runs the queue. */
|
|
2601
|
+
__publicField(this, "drainClock", () => performance.now());
|
|
2602
|
+
/** true once a frame budget owns the draining: `update` no longer drains by itself. */
|
|
2603
|
+
__publicField(this, "attached", false);
|
|
2604
|
+
__publicField(this, "eventQueue", {
|
|
2605
|
+
id: DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS,
|
|
2606
|
+
pending: () => this.queue.length + this.slices.length,
|
|
2607
|
+
run: (deadline, clock) => this.runBudgeted(deadline, clock),
|
|
2608
|
+
flush: () => {
|
|
2609
|
+
for (let guard = 0; guard < 16 && this.queue.length + this.slices.length > 0; guard++) this.runBudgeted(Infinity, () => 0);
|
|
2610
|
+
},
|
|
2611
|
+
attach: () => {
|
|
2612
|
+
this.attached = true;
|
|
2613
|
+
},
|
|
2614
|
+
detach: () => {
|
|
2615
|
+
this.attached = false;
|
|
2616
|
+
}
|
|
2617
|
+
});
|
|
2512
2618
|
__publicField(this, "unsubscribe");
|
|
2513
2619
|
__publicField(this, "visuals");
|
|
2514
2620
|
__publicField(this, "fragments");
|
|
@@ -2554,6 +2660,7 @@ var Renderer = class {
|
|
|
2554
2660
|
const maxParticles = Math.max(8, Math.floor((o.maxParticles ?? 2500) * (this.low ? 0.5 : 1)));
|
|
2555
2661
|
this.visuals = new Visuals(THREE, this.low, 600);
|
|
2556
2662
|
this.ground = new GroundSpots(THREE, o.scene, this.groundAt, 48);
|
|
2663
|
+
if (!o.vfx) warnOnce("game-destruction-three.own-effects-world", "game-destruction-three made an effects world of its own because the host lent none (options.vfx); pass the host effects so the effects share one budget");
|
|
2557
2664
|
this.vfx = o.vfx ?? createVfx({ profile: this.quality, gravity: this.gravity, groundAt: this.groundAt, sprites: { capacity: maxParticles, density: this.low ? 0.6 : 1 } });
|
|
2558
2665
|
this.ownVfx = o.vfx ? void 0 : createVfxRenderer(THREE, { scene: o.scene, camera: o.camera ?? new THREE.PerspectiveCamera(), vfx: this.vfx, pointScale: o.pointScale ?? 700 });
|
|
2559
2666
|
this.stopLanding = this.vfx.onLand(this.owner, (x, _y, z, color, tag) => {
|
|
@@ -2573,7 +2680,9 @@ var Renderer = class {
|
|
|
2573
2680
|
staticTopAt: (x, z, y, step) => this.staticTopAt(x, z, y, step),
|
|
2574
2681
|
gravity: this.gravity,
|
|
2575
2682
|
scene: o.scene,
|
|
2576
|
-
onHardLanding: (p, point, speed) => this.hardLanding(p, point, speed)
|
|
2683
|
+
onHardLanding: (p, point, speed) => this.hardLanding(p, point, speed),
|
|
2684
|
+
sweep: (p, from, to) => this.sweepPiece(p, from, to),
|
|
2685
|
+
onSlam: (p, hit, speed) => this.slam(p, hit, speed)
|
|
2577
2686
|
});
|
|
2578
2687
|
this.unsubscribe = this.world.on("*", (ev) => {
|
|
2579
2688
|
if (this.queue.length < QUEUE_CAP) this.queue.push(ev);
|
|
@@ -2763,26 +2872,58 @@ var Renderer = class {
|
|
|
2763
2872
|
const p = this.pieceSys.get(objectId);
|
|
2764
2873
|
if (p) this.pieceSys.impulse(p, impulse, point);
|
|
2765
2874
|
}
|
|
2875
|
+
/**
|
|
2876
|
+
* What a piece moving from `from` to `to` runs into: the core of its box (its thinnest half each way, at most `SWEEP_CORE`) swept through the world, the piece itself left out,
|
|
2877
|
+
* so a crate thrown by a blast stops at the wall instead of passing through it. Null for a world that cannot sweep.
|
|
2878
|
+
*/
|
|
2879
|
+
sweepPiece(p, from, to) {
|
|
2880
|
+
if (typeof this.world.sweepBox !== "function") return null;
|
|
2881
|
+
const d = [to[0] - from[0], to[1] - from[1], to[2] - from[2]];
|
|
2882
|
+
const travel = Math.hypot(d[0], d[1], d[2]);
|
|
2883
|
+
if (travel < 1e-4) return null;
|
|
2884
|
+
const core = Math.min(SWEEP_CORE, ...p.halfExtents);
|
|
2885
|
+
const box = { min: [from[0] - core, from[1] - core, from[2] - core], max: [from[0] + core, from[1] + core, from[2] + core] };
|
|
2886
|
+
const hit = this.world.sweepBox(box, d, travel, { ignore: p.objectId, spacing: Math.max(0.05, core) });
|
|
2887
|
+
if (!hit) return null;
|
|
2888
|
+
return { object: hit.object, part: hit.part, distance: hit.distance, point: hit.point, normal: hit.normal };
|
|
2889
|
+
}
|
|
2890
|
+
/** A piece thrown into a wall: the wall takes the blow of its weight where it met it, the piece takes it too, and each sounds as its material does. */
|
|
2891
|
+
slam(p, hit, speed) {
|
|
2892
|
+
const energy = 0.5 * p.mass * speed * speed;
|
|
2893
|
+
const direction = [-hit.normal[0], -hit.normal[1], -hit.normal[2]];
|
|
2894
|
+
this.landingSound(hit.object, hit.point, clamp(speed / 10, 0.2, 1));
|
|
2895
|
+
this.syncBound(p.objectId);
|
|
2896
|
+
try {
|
|
2897
|
+
this.world.hitPart(hit.object, hit.part, { type: "blunt", point: hit.point, energy, radius: 0.1, direction, normal: hit.normal, source: p.objectId });
|
|
2898
|
+
this.world.hit(p.objectId, { type: "blunt", point: hit.point, energy, radius: 0.08, direction: hit.normal, source: "slam" });
|
|
2899
|
+
} catch {
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2766
2902
|
hardLanding(p, point, speed) {
|
|
2767
|
-
|
|
2768
|
-
this.sound(key, point, clamp(speed / 10, 0.2, 1));
|
|
2903
|
+
this.landingSound(p.objectId, point, clamp(speed / 10, 0.2, 1));
|
|
2769
2904
|
this.syncBound(p.objectId);
|
|
2770
2905
|
try {
|
|
2771
2906
|
this.world.hit(p.objectId, { type: "blunt", point, energy: 0.5 * p.mass * speed * speed, radius: 0.08, direction: [0, -1, 0], source: "fall" });
|
|
2772
2907
|
} catch {
|
|
2773
2908
|
}
|
|
2774
2909
|
}
|
|
2775
|
-
|
|
2910
|
+
/** The impact of the heaviest part of an object (a piece landing): its material and the blunt blow of the fall. */
|
|
2911
|
+
landingSound(objectId, point, volume) {
|
|
2776
2912
|
const st = this.world.state(objectId);
|
|
2777
|
-
let best;
|
|
2913
|
+
let best = "";
|
|
2778
2914
|
let mass = -1;
|
|
2779
2915
|
for (const p of st?.parts ?? []) {
|
|
2780
2916
|
if (p.mass > mass) {
|
|
2781
2917
|
mass = p.mass;
|
|
2782
|
-
best =
|
|
2918
|
+
best = p.material;
|
|
2783
2919
|
}
|
|
2784
2920
|
}
|
|
2785
|
-
|
|
2921
|
+
this.impactSound(best, point, volume, "blunt");
|
|
2922
|
+
}
|
|
2923
|
+
/** The impact on a material by a blow: the legacy family key (`impactKey`) with the more specific keys to try first (`impactSoundKeys`). */
|
|
2924
|
+
impactSound(material, point, volume, blow) {
|
|
2925
|
+
const def = this.world.materials.get(material);
|
|
2926
|
+
this.sound(impactKey(def), point, volume, impactSoundKeys(material, def, blow));
|
|
2786
2927
|
}
|
|
2787
2928
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
2788
2929
|
// Update
|
|
@@ -2791,7 +2932,7 @@ var Renderer = class {
|
|
|
2791
2932
|
if (this.disposed) return;
|
|
2792
2933
|
dt = clamp(dt, 0, 0.1);
|
|
2793
2934
|
this.time += dt;
|
|
2794
|
-
this.drainQueue();
|
|
2935
|
+
if (!this.attached) this.drainQueue();
|
|
2795
2936
|
for (const b of this.bounds.values()) b.onFrame?.(dt);
|
|
2796
2937
|
for (const b of this.bounds.values()) if (b.soilNode && this.world.state(b.id)?.soil?.status === "yielding") this.onSettle(b.id);
|
|
2797
2938
|
this.updateActive(dt);
|
|
@@ -2802,36 +2943,50 @@ var Renderer = class {
|
|
|
2802
2943
|
this.ownVfx?.update(dt);
|
|
2803
2944
|
this.syncTransforms();
|
|
2804
2945
|
}
|
|
2946
|
+
deferredQueue() {
|
|
2947
|
+
return this.eventQueue;
|
|
2948
|
+
}
|
|
2805
2949
|
drainQueue() {
|
|
2950
|
+
this.runBudgeted(performance.now() + this.eventBudgetMs, () => performance.now());
|
|
2951
|
+
}
|
|
2952
|
+
/** Applies the waiting events in order until the deadline, after the slices earlier events left; always at least one unit when something waits. Returns the units run. */
|
|
2953
|
+
runBudgeted(deadline, clock) {
|
|
2806
2954
|
const batch = this.queue.splice(0, this.queue.length);
|
|
2807
|
-
const
|
|
2955
|
+
const outerClock = this.drainClock;
|
|
2956
|
+
this.drainClock = clock;
|
|
2808
2957
|
this.sliceDeadline = deadline;
|
|
2958
|
+
let ran = 0;
|
|
2809
2959
|
try {
|
|
2810
|
-
this.runSlices(deadline);
|
|
2960
|
+
ran = this.runSlices(deadline);
|
|
2811
2961
|
for (let i = 0; i < batch.length; i++) {
|
|
2812
|
-
if (i > 0 &&
|
|
2962
|
+
if ((i > 0 || ran > 0 && this.attached) && clock() >= deadline) {
|
|
2813
2963
|
this.queue.unshift(...batch.slice(i));
|
|
2814
|
-
return;
|
|
2964
|
+
return ran;
|
|
2815
2965
|
}
|
|
2816
2966
|
try {
|
|
2817
2967
|
this.apply(batch[i]);
|
|
2818
2968
|
} catch (err) {
|
|
2819
2969
|
if (typeof console !== "undefined") console.error("game-destruction-three: failed to apply", batch[i].type, err);
|
|
2820
2970
|
}
|
|
2971
|
+
ran++;
|
|
2821
2972
|
}
|
|
2822
2973
|
} finally {
|
|
2823
2974
|
this.sliceDeadline = Infinity;
|
|
2975
|
+
this.drainClock = outerClock;
|
|
2824
2976
|
}
|
|
2977
|
+
return ran;
|
|
2825
2978
|
}
|
|
2826
2979
|
/** Runs the work left by earlier frames until the deadline; at least one piece per frame, so it always finishes. */
|
|
2827
2980
|
runSlices(deadline) {
|
|
2828
|
-
|
|
2981
|
+
let ran = 0;
|
|
2982
|
+
for (; this.slices.length > 0 && (ran === 0 || this.drainClock() < deadline); ran++) {
|
|
2829
2983
|
try {
|
|
2830
2984
|
this.slices.shift()();
|
|
2831
2985
|
} catch (err) {
|
|
2832
2986
|
if (typeof console !== "undefined") console.error("game-destruction-three: failed to run deferred work", err);
|
|
2833
2987
|
}
|
|
2834
2988
|
}
|
|
2989
|
+
return ran;
|
|
2835
2990
|
}
|
|
2836
2991
|
/**
|
|
2837
2992
|
* The ground under an object settled or tilted: its parts are drawn under a node with the soil's offset (`ObjectState.soil.offset`), so the host's
|
|
@@ -2900,9 +3055,9 @@ var Renderer = class {
|
|
|
2900
3055
|
staticNow() {
|
|
2901
3056
|
if (this.time - this.staticAt > STATIC_REFRESH) {
|
|
2902
3057
|
this.staticAt = this.time;
|
|
2903
|
-
this.
|
|
3058
|
+
if (this.staticRound.length === 0) this.staticRound = [...this.bounds.keys()].reverse();
|
|
2904
3059
|
}
|
|
2905
|
-
if (!this.staticAll && this.staticDirty.size === 0) return this.staticBoxes;
|
|
3060
|
+
if (!this.staticAll && this.staticDirty.size === 0 && this.staticRound.length === 0) return this.staticBoxes;
|
|
2906
3061
|
const box = new this.THREE.Box3();
|
|
2907
3062
|
const measure = (b) => {
|
|
2908
3063
|
if (b.piece || this.pieceSys.get(b.id)) {
|
|
@@ -2922,14 +3077,21 @@ var Renderer = class {
|
|
|
2922
3077
|
}
|
|
2923
3078
|
this.staticBoxes.set(b.id, out.length > 6 ? out : []);
|
|
2924
3079
|
};
|
|
3080
|
+
const measureId = (id) => {
|
|
3081
|
+
const b = this.bounds.get(id);
|
|
3082
|
+
if (b) measure(b);
|
|
3083
|
+
else this.staticBoxes.delete(id);
|
|
3084
|
+
};
|
|
2925
3085
|
if (this.staticAll) {
|
|
2926
3086
|
this.staticBoxes.clear();
|
|
2927
3087
|
for (const b of this.bounds.values()) measure(b);
|
|
3088
|
+
this.staticRound = [];
|
|
2928
3089
|
} else {
|
|
2929
|
-
for (const id of this.staticDirty)
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
|
|
3090
|
+
for (const id of this.staticDirty) measureId(id);
|
|
3091
|
+
const until = performance.now() + STATIC_REFRESH_SLICE_MS;
|
|
3092
|
+
while (this.staticRound.length > 0) {
|
|
3093
|
+
measureId(this.staticRound.pop());
|
|
3094
|
+
if (performance.now() >= until) break;
|
|
2933
3095
|
}
|
|
2934
3096
|
}
|
|
2935
3097
|
this.staticAll = false;
|
|
@@ -2989,7 +3151,8 @@ var Renderer = class {
|
|
|
2989
3151
|
case "ricochet": {
|
|
2990
3152
|
const d = ev.direction;
|
|
2991
3153
|
this.vfx.burst("sparks", ev.point, d, 4 + Math.min(10, ev.energy / 150), 4);
|
|
2992
|
-
this.
|
|
3154
|
+
const glanced = this.partVis(ev.object, ev.part)?.material ?? "";
|
|
3155
|
+
this.sound("ricochet", ev.point, 0.7, ricochetSoundKeys(glanced, this.world.materials.get(glanced)));
|
|
2993
3156
|
break;
|
|
2994
3157
|
}
|
|
2995
3158
|
case "debris":
|
|
@@ -3031,7 +3194,7 @@ var Renderer = class {
|
|
|
3031
3194
|
this.vfx.fire({ point: ev.point, radius: PRIMED_FIRE_RADIUS, life: ev.delay });
|
|
3032
3195
|
this.primedBurning.add(`${ev.object}|${ev.part}`);
|
|
3033
3196
|
this.activate(ev.object, ev.part);
|
|
3034
|
-
this.sound("
|
|
3197
|
+
this.sound("primed", ev.point, 0.7, { seconds: ev.delay });
|
|
3035
3198
|
this.sound("burn_loop", ev.point, 0.5);
|
|
3036
3199
|
}
|
|
3037
3200
|
break;
|
|
@@ -3145,8 +3308,9 @@ var Renderer = class {
|
|
|
3145
3308
|
const b = this.bounds.get(objectId);
|
|
3146
3309
|
return b ? [b.root.position.x, b.root.position.y, b.root.position.z] : [0, 0, 0];
|
|
3147
3310
|
}
|
|
3148
|
-
sound(key, point, volume) {
|
|
3149
|
-
this.o.onSound?.(key, point, volume);
|
|
3311
|
+
sound(key, point, volume, detail) {
|
|
3312
|
+
if (detail) this.o.onSound?.(key, point, volume, detail);
|
|
3313
|
+
else this.o.onSound?.(key, point, volume);
|
|
3150
3314
|
}
|
|
3151
3315
|
partVis(objectId, partId) {
|
|
3152
3316
|
return this.bounds.get(objectId)?.parts.get(partId) ?? null;
|
|
@@ -3211,7 +3375,7 @@ var Renderer = class {
|
|
|
3211
3375
|
const reach = shown.reduce((widest, mv) => Math.max(widest, this.visuals.dentReach(mv, wp, ev.dent)), ev.dent.radius);
|
|
3212
3376
|
this.visuals.applyPartDent(shown, wp, wn, ev.dent, reach);
|
|
3213
3377
|
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);
|
|
3214
|
-
this.
|
|
3378
|
+
this.impactSound(pv.material, ev.worldPoint, clamp(0.3 + ev.dent.depth * 12, 0.3, 1), ev.blow);
|
|
3215
3379
|
}
|
|
3216
3380
|
onHole(ev) {
|
|
3217
3381
|
const b = this.bounds.get(ev.object);
|
|
@@ -3240,7 +3404,7 @@ var Renderer = class {
|
|
|
3240
3404
|
this.vfx.burst("dust", ev.worldPoint, [n.x, n.y, n.z], this.low ? 5 : 10, 0.7, puff);
|
|
3241
3405
|
if (ev.hole.through && ev.exitPoint) this.vfx.burst("dust", ev.exitPoint, [-n.x, -n.y, -n.z], this.low ? 10 : 20, 1.3, puff);
|
|
3242
3406
|
}
|
|
3243
|
-
this.sound(
|
|
3407
|
+
this.sound(shotKey(def), ev.worldPoint, 0.6, impactSoundKeys(pv.material, def, ev.blow));
|
|
3244
3408
|
}
|
|
3245
3409
|
onCrack(ev) {
|
|
3246
3410
|
const b = this.bounds.get(ev.object);
|
|
@@ -3250,13 +3414,13 @@ var Renderer = class {
|
|
|
3250
3414
|
const wp = new this.THREE.Vector3(ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]);
|
|
3251
3415
|
const mv = this.nearestMesh(pv, wp);
|
|
3252
3416
|
if (mv) {
|
|
3253
|
-
const
|
|
3417
|
+
const glass2 = !!def?.visual?.transparent;
|
|
3254
3418
|
const own = this.partColor(pv, def) ?? fallbackColor(pv.material, def);
|
|
3255
|
-
const color =
|
|
3256
|
-
const length =
|
|
3419
|
+
const color = glass2 ? 15267071 : new this.THREE.Color(own).multiplyScalar(0.45).getHex();
|
|
3420
|
+
const length = glass2 ? ev.crack.length : Math.min(ev.crack.length, OPAQUE_CRACK);
|
|
3257
3421
|
this.visuals.addCrack(mv, wp, this.worldDir(b, ev.crack.normal), ev.crack.rays, length, color, hashNumbers(ev.time, ev.worldPoint[0], ev.worldPoint[1], ev.worldPoint[2]));
|
|
3258
3422
|
}
|
|
3259
|
-
this.
|
|
3423
|
+
this.impactSound(pv.material, ev.worldPoint, 0.5, ev.blow);
|
|
3260
3424
|
}
|
|
3261
3425
|
/** 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). */
|
|
3262
3426
|
faceNormal(mv, point) {
|
|
@@ -3313,7 +3477,7 @@ var Renderer = class {
|
|
|
3313
3477
|
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, fdef === def || !fdef ? color : fallbackColor(fr.material, fdef), false), ev.object, maxAlive);
|
|
3314
3478
|
}
|
|
3315
3479
|
if (!played) this.vfx.burst("dust", ev.opening.center, [0, 1, 0], 14, 1.2, { color });
|
|
3316
|
-
this.
|
|
3480
|
+
this.impactSound(pv?.material ?? "", ev.opening.center, 0.8, ev.blow);
|
|
3317
3481
|
}
|
|
3318
3482
|
/**
|
|
3319
3483
|
* A unit came out of a wall of units: the unit flies as a fragment with a puff of its dust, and the marks drawn on it (bullet holes, spalls, the impact
|
|
@@ -3327,11 +3491,11 @@ var Renderer = class {
|
|
|
3327
3491
|
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
3328
3492
|
for (const fr of ev.fragments) {
|
|
3329
3493
|
const fdef = this.world.materials.get(fr.material);
|
|
3330
|
-
const
|
|
3331
|
-
this.fragments.spawn(fr, this.fragments.materialFor(fr.material,
|
|
3494
|
+
const tone3 = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === def ? color : fallbackColor(fr.material, fdef);
|
|
3495
|
+
this.fragments.spawn(fr, this.fragments.materialFor(fr.material, tone3, false), ev.object, maxAlive);
|
|
3332
3496
|
}
|
|
3333
3497
|
if (ev.torn) {
|
|
3334
|
-
this.
|
|
3498
|
+
this.impactSound(pv?.material ?? "", ev.center, 0.5, ev.blow);
|
|
3335
3499
|
return;
|
|
3336
3500
|
}
|
|
3337
3501
|
const T = this.THREE;
|
|
@@ -3341,7 +3505,7 @@ var Renderer = class {
|
|
|
3341
3505
|
for (const part of this.bounds.get(ev.object)?.parts.values() ?? []) for (const mv of part.meshes) this.visuals.clearDecals(mv, center, half, rotation);
|
|
3342
3506
|
this.vfx.peekWorkshop()?.decals.clearInside(ev.center, [half.x, half.y, half.z], ev.rotation);
|
|
3343
3507
|
this.vfx.burst("dust", ev.center, [0, 1, 0], 8, 0.8, { color });
|
|
3344
|
-
this.
|
|
3508
|
+
this.impactSound(pv?.material ?? "", ev.center, 0.7, ev.blow);
|
|
3345
3509
|
}
|
|
3346
3510
|
onFracture(ev) {
|
|
3347
3511
|
const b = this.bounds.get(ev.object);
|
|
@@ -3382,21 +3546,21 @@ var Renderer = class {
|
|
|
3382
3546
|
default:
|
|
3383
3547
|
this.vfx.burst("sparks", ev.point, up, 10, 4);
|
|
3384
3548
|
}
|
|
3385
|
-
this.sound(fractureKey(def, ev.style), ev.point, clamp(0.4 + ev.fragments.length * 0.03, 0.4, 1));
|
|
3549
|
+
this.sound(fractureKey(def, ev.style), ev.point, clamp(0.4 + ev.fragments.length * 0.03, 0.4, 1), breakSoundKeys(matId, def, ev.blow));
|
|
3386
3550
|
}
|
|
3387
3551
|
/** Spawns the fragments of a fracture from index `from`; what does not fit in this frame's budget is spawned by the next frames. */
|
|
3388
3552
|
spawnFragments(ev, from, look) {
|
|
3389
3553
|
const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
|
|
3390
3554
|
const step = this.low ? 2 : 1;
|
|
3391
3555
|
for (let i = from; i < ev.fragments.length; i += step) {
|
|
3392
|
-
if (i - from >= MIN_FRAGMENTS_PER_SLICE * step &&
|
|
3556
|
+
if (i - from >= MIN_FRAGMENTS_PER_SLICE * step && this.drainClock() >= this.sliceDeadline) {
|
|
3393
3557
|
this.slices.push(() => this.spawnFragments(ev, i, look));
|
|
3394
3558
|
return;
|
|
3395
3559
|
}
|
|
3396
3560
|
const fr = ev.fragments[i];
|
|
3397
3561
|
const fdef = this.world.materials.get(fr.material);
|
|
3398
|
-
const
|
|
3399
|
-
const mat = this.fragments.materialFor(fr.material,
|
|
3562
|
+
const tone3 = fr.inner && fdef?.visual?.innerColor !== void 0 ? fdef.visual.innerColor : fdef === look.def || !fdef ? look.color : fallbackColor(fr.material, fdef);
|
|
3563
|
+
const mat = this.fragments.materialFor(fr.material, tone3, look.transparent || fdef?.visual?.transparent === true, look.opacity);
|
|
3400
3564
|
this.fragments.spawn(fr, mat, ev.object, maxAlive);
|
|
3401
3565
|
}
|
|
3402
3566
|
}
|
|
@@ -3413,7 +3577,7 @@ var Renderer = class {
|
|
|
3413
3577
|
if (!st || st.groups.length < 2) return;
|
|
3414
3578
|
const loose = st.groups.length - 1;
|
|
3415
3579
|
for (let i = 0; i < loose; i++) {
|
|
3416
|
-
if (i > 0 &&
|
|
3580
|
+
if (i > 0 && this.drainClock() >= this.sliceDeadline) {
|
|
3417
3581
|
this.slices.push(() => {
|
|
3418
3582
|
this.staticDirty.add(objectId);
|
|
3419
3583
|
this.splitLoose(objectId);
|
|
@@ -3519,10 +3683,11 @@ var Renderer = class {
|
|
|
3519
3683
|
const report = options.report;
|
|
3520
3684
|
const confinement = report?.find((r) => r.blast !== void 0)?.blast?.confinement ?? 1;
|
|
3521
3685
|
const energy = ev.energy * confinement;
|
|
3522
|
-
const reachOf = (objectId) => report?.find((r) => r.object === objectId)?.blast?.shelter ?? 1;
|
|
3686
|
+
const reachOf = (objectId, at) => report?.find((r) => r.object === objectId)?.blast?.shelter ?? this.world.blastReach?.(p, at, { ignore: ev.object ? [ev.object, objectId] : [objectId] }) ?? 1;
|
|
3523
3687
|
const shove = (pc) => {
|
|
3524
3688
|
this.pieceSys.centerOf(pc, c);
|
|
3525
|
-
|
|
3689
|
+
if (c.distanceTo(this.v2.set(p[0], p[1], p[2])) > ev.radius) return;
|
|
3690
|
+
const hit = blastShove({ point: p, energy, radius: ev.radius, reach: reachOf(pc.objectId, [c.x, c.y, c.z]) }, { position: [c.x, c.y, c.z], mass: pc.mass }, this.o.blastShove);
|
|
3526
3691
|
if (!hit) return;
|
|
3527
3692
|
const spread = pc.halfExtents[0];
|
|
3528
3693
|
const momentum = hit.speed * pc.mass;
|
|
@@ -3531,7 +3696,12 @@ var Renderer = class {
|
|
|
3531
3696
|
for (const pc of [...this.pieceSys.list]) shove(pc);
|
|
3532
3697
|
if (this.o.shoveable) this.loosen(p, ev.radius, energy, reachOf);
|
|
3533
3698
|
this.fragments.blast(p, ev.radius, Math.min(20, Math.sqrt(energy) * 0.08));
|
|
3534
|
-
this.sound("explode", p, clamp(0.5 + ev.radius / 10, 0.5, 1)
|
|
3699
|
+
this.sound("explode", p, clamp(0.5 + ev.radius / 10, 0.5, 1), {
|
|
3700
|
+
radius: ev.radius,
|
|
3701
|
+
...ev.effect !== void 0 ? { effect: ev.effect } : {},
|
|
3702
|
+
...options.effect !== void 0 ? { effect: options.effect } : {},
|
|
3703
|
+
...report ? { confinement } : {}
|
|
3704
|
+
});
|
|
3535
3705
|
}
|
|
3536
3706
|
/** The bound objects without a piece that the host lets a blast move (`shoveable`): the ones it reaches with enough speed become pieces and get the shove. */
|
|
3537
3707
|
loosen(point, radius, energy, reachOf) {
|
|
@@ -3542,7 +3712,8 @@ var Renderer = class {
|
|
|
3542
3712
|
const { half, center } = this.measure(b.root, this.meshesOf(b));
|
|
3543
3713
|
const world = center.clone().applyQuaternion(b.root.quaternion).add(b.root.position);
|
|
3544
3714
|
const mass = this.massOf(b.id);
|
|
3545
|
-
|
|
3715
|
+
if (world.distanceTo(this.v2.set(point[0], point[1], point[2])) > radius) continue;
|
|
3716
|
+
const hit = blastShove({ point, energy, radius, reach: reachOf(b.id, [world.x, world.y, world.z]) }, { position: [world.x, world.y, world.z], mass }, this.o.blastShove);
|
|
3546
3717
|
if (!hit) continue;
|
|
3547
3718
|
const piece = this.makePiece(b.id, { mass });
|
|
3548
3719
|
const body = this.pieceSys.get(b.id);
|
|
@@ -3566,9 +3737,9 @@ var Renderer = class {
|
|
|
3566
3737
|
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] } });
|
|
3567
3738
|
}
|
|
3568
3739
|
stopFire(key) {
|
|
3569
|
-
const
|
|
3570
|
-
if (!
|
|
3571
|
-
if (
|
|
3740
|
+
const fire2 = this.fires.get(key);
|
|
3741
|
+
if (!fire2) return;
|
|
3742
|
+
if (fire2.handle !== void 0) this.vfx.stop(fire2.handle);
|
|
3572
3743
|
this.fires.delete(key);
|
|
3573
3744
|
}
|
|
3574
3745
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -3605,9 +3776,9 @@ var Renderer = class {
|
|
|
3605
3776
|
if (ps.status === "melted") this.onGone(a.object, pv, "melted", def);
|
|
3606
3777
|
else if (ps.status === "ashes") this.onGone(a.object, pv, "ashes", def);
|
|
3607
3778
|
}
|
|
3608
|
-
const
|
|
3609
|
-
if (
|
|
3610
|
-
if (ps.burning && ps.status !== "broken" && ps.status !== "ashes" && ps.status !== "melted") this.emitFire(
|
|
3779
|
+
const fire2 = this.fires.get(key);
|
|
3780
|
+
if (fire2) {
|
|
3781
|
+
if (ps.burning && ps.status !== "broken" && ps.status !== "ashes" && ps.status !== "melted") this.emitFire(fire2, pv, def);
|
|
3611
3782
|
else this.stopFire(key);
|
|
3612
3783
|
}
|
|
3613
3784
|
const hot = def?.visual?.glows === true && ps.temperature > GLOW_FROM_C;
|
|
@@ -3737,10 +3908,12 @@ var Renderer = class {
|
|
|
3737
3908
|
this.scene.remove(b.root);
|
|
3738
3909
|
this.pieceSys.remove(b.id);
|
|
3739
3910
|
this.bounds.delete(b.id);
|
|
3911
|
+
this.staticDirty.add(b.id);
|
|
3740
3912
|
}
|
|
3741
3913
|
}
|
|
3742
3914
|
}
|
|
3743
3915
|
clear() {
|
|
3916
|
+
this.staticAll = true;
|
|
3744
3917
|
this.queue.length = 0;
|
|
3745
3918
|
this.slices.length = 0;
|
|
3746
3919
|
this.active.clear();
|
|
@@ -3767,6 +3940,8 @@ var Renderer = class {
|
|
|
3767
3940
|
this.rng = createRng(RNG_SEED);
|
|
3768
3941
|
this.time = 0;
|
|
3769
3942
|
this.staticAt = 0;
|
|
3943
|
+
this.staticRound = [];
|
|
3944
|
+
this.staticAll = true;
|
|
3770
3945
|
this.throttles.clear();
|
|
3771
3946
|
this.fragments.reset();
|
|
3772
3947
|
this.pieceSys.reset();
|
|
@@ -3792,6 +3967,7 @@ var Renderer = class {
|
|
|
3792
3967
|
const materials = [kit.discMaterial().clone(), kit.patchMaterial(8421504).clone(), kit.lineMaterial(0).clone()];
|
|
3793
3968
|
const group = new T.Group();
|
|
3794
3969
|
group.add(new T.Mesh(kit.discGeometry(), materials[0]), new T.Mesh(kit.discGeometry(), materials[1]), new T.LineSegments(geometry, materials[2]));
|
|
3970
|
+
group.add(...this.fragments.warmMeshes());
|
|
3795
3971
|
for (const probe of group.children) probe.frustumCulled = false;
|
|
3796
3972
|
this.warmProbes = { group, geometry, materials };
|
|
3797
3973
|
return { group, dispose: () => {
|
|
@@ -3823,43 +3999,242 @@ var Renderer = class {
|
|
|
3823
3999
|
};
|
|
3824
4000
|
|
|
3825
4001
|
// src/audio/recipes.ts
|
|
3826
|
-
import { DISTANCE_CURVES, sceneMany as
|
|
3827
|
-
|
|
4002
|
+
import { DISTANCE_CURVES, sceneMany as many2, sceneNoise as noise2, sceneRecipe as sceneRecipe2, sceneTone as tone2 } from "@adatechnology/game-audio";
|
|
4003
|
+
|
|
4004
|
+
// src/audio/blast-fire.ts
|
|
4005
|
+
import { sceneMany as many, sceneNoise as noise, sceneRecipe, sceneTone as tone } from "@adatechnology/game-audio";
|
|
4006
|
+
var BLAST_FALLOFF = { model: "inverse_square", k: 12e-4, cutoffK: 6e-3, minGain: 2e-3 };
|
|
4007
|
+
var BLAST_FAR_FALLOFF = { model: "inverse_square", k: 8e-5, scale: 0.3, minGain: 1e-3 };
|
|
4008
|
+
var FIRE_FALLOFF = { model: "inverse_square", k: 0.01, cutoffK: 0.01, minGain: 5e-3 };
|
|
4009
|
+
var BLAST_FAR_DISTANCE = 60;
|
|
4010
|
+
var BLAST_KINDS = ["grenade", "big", "fuel", "car", "pipe_bomb", "rocket", "flashbang", "molotov"];
|
|
4011
|
+
var BLAST_KIND_OF = {
|
|
4012
|
+
m67: "grenade",
|
|
4013
|
+
vog_25: "grenade",
|
|
4014
|
+
m79_40mm: "grenade",
|
|
4015
|
+
claymore: "grenade",
|
|
4016
|
+
generic: "grenade",
|
|
4017
|
+
c4: "big",
|
|
4018
|
+
dynamite: "big",
|
|
4019
|
+
fuel_station_tank: "big",
|
|
4020
|
+
propane_p13: "fuel",
|
|
4021
|
+
fuel_barrel: "fuel",
|
|
4022
|
+
fuel_pump: "fuel",
|
|
4023
|
+
car_fuel_tank: "car",
|
|
4024
|
+
pipe_bomb: "pipe_bomb",
|
|
4025
|
+
m72_66mm: "rocket",
|
|
4026
|
+
at4_84mm: "rocket",
|
|
4027
|
+
carl_gustaf_84mm: "rocket",
|
|
4028
|
+
pg7v: "rocket",
|
|
4029
|
+
flashbang: "flashbang",
|
|
4030
|
+
molotov: "molotov"
|
|
4031
|
+
};
|
|
4032
|
+
var BIG_BLAST_RADIUS = 8;
|
|
4033
|
+
var blast = (id, layers, sources, falloff = BLAST_FALLOFF) => ({
|
|
4034
|
+
...sceneRecipe(id, layers, ""),
|
|
4035
|
+
falloff,
|
|
4036
|
+
sources
|
|
4037
|
+
});
|
|
4038
|
+
var crack = (f, d, vol) => noise({ type: "highpass", f, a: 1e-3, d, vol });
|
|
4039
|
+
var debris = (count, from, to, vol) => [
|
|
4040
|
+
...many(count, () => noise({ type: "bandpass", f: [300, 1400], q: 2.5, d: [0.03, 0.09], vol, delay: [from, to] })),
|
|
4041
|
+
...many(Math.ceil(count / 3), () => noise({ type: "bandpass", f: [1800, 3600], q: 5, d: [0.02, 0.04], vol: [vol[0] * 0.6, vol[1] * 0.6], delay: [from + 0.1, to] }))
|
|
4042
|
+
];
|
|
4043
|
+
var shrapnel = (count, to) => many(count, () => noise({ type: "bandpass", f: [2500, 6e3], q: 6, d: [0.015, 0.045], vol: [0.08, 0.16], delay: [0.02, to] }));
|
|
4044
|
+
var glass = (count, from) => [
|
|
4045
|
+
noise({ type: "highpass", f: [2600, 3e3], d: 0.4, vol: 0.7, wet: 0.5, delay: from }),
|
|
4046
|
+
...many(count, () => tone({ f: [3e3, 7500], d: [0.05, 0.13], vol: 0.12, delay: [from, from + 0.7] }))
|
|
4047
|
+
];
|
|
4048
|
+
var BLAST_SOUNDS = [
|
|
4049
|
+
blast(
|
|
4050
|
+
"explosion_grenade",
|
|
4051
|
+
[
|
|
4052
|
+
crack([2300, 2700], 0.06, 2.4),
|
|
4053
|
+
noise({ f: [2500, 3e3], f2: 160, d: 0.7, vol: 2, wet: 0.35 }),
|
|
4054
|
+
tone({ f: [80, 92], f2: 32, d: 0.5, vol: 1.6 }),
|
|
4055
|
+
noise({ type: "bandpass", f: [620, 760], q: 0.8, d: 0.18, vol: 1.1 }),
|
|
4056
|
+
...shrapnel(14, 0.7),
|
|
4057
|
+
...debris(10, 0.35, 1.8, [0.1, 0.2])
|
|
4058
|
+
],
|
|
4059
|
+
["sdk: frag grenade (M67, VOG-25, 40 mm): sharp crack, short body, fragments striking, a little debris"]
|
|
4060
|
+
),
|
|
4061
|
+
blast(
|
|
4062
|
+
"explosion_big",
|
|
4063
|
+
[
|
|
4064
|
+
crack([1900, 2300], 0.08, 2.4),
|
|
4065
|
+
noise({ f: [2200, 2600], f2: 90, d: 1.6, vol: 2.2, wet: 0.45 }),
|
|
4066
|
+
tone({ f: [52, 60], f2: 22, d: 1.6, vol: 1.9 }),
|
|
4067
|
+
noise({ f: [280, 330], f2: 60, a: 0.25, d: 2.8, vol: 0.8, wet: 0.6, delay: 0.05 }),
|
|
4068
|
+
...debris(24, 0.5, 3.6, [0.12, 0.25])
|
|
4069
|
+
],
|
|
4070
|
+
["sdk: big charge (C4, dynamite, a fuel station tank): crack, long deep roar, debris falling for 2 to 4 s"]
|
|
4071
|
+
),
|
|
4072
|
+
blast(
|
|
4073
|
+
"explosion_fuel",
|
|
4074
|
+
[
|
|
4075
|
+
noise({ type: "highpass", f: [1700, 2e3], a: 1e-3, d: 0.05, vol: 1 }),
|
|
4076
|
+
tone({ f: [380, 450], f2: 300, d: 0.4, vol: 0.3 }),
|
|
4077
|
+
noise({ f: [1700, 2e3], f2: 150, a: 0.03, d: 1.4, vol: 2.1, wet: 0.4 }),
|
|
4078
|
+
tone({ f: [48, 56], f2: 25, d: 1.2, vol: 1.7 }),
|
|
4079
|
+
noise({ type: "bandpass", f: [450, 560], q: 0.6, a: 0.15, d: 2.2, vol: 0.85, wet: 0.5, delay: 0.1 }),
|
|
4080
|
+
...debris(10, 0.5, 2.6, [0.08, 0.16])
|
|
4081
|
+
],
|
|
4082
|
+
['sdk: a fuel vessel bursting (propane P13, fuel barrel, pump): the shell tearing, a fireball "whoomp" and its roar']
|
|
4083
|
+
),
|
|
4084
|
+
blast(
|
|
4085
|
+
"explosion_car",
|
|
4086
|
+
[
|
|
4087
|
+
noise({ type: "highpass", f: [1700, 2e3], a: 1e-3, d: 0.06, vol: 1.1 }),
|
|
4088
|
+
noise({ f: [1600, 1900], f2: 140, a: 0.03, d: 1.6, vol: 2.1, wet: 0.4 }),
|
|
4089
|
+
tone({ f: [44, 52], f2: 24, d: 1.4, vol: 1.8 }),
|
|
4090
|
+
noise({ type: "bandpass", f: [420, 520], q: 0.6, a: 0.15, d: 2.6, vol: 0.9, wet: 0.5, delay: 0.1 }),
|
|
4091
|
+
...glass(10, 0.04),
|
|
4092
|
+
tone({ f: [130, 150], f2: 85, d: 1.1, vol: 0.18, delay: [0.3, 0.5] }),
|
|
4093
|
+
...debris(16, 0.6, 3.2, [0.1, 0.2])
|
|
4094
|
+
],
|
|
4095
|
+
["sdk: a car going up: the fuel tank fireball, every window breaking, the body groaning, parts falling"]
|
|
4096
|
+
),
|
|
4097
|
+
blast(
|
|
4098
|
+
"explosion_pipe_bomb",
|
|
4099
|
+
[
|
|
4100
|
+
crack([2e3, 2400], 0.07, 2.2),
|
|
4101
|
+
noise({ f: [2e3, 2400], f2: 150, d: 0.8, vol: 1.9, wet: 0.35 }),
|
|
4102
|
+
tone({ f: [70, 82], f2: 30, d: 0.6, vol: 1.5 }),
|
|
4103
|
+
...many(6, () => tone({ f: [1800, 3200], f2: 1200, d: [0.06, 0.12], vol: 0.06, delay: [0.05, 0.6] })),
|
|
4104
|
+
...shrapnel(18, 0.9),
|
|
4105
|
+
...debris(8, 0.4, 1.8, [0.08, 0.16])
|
|
4106
|
+
],
|
|
4107
|
+
["sdk: pipe bomb: crack, body, the steel pipe flying apart in ringing pieces"]
|
|
4108
|
+
),
|
|
4109
|
+
blast(
|
|
4110
|
+
"explosion_rocket",
|
|
4111
|
+
[
|
|
4112
|
+
crack([2100, 2500], 0.08, 2.4),
|
|
4113
|
+
noise({ f: [2400, 2800], f2: 120, d: 1.1, vol: 2.1, wet: 0.4 }),
|
|
4114
|
+
tone({ f: [60, 70], f2: 26, d: 0.9, vol: 1.8 }),
|
|
4115
|
+
noise({ f: [350, 420], f2: 80, a: 0.08, d: 1.8, vol: 0.9, wet: 0.6, delay: 0.05 }),
|
|
4116
|
+
...shrapnel(10, 0.8),
|
|
4117
|
+
...debris(16, 0.4, 2.6, [0.1, 0.2])
|
|
4118
|
+
],
|
|
4119
|
+
["sdk: rocket or anti-tank warhead (M72, AT4, RPG): hard crack, heavy body, debris"]
|
|
4120
|
+
),
|
|
4121
|
+
blast(
|
|
4122
|
+
"explosion_flashbang",
|
|
4123
|
+
[crack([1400, 1700], 0.12, 2.8), noise({ f: [1900, 2200], f2: 300, d: 0.35, vol: 1.3, wet: 0.3 }), tone({ f: [110, 130], f2: 60, d: 0.2, vol: 0.8 })],
|
|
4124
|
+
["sdk: flashbang: a deafening crack with almost no body and no fragments"]
|
|
4125
|
+
),
|
|
4126
|
+
blast(
|
|
4127
|
+
"molotov_break_ignite",
|
|
4128
|
+
[
|
|
4129
|
+
...glass(8, 0),
|
|
4130
|
+
noise({ f: [280, 340], f2: 2200, a: 0.12, d: 0.9, vol: 1.5, wet: 0.6, delay: 0.05 }),
|
|
4131
|
+
tone({ f: [85, 95], f2: 55, d: 0.4, vol: 0.5, delay: 0.08 })
|
|
4132
|
+
],
|
|
4133
|
+
['sdk: molotov: the bottle breaking and the fuel catching with a "fuuush"'],
|
|
4134
|
+
FIRE_FALLOFF
|
|
4135
|
+
),
|
|
4136
|
+
blast(
|
|
4137
|
+
"explosion_far",
|
|
4138
|
+
[
|
|
4139
|
+
noise({ f: [550, 650], f2: 80, a: 0.03, d: 2.5, vol: 2, wet: 1.4 }),
|
|
4140
|
+
tone({ f: [42, 48], f2: 25, d: 1.8, vol: 1.2 }),
|
|
4141
|
+
noise({ f: [350, 420], f2: 70, a: 0.1, d: 1.6, vol: 0.6, wet: 1.5, delay: [0.6, 1] })
|
|
4142
|
+
],
|
|
4143
|
+
["sdk: any blast far away: no crack left, a dull thud and the echo rolling back"],
|
|
4144
|
+
BLAST_FAR_FALLOFF
|
|
4145
|
+
),
|
|
4146
|
+
blast(
|
|
4147
|
+
"explosion_indoor_tail",
|
|
4148
|
+
[
|
|
4149
|
+
...many(3, () => noise({ f: [650, 800], d: 0.12, vol: 0.5, delay: [0.04, 0.2] })),
|
|
4150
|
+
noise({ f: [850, 950], f2: 120, a: 0.05, d: 2.6, vol: 0.9, wet: 1.6, delay: 0.08 })
|
|
4151
|
+
],
|
|
4152
|
+
["sdk: the room answering a blast inside it: slaps off the walls and a boomy tail"]
|
|
4153
|
+
)
|
|
4154
|
+
];
|
|
4155
|
+
var FIRE_SOUNDS = [
|
|
4156
|
+
{
|
|
4157
|
+
...blast(
|
|
4158
|
+
"fire_ignite",
|
|
4159
|
+
[noise({ f: [230, 270], f2: 1500, a: 0.07, d: 0.5, vol: 1.6 }), tone({ f: [80, 90], f2: 55, d: 0.3, vol: 0.45 }), noise({ type: "highpass", f: 3e3, d: 0.3, vol: 0.1, delay: 0.1 })],
|
|
4160
|
+
['sdk: fire catching, a soft "fuuump"'],
|
|
4161
|
+
FIRE_FALLOFF
|
|
4162
|
+
),
|
|
4163
|
+
group: "fire_ignite"
|
|
4164
|
+
},
|
|
4165
|
+
{
|
|
4166
|
+
...blast("gas_leak", [noise({ type: "bandpass", f: [3200, 3800], q: 0.7, a: 0.08, d: { param: "seconds" }, vol: 0.3 })], ["sdk: gas jetting from a punctured vessel"], FIRE_FALLOFF),
|
|
4167
|
+
vars: { seconds: 2 }
|
|
4168
|
+
},
|
|
4169
|
+
{
|
|
4170
|
+
...blast(
|
|
4171
|
+
"fire_extinguish",
|
|
4172
|
+
[noise({ type: "highpass", f: [2300, 2700], a: 0.03, d: { param: "seconds" }, vol: 0.3 }), noise({ type: "bandpass", f: [800, 1e3], q: 0.5, d: 0.5, vol: 0.25 })],
|
|
4173
|
+
["sdk: water or foam on a fire: a steam puff and a sizzle"],
|
|
4174
|
+
FIRE_FALLOFF
|
|
4175
|
+
),
|
|
4176
|
+
vars: { seconds: 1.2 }
|
|
4177
|
+
},
|
|
4178
|
+
{ ...blast("fire_crackle", [noise({ type: "highpass", f: [1500, 3e3], d: [0.01, 0.025], vol: [1.2, 2.2] })], ["sdk: a crackle of the fire"], FIRE_FALLOFF), group: "fire_crackle" },
|
|
4179
|
+
{ ...blast("fire_pop", [noise({ type: "bandpass", f: [500, 1100], q: 1, d: [0.03, 0.06], vol: [3, 4.5] })], ["sdk: a log or a resin pocket popping"], FIRE_FALLOFF), group: "fire_crackle" }
|
|
4180
|
+
];
|
|
4181
|
+
var FIRE_LOOP = {
|
|
4182
|
+
id: "fire_loop",
|
|
4183
|
+
bus: "world",
|
|
4184
|
+
order: "chain_loop",
|
|
4185
|
+
source: { kind: "noise", loop: true },
|
|
4186
|
+
filters: { mode: "serial", bank: [{ type: "lowpass", frequency: { set: 950 }, q: 0.5 }] },
|
|
4187
|
+
volume: 0,
|
|
4188
|
+
sources: ["sdk: the roar under the crackles of a fire"]
|
|
4189
|
+
};
|
|
4190
|
+
var BLAST_SOUND_OF = {
|
|
4191
|
+
grenade: "explosion_grenade",
|
|
4192
|
+
big: "explosion_big",
|
|
4193
|
+
fuel: "explosion_fuel",
|
|
4194
|
+
car: "explosion_car",
|
|
4195
|
+
pipe_bomb: "explosion_pipe_bomb",
|
|
4196
|
+
rocket: "explosion_rocket",
|
|
4197
|
+
flashbang: "explosion_flashbang",
|
|
4198
|
+
molotov: "molotov_break_ignite"
|
|
4199
|
+
};
|
|
4200
|
+
|
|
4201
|
+
// src/audio/recipes.ts
|
|
4202
|
+
var scene = (id, layers, line, vars) => sceneRecipe2(id, layers, line, vars ? { vars } : {});
|
|
3828
4203
|
var boomLayers = (big) => [
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
...big ? [
|
|
3833
|
-
...
|
|
4204
|
+
noise2({ f: 1200, f2: 120, d: 1.8, vol: 2, wet: 1.2 }),
|
|
4205
|
+
tone2({ f: 65, f2: 24, d: 1, vol: 1.8 }),
|
|
4206
|
+
noise2({ type: "highpass", f: 1500, d: 0.2, vol: 0.8 }),
|
|
4207
|
+
...big ? [noise2({ f: 500, f2: 90, a: 0.2, d: 2.4, vol: 1.2, wet: 1, delay: 0.15 })] : [],
|
|
4208
|
+
...many2(8, () => tone2({ f: [2600, 5600], d: 0.05, vol: 0.08, delay: [0.4, 1.6] }))
|
|
3834
4209
|
];
|
|
3835
4210
|
var SCENE_SOUNDS = [
|
|
3836
4211
|
scene(
|
|
3837
4212
|
"glass_shatter",
|
|
3838
|
-
[
|
|
4213
|
+
[noise2({ type: "highpass", f: 2800, d: 0.45, vol: 0.9, wet: 0.5 }), ...many2(12, () => tone2({ f: [3e3, 7500], d: [0.06, 0.14], vol: 0.18, delay: [0, 0.55] }))],
|
|
3839
4214
|
"L1359 glass"
|
|
3840
4215
|
),
|
|
3841
4216
|
scene(
|
|
3842
4217
|
"wood_hit",
|
|
3843
|
-
[
|
|
4218
|
+
[noise2({ type: "bandpass", f: 850, q: 1, d: 0.25, vol: 0.9, wet: 0.3 }), tone2({ f: 210, f2: 90, d: 0.1, vol: 0.6 }), noise2({ type: "highpass", f: 2e3, d: 0.04, vol: 0.5 })],
|
|
3844
4219
|
"L1360 wood"
|
|
3845
4220
|
),
|
|
3846
|
-
scene("thunk", [
|
|
3847
|
-
scene("ping", [
|
|
3848
|
-
scene("clang", [
|
|
3849
|
-
scene("chip", [
|
|
4221
|
+
scene("thunk", [noise2({ type: "bandpass", f: 700, q: 2, d: 0.07, vol: 0.4 })], "L1361 thunk"),
|
|
4222
|
+
scene("ping", [tone2({ f: [2600, 3500], f2: 1400, d: 0.14, vol: 0.18, wet: 0.3 })], "L1362 ping"),
|
|
4223
|
+
scene("clang", [tone2({ f: [520, 720], f2: 480, d: 0.5, vol: 0.35, wet: 0.4 }), tone2({ f: 1370, f2: 1300, d: 0.3, vol: 0.15 }), noise2({ type: "highpass", f: 2e3, d: 0.05, vol: 0.5 })], "L1363 clang"),
|
|
4224
|
+
scene("chip", [noise2({ type: "bandpass", f: 1600, q: 1.2, d: 0.06, vol: 0.5 })], "L1364 chip"),
|
|
3850
4225
|
scene(
|
|
3851
4226
|
"crumble",
|
|
3852
|
-
[
|
|
4227
|
+
[noise2({ f: 900, f2: 200, d: 0.6, vol: 0.9, wet: 0.3 }), ...many2(5, () => noise2({ type: "bandpass", f: [500, 1400], q: 2, d: 0.05, vol: 0.4, delay: [0.1, 0.5] }))],
|
|
3853
4228
|
"L1365 crumble"
|
|
3854
4229
|
),
|
|
3855
4230
|
// `thud(x, z, v)`: `v` is the `scale` param (default 1)
|
|
3856
|
-
scene("thud", [
|
|
4231
|
+
scene("thud", [tone2({ f: 75, f2: 38, d: 0.22, vol: { param: "scale", scale: 0.6 } }), noise2({ f: 420, d: 0.16, vol: { param: "scale", scale: 0.45 } })], "L1358 thud", { scale: 1 }),
|
|
3857
4232
|
// `hiss(x, z, d)`: `seconds` is the param `d` (default 1.2)
|
|
3858
|
-
scene("hiss", [
|
|
4233
|
+
scene("hiss", [noise2({ type: "highpass", f: 3500, a: 0.05, d: { param: "seconds" }, vol: 0.35 })], "L1366 hiss", { seconds: 1.2 }),
|
|
3859
4234
|
scene("boom", boomLayers(false), "L1367 boom"),
|
|
3860
4235
|
scene("boom_big", boomLayers(true), "L1367 boom (big)"),
|
|
3861
|
-
scene("burst", [
|
|
3862
|
-
scene("crack", [
|
|
4236
|
+
scene("burst", [noise2({ f: 700, f2: 120, d: 0.7, vol: 1.4, wet: 0.5 }), noise2({ type: "highpass", f: 3e3, a: 0.05, d: 1.2, vol: 0.4, delay: 0.1 })], "L1357 burst"),
|
|
4237
|
+
scene("crack", [noise2({ type: "highpass", f: 1800, d: 0.05, vol: 0.8 }), tone2({ f: 420, f2: 180, d: 0.06, vol: 0.4 })], "L1354 crack")
|
|
3863
4238
|
];
|
|
3864
4239
|
var EXPLOSION_BOOM = {
|
|
3865
4240
|
id: "explosion_boom",
|
|
@@ -3888,29 +4263,129 @@ var BURN_LOOP = {
|
|
|
3888
4263
|
volume: 0,
|
|
3889
4264
|
sources: ["cena:L1316 fire loop (silent in the page: its gain is never raised)", "sdk: the level while burning"]
|
|
3890
4265
|
};
|
|
3891
|
-
var drag = (id, layers) => ({ ...
|
|
4266
|
+
var drag = (id, layers) => ({ ...sceneRecipe2(id, layers, ""), sources: ["sdk: a base dragged on a floor, one grain per stretch"] });
|
|
3892
4267
|
var DRAG_SOUNDS = [
|
|
3893
|
-
drag("drag_wood", [
|
|
3894
|
-
drag("drag_metal", [
|
|
3895
|
-
drag("drag_glass", [
|
|
3896
|
-
drag("drag_masonry", [
|
|
3897
|
-
drag("drag_soft", [
|
|
4268
|
+
drag("drag_wood", [noise2({ type: "bandpass", f: 380, q: 1.4, d: 0.28, vol: 0.5 }), noise2({ type: "bandpass", f: [900, 1300], q: 3, d: 0.08, vol: 0.14 })]),
|
|
4269
|
+
drag("drag_metal", [noise2({ type: "bandpass", f: 1500, q: 4, d: 0.26, vol: 0.32 }), tone2({ f: [880, 1240], f2: 760, d: 0.18, vol: 0.05 })]),
|
|
4270
|
+
drag("drag_glass", [noise2({ type: "bandpass", f: 2600, q: 5, d: 0.2, vol: 0.22 })]),
|
|
4271
|
+
drag("drag_masonry", [noise2({ type: "bandpass", f: 260, q: 0.9, d: 0.3, vol: 0.62 }), ...many2(3, () => noise2({ type: "bandpass", f: [600, 1200], q: 2, d: 0.04, vol: 0.14, delay: [0, 0.2] }))]),
|
|
4272
|
+
drag("drag_soft", [noise2({ type: "lowpass", f: 420, d: 0.28, vol: 0.3 })])
|
|
3898
4273
|
];
|
|
3899
|
-
var leaf = (id, layers) => ({ ...
|
|
4274
|
+
var leaf = (id, layers) => ({ ...sceneRecipe2(id, layers, ""), sources: ["sdk: a door, lid or drawer worked by hand"] });
|
|
3900
4275
|
var LEAF_SOUNDS = [
|
|
3901
|
-
leaf("latch", [
|
|
3902
|
-
leaf("creak_wood", [
|
|
3903
|
-
leaf("creak_metal", [
|
|
3904
|
-
leaf("shut_wood", [
|
|
3905
|
-
leaf("shut_metal", [
|
|
3906
|
-
leaf("shut_soft", [
|
|
3907
|
-
leaf("rattle",
|
|
3908
|
-
leaf("slide", [
|
|
4276
|
+
leaf("latch", [noise2({ type: "bandpass", f: 2400, q: 4, d: 0.03, vol: 0.35 }), tone2({ f: 1900, f2: 1500, d: 0.025, vol: 0.06, delay: 0.035 })]),
|
|
4277
|
+
leaf("creak_wood", [tone2({ f: [190, 240], f2: 310, d: 0.5, vol: 0.05 }), noise2({ type: "bandpass", f: 520, q: 6, a: 0.05, d: 0.45, vol: 0.12 })]),
|
|
4278
|
+
leaf("creak_metal", [tone2({ f: [620, 760], f2: 900, d: 0.35, vol: 0.035 }), noise2({ type: "bandpass", f: 1400, q: 8, a: 0.04, d: 0.3, vol: 0.08 })]),
|
|
4279
|
+
leaf("shut_wood", [tone2({ f: 120, f2: 60, d: 0.14, vol: 0.5 }), noise2({ type: "bandpass", f: 600, q: 1.2, d: 0.1, vol: 0.55, wet: 0.25 }), noise2({ type: "bandpass", f: 2400, q: 4, d: 0.03, vol: 0.25, delay: 0.02 })]),
|
|
4280
|
+
leaf("shut_metal", [tone2({ f: 160, f2: 85, d: 0.18, vol: 0.55 }), noise2({ type: "bandpass", f: 900, q: 1.6, d: 0.12, vol: 0.45, wet: 0.3 }), tone2({ f: 1250, f2: 1150, d: 0.12, vol: 0.06 })]),
|
|
4281
|
+
leaf("shut_soft", [tone2({ f: 95, f2: 55, d: 0.12, vol: 0.35 }), noise2({ type: "lowpass", f: 500, d: 0.1, vol: 0.3 })]),
|
|
4282
|
+
leaf("rattle", many2(5, () => noise2({ type: "bandpass", f: [1600, 2600], q: 3, d: 0.035, vol: 0.32, delay: [0, 0.45] }))),
|
|
4283
|
+
leaf("slide", [noise2({ type: "bandpass", f: 700, q: 2, a: 0.04, d: 0.35, vol: 0.22 }), noise2({ type: "bandpass", f: 2200, q: 5, d: 0.3, vol: 0.05 })]),
|
|
4284
|
+
// an item taken in the hand: the fingers on a hard surface and the sleeve moving, or a soft thing gathered up (the kit's recordings answer these ids)
|
|
4285
|
+
leaf("grab_hard", [noise2({ type: "bandpass", f: 1800, q: 3, d: 0.03, vol: 0.18 }), noise2({ type: "bandpass", f: 900, q: 1.2, a: 0.02, d: 0.14, vol: 0.08 })]),
|
|
4286
|
+
leaf("grab_soft", [noise2({ type: "lowpass", f: 1400, a: 0.02, d: 0.16, vol: 0.14 })])
|
|
4287
|
+
];
|
|
4288
|
+
var impact = (id, layers) => ({ ...sceneRecipe2(id, layers, ""), sources: ["sdk: a blow on a material, by material and kind of blow (synthesised until the recorded samples arrive)"] });
|
|
4289
|
+
var IMPACT_SOUNDS = [
|
|
4290
|
+
// metal
|
|
4291
|
+
impact("metal_bullet", [noise2({ type: "highpass", f: 3e3, d: 0.03, vol: 0.8 }), tone2({ f: [2600, 3400], f2: 1800, d: 0.14, vol: 0.28, wet: 0.35 }), tone2({ f: [520, 720], f2: 480, d: 0.35, vol: 0.22, wet: 0.35 })]),
|
|
4292
|
+
impact("metal_blunt", [noise2({ type: "bandpass", f: 800, q: 1, d: 0.05, vol: 0.5 }), tone2({ f: [150, 210], f2: 120, d: 0.6, vol: 0.5, wet: 0.4 }), tone2({ f: [420, 520], f2: 400, d: 0.4, vol: 0.12 })]),
|
|
4293
|
+
impact("metal_blade", [noise2({ type: "bandpass", f: [3500, 4500], q: 6, a: 0.01, d: 0.18, vol: 0.45 }), tone2({ f: [2200, 2800], f2: 3e3, d: 0.15, vol: 0.06 })]),
|
|
4294
|
+
impact("metal_thin", [noise2({ type: "highpass", f: 2500, d: 0.04, vol: 0.6 }), tone2({ f: [900, 1300], f2: 700, d: 0.25, vol: 0.22, wet: 0.3 }), noise2({ type: "bandpass", f: 1200, q: 2, d: 0.08, vol: 0.3 })]),
|
|
4295
|
+
// wood (the bullet keeps the art scene's `wood_hit`)
|
|
4296
|
+
impact("wood_blunt", [tone2({ f: [110, 150], f2: 70, d: 0.14, vol: 0.6 }), noise2({ type: "bandpass", f: 500, q: 1.2, d: 0.12, vol: 0.55, wet: 0.2 })]),
|
|
4297
|
+
impact("wood_blade", [noise2({ type: "bandpass", f: [1800, 2400], q: 2, a: 0.01, d: 0.12, vol: 0.5 }), ...many2(3, () => noise2({ type: "bandpass", f: [2500, 4e3], q: 3, d: 0.02, vol: 0.22, delay: [0.02, 0.1] }))]),
|
|
4298
|
+
impact("mdf_puff", [noise2({ type: "lowpass", f: 900, d: 0.08, vol: 1 }), tone2({ f: [180, 230], f2: 110, d: 0.06, vol: 0.3 }), noise2({ type: "highpass", f: 3e3, a: 0.01, d: 0.2, vol: 0.1 })]),
|
|
4299
|
+
// glass and ceramic
|
|
4300
|
+
impact("glass_bullet", [noise2({ type: "highpass", f: 4e3, d: 0.05, vol: 0.7 }), tone2({ f: [3200, 4200], f2: 2600, d: 0.2, vol: 0.14, wet: 0.3 }), ...many2(4, () => tone2({ f: [4e3, 7e3], d: 0.05, vol: 0.07, delay: [0.02, 0.2] }))]),
|
|
4301
|
+
impact("glass_blunt", [noise2({ type: "bandpass", f: 2200, q: 3, d: 0.05, vol: 0.8 }), tone2({ f: [1400, 1800], f2: 1300, d: 0.2, vol: 0.16, wet: 0.3 })]),
|
|
4302
|
+
impact("glass_blade", [noise2({ type: "bandpass", f: 5e3, q: 8, a: 0.02, d: 0.25, vol: 0.3 })]),
|
|
4303
|
+
impact("ceramic_hit", [noise2({ type: "bandpass", f: 3e3, q: 2, d: 0.03, vol: 0.6 }), tone2({ f: [1800, 2400], f2: 1600, d: 0.1, vol: 0.2 })]),
|
|
4304
|
+
impact("ceramic_bullet", [noise2({ type: "highpass", f: 2500, d: 0.06, vol: 0.85 }), ...many2(5, () => tone2({ f: [2500, 5e3], d: 0.04, vol: 0.09, delay: [0, 0.15] }))]),
|
|
4305
|
+
// masonry and the ground
|
|
4306
|
+
impact("masonry_bullet", [noise2({ type: "bandpass", f: [1600, 2e3], q: 1, d: 0.05, vol: 1.8 }), noise2({ type: "lowpass", f: 600, d: 0.12, vol: 0.9, wet: 0.2 }), ...many2(4, () => noise2({ type: "bandpass", f: [2e3, 4e3], q: 3, d: 0.02, vol: 0.28, delay: [0.03, 0.3] }))]),
|
|
4307
|
+
impact("masonry_blunt", [tone2({ f: [70, 95], f2: 45, d: 0.12, vol: 0.55 }), noise2({ type: "lowpass", f: 400, d: 0.1, vol: 0.5 })]),
|
|
4308
|
+
impact("masonry_blade", [noise2({ type: "bandpass", f: [2500, 3500], q: 4, a: 0.01, d: 0.15, vol: 0.4 })]),
|
|
4309
|
+
impact("plaster_puff", [noise2({ type: "lowpass", f: 900, d: 0.09, vol: 0.7 }), noise2({ type: "highpass", f: 3e3, a: 0.01, d: 0.2, vol: 0.14 })]),
|
|
4310
|
+
impact("soil_bullet", [noise2({ type: "lowpass", f: 700, d: 0.08, vol: 2.2 }), ...many2(3, () => noise2({ type: "bandpass", f: [1200, 2200], q: 2, d: 0.03, vol: 0.18, delay: [0.02, 0.2] }))]),
|
|
4311
|
+
impact("soil_blunt", [tone2({ f: [55, 70], f2: 40, d: 0.1, vol: 0.45 }), noise2({ type: "lowpass", f: 300, d: 0.08, vol: 0.4 })]),
|
|
4312
|
+
impact("sand_hit", [noise2({ type: "bandpass", f: 3500, q: 0.8, a: 5e-3, d: 0.15, vol: 0.45 }), noise2({ type: "lowpass", f: 500, d: 0.06, vol: 0.45 })]),
|
|
4313
|
+
impact("gravel_hit", [noise2({ type: "lowpass", f: 500, d: 0.06, vol: 0.45 }), ...many2(6, () => noise2({ type: "bandpass", f: [1500, 3500], q: 3, d: 0.025, vol: 0.28, delay: [0, 0.18] }))]),
|
|
4314
|
+
// plastic, rubber, wax
|
|
4315
|
+
impact("plastic_hit", [noise2({ type: "bandpass", f: 1600, q: 3, d: 0.04, vol: 0.5 }), tone2({ f: [380, 520], f2: 300, d: 0.08, vol: 0.32 })]),
|
|
4316
|
+
impact("plastic_bullet", [noise2({ type: "bandpass", f: 2500, q: 2, d: 0.03, vol: 1.3 }), tone2({ f: [600, 800], f2: 450, d: 0.06, vol: 0.4 })]),
|
|
4317
|
+
impact("rubber_hit", [tone2({ f: [90, 130], f2: 60, d: 0.1, vol: 0.45 }), noise2({ type: "lowpass", f: 350, d: 0.06, vol: 0.28 })]),
|
|
4318
|
+
impact("wax_hit", [noise2({ type: "lowpass", f: 800, d: 0.05, vol: 0.45 }), tone2({ f: [240, 280], f2: 180, d: 0.05, vol: 0.22 })]),
|
|
4319
|
+
// soft things, paper, plants
|
|
4320
|
+
impact("soft_bullet", [noise2({ type: "lowpass", f: 450, a: 3e-3, d: 0.1, vol: 1.8 }), noise2({ type: "bandpass", f: 900, q: 1, d: 0.05, vol: 0.5 })]),
|
|
4321
|
+
impact("fabric_rip", [noise2({ type: "bandpass", f: [2e3, 2800], q: 1.5, a: 0.02, d: 0.25, vol: 0.4 }), ...many2(4, () => noise2({ type: "bandpass", f: [3e3, 5e3], q: 4, d: 0.02, vol: 0.14, delay: [0, 0.22] }))]),
|
|
4322
|
+
impact("paper_hit", [noise2({ type: "bandpass", f: 2200, q: 1, d: 0.05, vol: 0.45 })]),
|
|
4323
|
+
impact("paper_tear", [noise2({ type: "highpass", f: 2500, a: 0.02, d: 0.3, vol: 0.15 })]),
|
|
4324
|
+
impact("leaf_rustle", many2(6, () => noise2({ type: "bandpass", f: [2500, 6e3], q: 2, d: 0.05, vol: 0.16, delay: [0, 0.3] }))),
|
|
4325
|
+
impact("branch_cut", [noise2({ type: "bandpass", f: 1500, q: 2, d: 0.04, vol: 0.55 }), tone2({ f: [260, 340], f2: 150, d: 0.05, vol: 0.22 })]),
|
|
4326
|
+
impact("fruit_splat", [noise2({ type: "lowpass", f: 1200, d: 0.12, vol: 0.55 }), noise2({ type: "bandpass", f: 600, q: 0.7, d: 0.2, vol: 0.32 })]),
|
|
4327
|
+
// a round glancing off: steel whines down in pitch ("piiuu"), stone and concrete give a short crack with grit
|
|
4328
|
+
impact("ricochet_metal", [noise2({ type: "highpass", f: 3e3, d: 0.02, vol: 0.6 }), tone2({ f: [2800, 3600], f2: 900, d: 0.38, vol: 0.2, wet: 0.4 }), tone2({ f: [5200, 6400], f2: 2e3, d: 0.2, vol: 0.05 })]),
|
|
4329
|
+
impact("ricochet_stone", [noise2({ type: "bandpass", f: [2200, 2800], q: 2, d: 0.04, vol: 0.8 }), tone2({ f: [1800, 2400], f2: 1200, d: 0.08, vol: 0.1 }), ...many2(3, () => noise2({ type: "bandpass", f: [2500, 4500], q: 3, d: 0.015, vol: 0.2, delay: [0.02, 0.12] }))]),
|
|
4330
|
+
// breaks a material has of its own (the rest keep the art scene's shatter, crumble and splinter)
|
|
4331
|
+
impact("ceramic_break", [noise2({ type: "highpass", f: 1800, d: 0.3, vol: 0.85, wet: 0.4 }), ...many2(8, () => tone2({ f: [1800, 4500], d: [0.04, 0.1], vol: 0.15, delay: [0, 0.4] }))]),
|
|
4332
|
+
impact("tempered_burst", [noise2({ type: "highpass", f: 3500, d: 0.6, vol: 0.9, wet: 0.4 }), ...many2(16, () => tone2({ f: [4e3, 8e3], d: 0.04, vol: 0.08, delay: [0, 0.8] }))]),
|
|
4333
|
+
impact("plastic_crack", [noise2({ type: "bandpass", f: 1800, q: 2, d: 0.06, vol: 0.75 }), tone2({ f: [450, 550], f2: 250, d: 0.05, vol: 0.22 })]),
|
|
4334
|
+
impact("mdf_crumble", [noise2({ type: "lowpass", f: 700, d: 0.25, vol: 0.75, wet: 0.2 }), ...many2(4, () => noise2({ type: "lowpass", f: 500, d: 0.05, vol: 0.32, delay: [0.05, 0.3] }))])
|
|
3909
4335
|
];
|
|
3910
|
-
var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, ...LEAF_SOUNDS, EXPLOSION_BOOM], loops: [BURN_LOOP] };
|
|
4336
|
+
var DESTRUCTION_SOUND_BANK = { id: "destruction_three", sounds: [...SCENE_SOUNDS, ...DRAG_SOUNDS, ...LEAF_SOUNDS, ...IMPACT_SOUNDS, EXPLOSION_BOOM, ...BLAST_SOUNDS, ...FIRE_SOUNDS], loops: [BURN_LOOP, FIRE_LOOP] };
|
|
3911
4337
|
|
|
3912
4338
|
// src/audio/sound.ts
|
|
3913
|
-
import { GAME_SFX_BANK } from "@adatechnology/game-audio";
|
|
4339
|
+
import { FOLEY_BANK, GAME_SFX_BANK } from "@adatechnology/game-audio";
|
|
4340
|
+
var IMPACT_SOUND_KEYS = {
|
|
4341
|
+
impact_metal_ballistic: { sound: "metal_bullet" },
|
|
4342
|
+
impact_metal_blunt: { sound: "metal_blunt" },
|
|
4343
|
+
impact_metal_slash: { sound: "metal_blade" },
|
|
4344
|
+
impact_aluminium_ballistic: { sound: "metal_thin" },
|
|
4345
|
+
impact_aluminium_blunt: { sound: "metal_thin" },
|
|
4346
|
+
impact_copper_ballistic: { sound: "metal_thin" },
|
|
4347
|
+
impact_copper_blunt: { sound: "metal_thin" },
|
|
4348
|
+
impact_wood_ballistic: { sound: "wood_hit" },
|
|
4349
|
+
impact_wood_blunt: { sound: "wood_blunt" },
|
|
4350
|
+
impact_wood_slash: { sound: "wood_blade" },
|
|
4351
|
+
impact_mdf_ballistic: { sound: "mdf_puff" },
|
|
4352
|
+
impact_mdf_blunt: { sound: "mdf_puff", volumeScale: 0.8 },
|
|
4353
|
+
impact_glass_ballistic: { sound: "glass_bullet" },
|
|
4354
|
+
impact_glass_blunt: { sound: "glass_blunt" },
|
|
4355
|
+
impact_glass_slash: { sound: "glass_blade" },
|
|
4356
|
+
impact_ceramic: { sound: "ceramic_hit" },
|
|
4357
|
+
impact_ceramic_ballistic: { sound: "ceramic_bullet" },
|
|
4358
|
+
impact_masonry_ballistic: { sound: "masonry_bullet" },
|
|
4359
|
+
impact_masonry_blunt: { sound: "masonry_blunt" },
|
|
4360
|
+
impact_masonry_slash: { sound: "masonry_blade" },
|
|
4361
|
+
impact_plaster: { sound: "plaster_puff" },
|
|
4362
|
+
impact_soil: { sound: "soil_blunt" },
|
|
4363
|
+
impact_soil_ballistic: { sound: "soil_bullet" },
|
|
4364
|
+
impact_sand: { sound: "sand_hit" },
|
|
4365
|
+
impact_gravel: { sound: "gravel_hit" },
|
|
4366
|
+
impact_plastic: { sound: "plastic_hit" },
|
|
4367
|
+
impact_plastic_ballistic: { sound: "plastic_bullet" },
|
|
4368
|
+
impact_rubber: { sound: "rubber_hit" },
|
|
4369
|
+
impact_wax: { sound: "wax_hit" },
|
|
4370
|
+
impact_soft_ballistic: { sound: "soft_bullet" },
|
|
4371
|
+
impact_soft_slash: { sound: "fabric_rip" },
|
|
4372
|
+
impact_paper: { sound: "paper_hit" },
|
|
4373
|
+
impact_paper_slash: { sound: "paper_tear" },
|
|
4374
|
+
impact_plant: { sound: "leaf_rustle" },
|
|
4375
|
+
impact_plant_slash: { sound: "branch_cut" },
|
|
4376
|
+
impact_flesh_fruit: { sound: "fruit_splat" },
|
|
4377
|
+
ricochet_metal: { sound: "ricochet_metal" },
|
|
4378
|
+
ricochet_masonry: { sound: "ricochet_stone" },
|
|
4379
|
+
ricochet_soil: { sound: "ricochet_stone" },
|
|
4380
|
+
ricochet_ceramic: { sound: "ricochet_stone" },
|
|
4381
|
+
break_ceramic: { sound: "ceramic_break" },
|
|
4382
|
+
break_tempered_glass: { sound: "tempered_burst" },
|
|
4383
|
+
break_plastic: { sound: "plastic_crack" },
|
|
4384
|
+
break_mdf: { sound: "mdf_crumble" },
|
|
4385
|
+
break_soft: { sound: "fabric_rip" },
|
|
4386
|
+
break_paper: { sound: "paper_tear" },
|
|
4387
|
+
break_plant: { sound: "branch_cut" }
|
|
4388
|
+
};
|
|
3914
4389
|
var DESTRUCTION_SOUND_KEYS = {
|
|
3915
4390
|
art_scene: {
|
|
3916
4391
|
impact_metal: { sound: "clang" },
|
|
@@ -3918,6 +4393,12 @@ var DESTRUCTION_SOUND_KEYS = {
|
|
|
3918
4393
|
impact_glass: { sound: "ping" },
|
|
3919
4394
|
impact_masonry: { sound: "chip" },
|
|
3920
4395
|
impact_soft: { sound: "thud" },
|
|
4396
|
+
// a round strikes: the steel rings high, glass cracks, wood and masonry chip, a soft part takes a dull thunk
|
|
4397
|
+
shot_metal: { sound: "ping" },
|
|
4398
|
+
shot_wood: { sound: "chip" },
|
|
4399
|
+
shot_glass: { sound: "crack" },
|
|
4400
|
+
shot_masonry: { sound: "chip" },
|
|
4401
|
+
shot_soft: { sound: "thunk" },
|
|
3921
4402
|
drag_wood: { sound: "drag_wood" },
|
|
3922
4403
|
drag_metal: { sound: "drag_metal" },
|
|
3923
4404
|
drag_glass: { sound: "drag_glass" },
|
|
@@ -3943,22 +4424,38 @@ var DESTRUCTION_SOUND_KEYS = {
|
|
|
3943
4424
|
hood_shut: { sound: "shut_metal" },
|
|
3944
4425
|
trunk_shut: { sound: "shut_metal" },
|
|
3945
4426
|
door_close: { sound: "shut_wood", volumeScale: 0.7 },
|
|
4427
|
+
// a vehicle's leaves (the vehicle bank of game-vehicles-three): a car door shuts with a padded thump and a latch, not like a fridge
|
|
4428
|
+
vehicle_door_open: { sound: "car_door_open", fallback: "latch" },
|
|
4429
|
+
vehicle_door_shut: { sound: "car_door_close", fallback: "shut_metal" },
|
|
4430
|
+
vehicle_trunk_open: { sound: "car_door_open", fallback: "latch" },
|
|
4431
|
+
vehicle_trunk_shut: { sound: "car_trunk_close", fallback: "shut_metal" },
|
|
4432
|
+
vehicle_hood_open: { sound: "car_door_open", fallback: "latch" },
|
|
4433
|
+
vehicle_hood_shut: { sound: "car_hood_close", fallback: "shut_metal" },
|
|
3946
4434
|
drawer_open: { sound: "slide" },
|
|
3947
4435
|
drawer_close: { sound: "slide" },
|
|
3948
4436
|
lid_open: { sound: "latch" },
|
|
3949
4437
|
lid_close: { sound: "shut_soft" },
|
|
3950
4438
|
flap_open: { sound: "latch" },
|
|
3951
4439
|
flap_close: { sound: "shut_soft" },
|
|
4440
|
+
// an item taken in the hand, by what it is made of (game-audio's KIT_SAMPLE_ALIASES puts recordings on these ids)
|
|
4441
|
+
grab_metal: { sound: "grab_hard" },
|
|
4442
|
+
grab_wood: { sound: "grab_hard" },
|
|
4443
|
+
grab_glass: { sound: "grab_hard", volumeScale: 0.7 },
|
|
4444
|
+
grab_masonry: { sound: "grab_hard" },
|
|
4445
|
+
grab_soft: { sound: "grab_soft" },
|
|
3952
4446
|
shatter_glass: { sound: "glass_shatter" },
|
|
3953
4447
|
crumble: { sound: "crumble" },
|
|
3954
4448
|
splinter: { sound: "crack" },
|
|
3955
4449
|
ricochet: { sound: "ping" },
|
|
3956
4450
|
unscrew: { sound: "chip" },
|
|
3957
4451
|
detach: { sound: "thunk" },
|
|
3958
|
-
ignite: { sound: "
|
|
3959
|
-
|
|
4452
|
+
ignite: { sound: "fire_ignite" },
|
|
4453
|
+
primed: { sound: "gas_leak" },
|
|
4454
|
+
extinguish: { sound: "fire_extinguish" },
|
|
3960
4455
|
melt_sizzle: { sound: "hiss", params: { seconds: 0.6 } },
|
|
3961
|
-
|
|
4456
|
+
// the blast family comes from the explosion's effect (`BLAST_KIND_OF`); this rule answers only an explosion the kit cannot place
|
|
4457
|
+
explode: { sound: "explosion_grenade", above: { volume: 0.9, sound: "explosion_big" } },
|
|
4458
|
+
...IMPACT_SOUND_KEYS
|
|
3962
4459
|
},
|
|
3963
4460
|
game: {
|
|
3964
4461
|
impact_metal: { sound: "sfx_impact" },
|
|
@@ -3972,14 +4469,26 @@ var DESTRUCTION_SOUND_KEYS = {
|
|
|
3972
4469
|
explode: { sound: "explosion_boom" }
|
|
3973
4470
|
}
|
|
3974
4471
|
};
|
|
3975
|
-
var DEFAULT_FIRE = { perFire: 0.
|
|
4472
|
+
var DEFAULT_FIRE = { perFire: 0.08, max: 0.45 };
|
|
4473
|
+
var DEFAULT_CRACKLES = { base: 2, perSqrtFire: 3, max: 14, volume: 0.5 };
|
|
4474
|
+
var DEFAULT_INDOOR_CONFINEMENT = 1.3;
|
|
3976
4475
|
function createDestructionSound(engine, options = {}) {
|
|
3977
4476
|
engine.register(DESTRUCTION_SOUND_BANK);
|
|
3978
4477
|
engine.register(GAME_SFX_BANK);
|
|
3979
|
-
|
|
4478
|
+
engine.register(FOLEY_BANK);
|
|
4479
|
+
const fire2 = { ...DEFAULT_FIRE, ...options.fire };
|
|
4480
|
+
const crackles = options.crackles === false ? null : { ...DEFAULT_CRACKLES, ...options.crackles };
|
|
4481
|
+
const fireLoop = options.fireLoop ?? "fire_loop";
|
|
4482
|
+
const kinds = { ...BLAST_KIND_OF, ...options.blastKinds };
|
|
4483
|
+
const farDistance = options.blastFarDistance ?? BLAST_FAR_DISTANCE;
|
|
4484
|
+
const indoorFrom = options.indoorConfinement ?? DEFAULT_INDOOR_CONFINEMENT;
|
|
3980
4485
|
const rules = { ...DESTRUCTION_SOUND_KEYS.art_scene, ...options.style === "game" ? DESTRUCTION_SOUND_KEYS.game : {}, ...options.keys };
|
|
4486
|
+
const blastByKind = options.style !== "game" && !(options.keys && "explode" in options.keys);
|
|
4487
|
+
const channel = engine.channel({ seed: options.seed ?? 1 });
|
|
3981
4488
|
let loop;
|
|
3982
4489
|
let burning = 0;
|
|
4490
|
+
const fires = [];
|
|
4491
|
+
let nextCrackle = 0;
|
|
3983
4492
|
const setFire = () => {
|
|
3984
4493
|
if (options.burnLoop === false) return;
|
|
3985
4494
|
if (burning <= 0) {
|
|
@@ -3987,48 +4496,157 @@ function createDestructionSound(engine, options = {}) {
|
|
|
3987
4496
|
loop = void 0;
|
|
3988
4497
|
return;
|
|
3989
4498
|
}
|
|
3990
|
-
loop ?? (loop = engine.loop(
|
|
3991
|
-
loop.set({ volume: Math.min(
|
|
4499
|
+
loop ?? (loop = engine.loop(fireLoop));
|
|
4500
|
+
loop.set({ volume: Math.min(fire2.max, fire2.perFire * burning) });
|
|
4501
|
+
};
|
|
4502
|
+
const forget = (point) => {
|
|
4503
|
+
let best = -1;
|
|
4504
|
+
let bestD = Infinity;
|
|
4505
|
+
fires.forEach((f, i) => {
|
|
4506
|
+
const d = (f[0] - point[0]) ** 2 + (f[1] - point[1]) ** 2 + (f[2] - point[2]) ** 2;
|
|
4507
|
+
if (d < bestD) {
|
|
4508
|
+
bestD = d;
|
|
4509
|
+
best = i;
|
|
4510
|
+
}
|
|
4511
|
+
});
|
|
4512
|
+
if (best >= 0) fires.splice(best, 1);
|
|
4513
|
+
};
|
|
4514
|
+
const blastSound = (point, volume, detail) => {
|
|
4515
|
+
const named = detail?.effect === void 0 ? void 0 : kinds[detail.effect];
|
|
4516
|
+
const kind = named ?? ((detail?.radius ?? 0) >= BIG_BLAST_RADIUS || volume >= 0.9 ? "big" : "grenade");
|
|
4517
|
+
const sound = BLAST_SOUND_OF[kind];
|
|
4518
|
+
const distance = engine.plan(sound, { position: point, seed: 1 })?.distance ?? 0;
|
|
4519
|
+
return kind !== "molotov" && distance > farDistance ? "explosion_far" : sound;
|
|
3992
4520
|
};
|
|
3993
4521
|
return {
|
|
3994
|
-
onSound(key, point, volume) {
|
|
4522
|
+
onSound(key, point, volume, detail) {
|
|
3995
4523
|
if (key === "burn_loop") {
|
|
3996
4524
|
burning += 1;
|
|
4525
|
+
fires.push(point);
|
|
3997
4526
|
setFire();
|
|
3998
4527
|
return;
|
|
3999
4528
|
}
|
|
4000
4529
|
if (key === "burn_end") {
|
|
4001
4530
|
if (burning > 0) burning -= 1;
|
|
4531
|
+
forget(point);
|
|
4002
4532
|
setFire();
|
|
4003
4533
|
return;
|
|
4004
4534
|
}
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4535
|
+
if (key === "explode" && blastByKind) {
|
|
4536
|
+
const sound2 = blastSound(point, volume, detail);
|
|
4537
|
+
engine.play(sound2, { position: point, volume });
|
|
4538
|
+
if (sound2 !== "explosion_far" && sound2 !== "molotov_break_ignite" && (detail?.confinement ?? 1) >= indoorFrom) engine.play("explosion_indoor_tail", { position: point, volume });
|
|
4539
|
+
return;
|
|
4540
|
+
}
|
|
4541
|
+
let rule;
|
|
4542
|
+
for (const k of detail?.keys ?? []) {
|
|
4543
|
+
if (k in rules) {
|
|
4544
|
+
rule = rules[k];
|
|
4545
|
+
break;
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
if (rule === void 0) rule = key in rules ? rules[key] : rules[key.replace(/^shot_/, "impact_")];
|
|
4549
|
+
if (rule === null) return;
|
|
4550
|
+
if (!rule) {
|
|
4551
|
+
if (engine.registry.sound(key)) engine.play(key, { position: point, volume });
|
|
4552
|
+
return;
|
|
4553
|
+
}
|
|
4554
|
+
const wanted = rule.above && volume >= rule.above.volume ? rule.above.sound : rule.sound;
|
|
4555
|
+
const sound = rule.fallback && !engine.registry.sound(wanted) ? rule.fallback : wanted;
|
|
4556
|
+
const params = detail?.seconds === void 0 ? rule.params : { ...rule.params, seconds: detail.seconds };
|
|
4557
|
+
engine.play(sound, { position: point, volume: volume * (rule.volumeScale ?? 1), ...params ? { params } : {} });
|
|
4558
|
+
},
|
|
4559
|
+
update(dt) {
|
|
4560
|
+
if (!crackles || fires.length === 0 || options.burnLoop === false) return;
|
|
4561
|
+
nextCrackle -= dt;
|
|
4562
|
+
for (let i = 0; i < 4 && nextCrackle <= 0; i++) {
|
|
4563
|
+
const rate = Math.min(crackles.max, crackles.base + crackles.perSqrtFire * Math.sqrt(fires.length));
|
|
4564
|
+
nextCrackle += (0.4 + channel.random() * 1.2) / rate;
|
|
4565
|
+
const at = fires[Math.floor(channel.random() * fires.length)];
|
|
4566
|
+
engine.play(channel.random() < 0.18 ? "fire_pop" : "fire_crackle", { position: at, volume: crackles.volume });
|
|
4567
|
+
}
|
|
4568
|
+
if (nextCrackle < 0) nextCrackle = 0;
|
|
4569
|
+
},
|
|
4570
|
+
blastSound,
|
|
4571
|
+
rule(key, detail) {
|
|
4572
|
+
for (const k of detail?.keys ?? []) if (k in rules) return rules[k] ?? void 0;
|
|
4573
|
+
return rules[key] ?? void 0;
|
|
4009
4574
|
},
|
|
4010
|
-
rule: (key) => rules[key] ?? void 0,
|
|
4011
4575
|
burning: () => burning,
|
|
4012
4576
|
dispose() {
|
|
4013
4577
|
loop?.stop();
|
|
4014
4578
|
loop = void 0;
|
|
4015
4579
|
burning = 0;
|
|
4580
|
+
fires.length = 0;
|
|
4016
4581
|
}
|
|
4017
4582
|
};
|
|
4018
4583
|
}
|
|
4584
|
+
|
|
4585
|
+
// src/audio/samples.ts
|
|
4586
|
+
import { sampleBankFromManifest } from "@adatechnology/game-audio";
|
|
4587
|
+
var blast2 = (gain) => ({ bus: "world", gain, falloff: BLAST_FALLOFF, cutoffK: 6e-3, priority: 3 });
|
|
4588
|
+
var fire = (gain, group) => ({ bus: "world", gain, falloff: FIRE_FALLOFF, cutoffK: 0.01, ...group ? { group } : {} });
|
|
4589
|
+
var BLAST_FIRE_SAMPLES = {
|
|
4590
|
+
explosion_grenade: blast2(1),
|
|
4591
|
+
explosion_big: blast2(1),
|
|
4592
|
+
explosion_car: blast2(1),
|
|
4593
|
+
explosion_flashbang: blast2(1),
|
|
4594
|
+
molotov_break_ignite: blast2(0.8),
|
|
4595
|
+
// the far takes are already muffled: no extra distance lowpass
|
|
4596
|
+
explosion_far: { bus: "world", gain: 1, falloff: BLAST_FAR_FALLOFF, cutoffK: 0, priority: 2 },
|
|
4597
|
+
fire_ignite: fire(0.8, "fire_ignite"),
|
|
4598
|
+
fire_crackle: fire(0.8, "fire_crackle"),
|
|
4599
|
+
fire_pop: fire(0.6, "fire_crackle"),
|
|
4600
|
+
fire_extinguish: fire(0.7),
|
|
4601
|
+
gas_leak: fire(0.5),
|
|
4602
|
+
// the roar: recorded fires chained with a crossfade (the level comes from `createDestructionSound`, by how much burns)
|
|
4603
|
+
fire_loop: { bus: "world", gain: 4, loop: { crossfade: 1 } }
|
|
4604
|
+
};
|
|
4605
|
+
function blastFireSampleBank(manifest, options = {}) {
|
|
4606
|
+
const sounds = options.sounds ?? BLAST_FIRE_SAMPLES;
|
|
4607
|
+
const bank = sampleBankFromManifest(manifest, { id: options.id ?? "destruction-samples", ...options.baseUrl === void 0 ? {} : { baseUrl: options.baseUrl }, sounds });
|
|
4608
|
+
const samples = (bank.samples ?? []).map((sample) => {
|
|
4609
|
+
const loop = sounds[sample.id]?.loop;
|
|
4610
|
+
return loop === void 0 ? sample : { ...sample, loop };
|
|
4611
|
+
});
|
|
4612
|
+
return { ...bank, samples };
|
|
4613
|
+
}
|
|
4019
4614
|
export {
|
|
4615
|
+
BIG_BLAST_RADIUS,
|
|
4616
|
+
BLAST_FALLOFF,
|
|
4617
|
+
BLAST_FAR_DISTANCE,
|
|
4618
|
+
BLAST_FAR_FALLOFF,
|
|
4619
|
+
BLAST_FIRE_SAMPLES,
|
|
4620
|
+
BLAST_KINDS,
|
|
4621
|
+
BLAST_KIND_OF,
|
|
4622
|
+
BLAST_SOUNDS,
|
|
4623
|
+
BLAST_SOUND_OF,
|
|
4020
4624
|
BURN_LOOP,
|
|
4625
|
+
DEFAULT_CRACKLES,
|
|
4021
4626
|
DEFAULT_FIRE,
|
|
4627
|
+
DEFAULT_INDOOR_CONFINEMENT,
|
|
4022
4628
|
DESTRUCTION_SOUND_BANK,
|
|
4023
4629
|
DESTRUCTION_SOUND_KEYS,
|
|
4024
4630
|
DRAG_SOUNDS,
|
|
4025
4631
|
EXPLOSION_BOOM,
|
|
4632
|
+
FIRE_FALLOFF,
|
|
4633
|
+
FIRE_LOOP,
|
|
4634
|
+
FIRE_SOUNDS,
|
|
4635
|
+
IMPACT_FAMILY_TAGS,
|
|
4636
|
+
IMPACT_SOUNDS,
|
|
4637
|
+
IMPACT_SOUND_KEYS,
|
|
4026
4638
|
SCENE_SOUNDS,
|
|
4639
|
+
blastFireSampleBank,
|
|
4640
|
+
breakSoundKeys,
|
|
4027
4641
|
createDestructionRenderer,
|
|
4028
4642
|
createDestructionSound,
|
|
4029
4643
|
dragSoundKey,
|
|
4030
4644
|
heatColor,
|
|
4645
|
+
impactFamily,
|
|
4031
4646
|
impactKey,
|
|
4647
|
+
impactSoundKeys,
|
|
4648
|
+
ricochetSoundKeys,
|
|
4649
|
+
shotKey,
|
|
4032
4650
|
specFromObject3D
|
|
4033
4651
|
};
|
|
4034
4652
|
//# sourceMappingURL=index.js.map
|