@adatechnology/game-destruction-three 0.14.0 → 0.15.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 +21 -1
- package/dist/index.global.js +106 -33
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +106 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -32,6 +32,10 @@ interface SoundDetail {
|
|
|
32
32
|
readonly confinement?: number;
|
|
33
33
|
/** `primed`: seconds until the vessel bursts (the gas leak lasts that long). */
|
|
34
34
|
readonly seconds?: number;
|
|
35
|
+
/** `burn_loop` / `burn_end`: the material burning (`wood_hard`, `rubber`...), which picks the fire's sound. */
|
|
36
|
+
readonly material?: string;
|
|
37
|
+
/** `burn_loop` / `burn_end`: the fuel itself, when it is not a material (a gas jet, a pool of fuel). Wins over `material`. */
|
|
38
|
+
readonly fuel?: 'wood' | 'fabric' | 'plastic' | 'gas' | 'liquid';
|
|
35
39
|
}
|
|
36
40
|
interface RendererOptions {
|
|
37
41
|
/** Scene that receives fragments, loose pieces, decals and particles. */
|
|
@@ -352,6 +356,12 @@ declare const BLAST_SOUNDS: readonly SoundRecipe[];
|
|
|
352
356
|
declare const FIRE_SOUNDS: readonly SoundRecipe[];
|
|
353
357
|
/** The roar of the fires: low, soft noise; `createDestructionSound` raises it with every part that burns and sprinkles crackles on it. */
|
|
354
358
|
declare const FIRE_LOOP: LoopRecipe;
|
|
359
|
+
/**
|
|
360
|
+
* What is burning, for the sound: each fuel has its own roar (a gas jet, a fuel pool, wood, plastic) and its own share of crackles. The rules are the
|
|
361
|
+
* same for all (the level grows with how much burns, fades with distance, never clips); only the timbre comes from the fuel.
|
|
362
|
+
*/
|
|
363
|
+
declare const FIRE_FUELS: readonly ["wood", "fabric", "plastic", "gas", "liquid"];
|
|
364
|
+
type FireFuel = (typeof FIRE_FUELS)[number];
|
|
355
365
|
/** The sound of a blast family. */
|
|
356
366
|
declare const BLAST_SOUND_OF: Readonly<Record<BlastKind, string>>;
|
|
357
367
|
|
|
@@ -394,8 +404,14 @@ type DestructionSoundOptions = {
|
|
|
394
404
|
};
|
|
395
405
|
/** Burn loop on or off (default on). */
|
|
396
406
|
readonly burnLoop?: boolean;
|
|
397
|
-
/** The
|
|
407
|
+
/** The loop of the wood fire (default `fire_loop`; `burn_loop` is the art scene's bright one). */
|
|
398
408
|
readonly fireLoop?: string;
|
|
409
|
+
/** Adds to or changes `FIRE_FUEL_OF`: which fuel a burning material sounds like. */
|
|
410
|
+
readonly fuels?: Readonly<Record<string, FireFuel>>;
|
|
411
|
+
/** Adds to or changes `FIRE_LOOP_OF`: the loop of each fuel. */
|
|
412
|
+
readonly fireLoops?: Partial<Readonly<Record<FireFuel, string>>>;
|
|
413
|
+
/** The fuel of a fire whose material is not in the table (default `wood`). */
|
|
414
|
+
readonly defaultFuel?: FireFuel;
|
|
399
415
|
/** Crackles per second over the fire loop: `base + perSqrtFire × √(parts burning)`, at most `max` (default 2, 3, 14). `false` turns them off. */
|
|
400
416
|
readonly crackles?: false | {
|
|
401
417
|
readonly base?: number;
|
|
@@ -423,6 +439,10 @@ type DestructionSound = {
|
|
|
423
439
|
rule(key: string, detail?: SoundDetail): SoundKeyRule | undefined;
|
|
424
440
|
/** Parts burning now. */
|
|
425
441
|
readonly burning: () => number;
|
|
442
|
+
/** Parts burning now, by fuel. */
|
|
443
|
+
readonly burningBy: () => Readonly<Partial<Record<FireFuel, number>>>;
|
|
444
|
+
/** Puts every fire sound out (the scene was cleared); the engine and the bank stay. */
|
|
445
|
+
reset(): void;
|
|
426
446
|
dispose(): void;
|
|
427
447
|
};
|
|
428
448
|
declare const DEFAULT_FIRE: {
|
package/dist/index.global.js
CHANGED
|
@@ -13728,7 +13728,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13728
13728
|
this.startFire(ev);
|
|
13729
13729
|
this.activate(ev.object, ev.part);
|
|
13730
13730
|
this.sound("ignite", ev.point, 0.7);
|
|
13731
|
-
this.sound("burn_loop", ev.point, 0.5);
|
|
13731
|
+
this.sound("burn_loop", ev.point, 0.5, this.burning(ev.object, ev.part));
|
|
13732
13732
|
break;
|
|
13733
13733
|
case "primed":
|
|
13734
13734
|
if (ev.trigger !== "explosion") {
|
|
@@ -13736,7 +13736,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13736
13736
|
this.primedBurning.add(`${ev.object}|${ev.part}`);
|
|
13737
13737
|
this.activate(ev.object, ev.part);
|
|
13738
13738
|
this.sound("primed", ev.point, 0.7, { seconds: ev.delay });
|
|
13739
|
-
this.sound("burn_loop", ev.point, 0.5);
|
|
13739
|
+
this.sound("burn_loop", ev.point, 0.5, { fuel: "gas" });
|
|
13740
13740
|
}
|
|
13741
13741
|
break;
|
|
13742
13742
|
case "spall":
|
|
@@ -13762,15 +13762,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13762
13762
|
this.stopFire(`${ev.object}|${ev.part}`);
|
|
13763
13763
|
this.vfx.burst("steam", f.point, [0, 1, 0], 14, 1.2);
|
|
13764
13764
|
}
|
|
13765
|
-
if (f || primed) this.sound("burn_end", f?.point ?? this.rootPoint(ev.object), 0.5);
|
|
13765
|
+
if (f || primed) this.sound("burn_end", f?.point ?? this.rootPoint(ev.object), 0.5, primed ? { fuel: "gas" } : this.burning(ev.object, ev.part));
|
|
13766
13766
|
this.activate(ev.object, ev.part);
|
|
13767
13767
|
this.sound("extinguish", f?.point ?? this.rootPoint(ev.object), 0.6);
|
|
13768
13768
|
break;
|
|
13769
13769
|
}
|
|
13770
13770
|
case "burnout": {
|
|
13771
13771
|
this.stopFire(`${ev.object}|${ev.part}`);
|
|
13772
|
-
this.primedBurning.delete(`${ev.object}|${ev.part}`);
|
|
13773
|
-
this.sound("burn_end", ev.point, 0.5);
|
|
13772
|
+
const primed = this.primedBurning.delete(`${ev.object}|${ev.part}`);
|
|
13773
|
+
this.sound("burn_end", ev.point, 0.5, primed ? { fuel: "gas" } : this.burning(ev.object, ev.part));
|
|
13774
13774
|
this.vfx.burst("ash", ev.point, [0, 1, 0], 16, 1);
|
|
13775
13775
|
this.vfx.burst("smoke", ev.point, [0, 1, 0], 6, 0.8, { color: SMOKE_TONES.grey });
|
|
13776
13776
|
const pv = this.partVis(ev.object, ev.part);
|
|
@@ -13838,7 +13838,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13838
13838
|
break;
|
|
13839
13839
|
}
|
|
13840
13840
|
case "explode":
|
|
13841
|
-
this.primedBurning.delete(`${ev.object}|${ev.part}`);
|
|
13841
|
+
if (this.primedBurning.delete(`${ev.object}|${ev.part}`)) this.sound("burn_end", ev.point, 0.5, { fuel: "gas" });
|
|
13842
13842
|
this.onExplode(ev);
|
|
13843
13843
|
break;
|
|
13844
13844
|
case "destroyed":
|
|
@@ -13853,6 +13853,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13853
13853
|
if (detail) this.o.onSound?.(key, point, volume, detail);
|
|
13854
13854
|
else this.o.onSound?.(key, point, volume);
|
|
13855
13855
|
}
|
|
13856
|
+
/** What a burning part is made of, for the fire's sound. */
|
|
13857
|
+
burning(objectId, partId) {
|
|
13858
|
+
const material = this.partVis(objectId, partId)?.material || this.world.part(objectId, partId)?.material;
|
|
13859
|
+
return material ? { material } : void 0;
|
|
13860
|
+
}
|
|
13856
13861
|
partVis(objectId, partId) {
|
|
13857
13862
|
return this.bounds.get(objectId)?.parts.get(partId) ?? null;
|
|
13858
13863
|
}
|
|
@@ -16087,6 +16092,47 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
16087
16092
|
volume: 0,
|
|
16088
16093
|
sources: ["sdk: the roar under the crackles of a fire"]
|
|
16089
16094
|
};
|
|
16095
|
+
var FIRE_FUEL_OF = {
|
|
16096
|
+
wood_soft: "wood",
|
|
16097
|
+
wood_hard: "wood",
|
|
16098
|
+
plywood: "wood",
|
|
16099
|
+
mdf: "wood",
|
|
16100
|
+
green_wood: "wood",
|
|
16101
|
+
foliage: "wood",
|
|
16102
|
+
cactus: "wood",
|
|
16103
|
+
paper: "fabric",
|
|
16104
|
+
fabric: "fabric",
|
|
16105
|
+
leather: "fabric",
|
|
16106
|
+
foam: "plastic",
|
|
16107
|
+
plastic: "plastic",
|
|
16108
|
+
rubber: "plastic",
|
|
16109
|
+
wax: "plastic"
|
|
16110
|
+
};
|
|
16111
|
+
var FIRE_LOOP_OF = {
|
|
16112
|
+
wood: "fire_loop",
|
|
16113
|
+
fabric: "fire_loop",
|
|
16114
|
+
plastic: "fire_loop_plastic",
|
|
16115
|
+
gas: "fire_loop_gas",
|
|
16116
|
+
liquid: "fire_loop_liquid"
|
|
16117
|
+
};
|
|
16118
|
+
var FIRE_CRACKLE_OF = { wood: 1, fabric: 0.5, plastic: 0.35, gas: 0, liquid: 0 };
|
|
16119
|
+
var roar = (id, bank, source) => ({
|
|
16120
|
+
id,
|
|
16121
|
+
bus: "world",
|
|
16122
|
+
order: "chain_loop",
|
|
16123
|
+
source: { kind: "noise", loop: true },
|
|
16124
|
+
filters: { mode: "serial", bank },
|
|
16125
|
+
volume: 0,
|
|
16126
|
+
sources: [source]
|
|
16127
|
+
});
|
|
16128
|
+
var FIRE_FUEL_LOOPS = [
|
|
16129
|
+
// a gas jet: a steady, broad roar, brighter than wood and without crackles
|
|
16130
|
+
roar("fire_loop_gas", [{ type: "highpass", frequency: { set: 220 }, q: 0.7 }, { type: "lowpass", frequency: { set: 2400 }, q: 0.6 }], "sdk: a gas jet burning (a vessel venting into the flame)"),
|
|
16131
|
+
// a fuel pool: a soft, low, rolling roar
|
|
16132
|
+
roar("fire_loop_liquid", [{ type: "highpass", frequency: { set: 90 }, q: 0.7 }, { type: "lowpass", frequency: { set: 650 }, q: 0.9 }], "sdk: a pool of fuel burning"),
|
|
16133
|
+
// plastic and rubber: a thin sizzle over a low burn
|
|
16134
|
+
roar("fire_loop_plastic", [{ type: "bandpass", frequency: { set: 1800 }, q: 0.6 }], "sdk: plastic and rubber melting in the fire")
|
|
16135
|
+
];
|
|
16090
16136
|
var BLAST_SOUND_OF = {
|
|
16091
16137
|
grenade: "explosion_grenade",
|
|
16092
16138
|
big: "explosion_big",
|
|
@@ -16233,7 +16279,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
16233
16279
|
impact2("plastic_crack", [sceneNoise({ type: "bandpass", f: 1800, q: 2, d: 0.06, vol: 0.75 }), sceneTone({ f: [450, 550], f2: 250, d: 0.05, vol: 0.22 })]),
|
|
16234
16280
|
impact2("mdf_crumble", [sceneNoise({ type: "lowpass", f: 700, d: 0.25, vol: 0.75, wet: 0.2 }), ...sceneMany(4, () => sceneNoise({ type: "lowpass", f: 500, d: 0.05, vol: 0.32, delay: [0.05, 0.3] }))])
|
|
16235
16281
|
];
|
|
16236
|
-
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] };
|
|
16282
|
+
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, ...FIRE_FUEL_LOOPS] };
|
|
16237
16283
|
|
|
16238
16284
|
// src/audio/sound.ts
|
|
16239
16285
|
var IMPACT_SOUND_KEYS = {
|
|
@@ -16384,31 +16430,52 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
16384
16430
|
const rules = { ...DESTRUCTION_SOUND_KEYS.art_scene, ...options.style === "game" ? DESTRUCTION_SOUND_KEYS.game : {}, ...options.keys };
|
|
16385
16431
|
const blastByKind = options.style !== "game" && !(options.keys && "explode" in options.keys);
|
|
16386
16432
|
const channel = engine.channel({ seed: options.seed ?? 1 });
|
|
16387
|
-
|
|
16388
|
-
|
|
16433
|
+
const fuelOf = { ...FIRE_FUEL_OF, ...options.fuels };
|
|
16434
|
+
const loopOf = { ...FIRE_LOOP_OF, ...options.fireLoop ? { wood: fireLoop, fabric: fireLoop } : {}, ...options.fireLoops };
|
|
16435
|
+
const defaultFuel = options.defaultFuel ?? "wood";
|
|
16436
|
+
const loops = /* @__PURE__ */ new Map();
|
|
16389
16437
|
const fires = [];
|
|
16390
16438
|
let nextCrackle = 0;
|
|
16439
|
+
const fuelFor = (detail) => detail?.fuel ?? (detail?.material !== void 0 ? fuelOf[detail.material] : void 0) ?? defaultFuel;
|
|
16391
16440
|
const setFire = () => {
|
|
16392
16441
|
if (options.burnLoop === false) return;
|
|
16393
|
-
|
|
16394
|
-
|
|
16395
|
-
|
|
16396
|
-
|
|
16442
|
+
const count = /* @__PURE__ */ new Map();
|
|
16443
|
+
for (const f of fires) {
|
|
16444
|
+
const id = loopOf[f.fuel];
|
|
16445
|
+
count.set(id, (count.get(id) ?? 0) + 1);
|
|
16446
|
+
}
|
|
16447
|
+
for (const [id, handle] of loops) {
|
|
16448
|
+
if (count.has(id)) continue;
|
|
16449
|
+
handle.stop();
|
|
16450
|
+
loops.delete(id);
|
|
16451
|
+
}
|
|
16452
|
+
for (const [id, n] of count) {
|
|
16453
|
+
let handle = loops.get(id);
|
|
16454
|
+
if (!handle) {
|
|
16455
|
+
handle = engine.loop(id);
|
|
16456
|
+
loops.set(id, handle);
|
|
16457
|
+
}
|
|
16458
|
+
handle.set({ volume: Math.min(fire2.max, fire2.perFire * n) });
|
|
16397
16459
|
}
|
|
16398
|
-
loop ?? (loop = engine.loop(fireLoop));
|
|
16399
|
-
loop.set({ volume: Math.min(fire2.max, fire2.perFire * burning) });
|
|
16400
16460
|
};
|
|
16401
|
-
const forget = (point) => {
|
|
16461
|
+
const forget = (point, fuel) => {
|
|
16402
16462
|
let best = -1;
|
|
16403
16463
|
let bestD = Infinity;
|
|
16404
16464
|
fires.forEach((f, i) => {
|
|
16405
|
-
|
|
16465
|
+
if (fuel !== void 0 && f.fuel !== fuel) return;
|
|
16466
|
+
const d = (f.point[0] - point[0]) ** 2 + (f.point[1] - point[1]) ** 2 + (f.point[2] - point[2]) ** 2;
|
|
16406
16467
|
if (d < bestD) {
|
|
16407
16468
|
bestD = d;
|
|
16408
16469
|
best = i;
|
|
16409
16470
|
}
|
|
16410
16471
|
});
|
|
16411
|
-
if (best
|
|
16472
|
+
if (best < 0 && fuel !== void 0) forget(point, void 0);
|
|
16473
|
+
else if (best >= 0) fires.splice(best, 1);
|
|
16474
|
+
};
|
|
16475
|
+
const reset = () => {
|
|
16476
|
+
for (const handle of loops.values()) handle.stop();
|
|
16477
|
+
loops.clear();
|
|
16478
|
+
fires.length = 0;
|
|
16412
16479
|
};
|
|
16413
16480
|
const blastSound = (point, volume, detail) => {
|
|
16414
16481
|
const named = detail?.effect === void 0 ? void 0 : kinds[detail.effect];
|
|
@@ -16420,14 +16487,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
16420
16487
|
return {
|
|
16421
16488
|
onSound(key, point, volume, detail) {
|
|
16422
16489
|
if (key === "burn_loop") {
|
|
16423
|
-
|
|
16424
|
-
fires.push(point);
|
|
16490
|
+
fires.push({ point, fuel: fuelFor(detail) });
|
|
16425
16491
|
setFire();
|
|
16426
16492
|
return;
|
|
16427
16493
|
}
|
|
16428
16494
|
if (key === "burn_end") {
|
|
16429
|
-
|
|
16430
|
-
forget(point);
|
|
16495
|
+
forget(point, detail?.fuel ?? (detail?.material !== void 0 ? fuelOf[detail.material] : void 0));
|
|
16431
16496
|
setFire();
|
|
16432
16497
|
return;
|
|
16433
16498
|
}
|
|
@@ -16457,12 +16522,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
16457
16522
|
},
|
|
16458
16523
|
update(dt) {
|
|
16459
16524
|
if (!crackles || fires.length === 0 || options.burnLoop === false) return;
|
|
16525
|
+
const weight = fires.reduce((sum, f) => sum + FIRE_CRACKLE_OF[f.fuel], 0);
|
|
16526
|
+
if (weight <= 0) return;
|
|
16460
16527
|
nextCrackle -= dt;
|
|
16461
16528
|
for (let i = 0; i < 4 && nextCrackle <= 0; i++) {
|
|
16462
|
-
const rate = Math.min(crackles.max, crackles.base + crackles.perSqrtFire * Math.sqrt(
|
|
16529
|
+
const rate = Math.min(crackles.max, crackles.base * Math.min(1, weight) + crackles.perSqrtFire * Math.sqrt(weight));
|
|
16463
16530
|
nextCrackle += (0.4 + channel.random() * 1.2) / rate;
|
|
16464
|
-
|
|
16465
|
-
|
|
16531
|
+
let pick3 = channel.random() * weight;
|
|
16532
|
+
const at3 = fires.find((f) => (pick3 -= FIRE_CRACKLE_OF[f.fuel]) <= 0) ?? fires[fires.length - 1];
|
|
16533
|
+
engine.play(channel.random() < 0.18 ? "fire_pop" : "fire_crackle", { position: at3.point, volume: crackles.volume });
|
|
16466
16534
|
}
|
|
16467
16535
|
if (nextCrackle < 0) nextCrackle = 0;
|
|
16468
16536
|
},
|
|
@@ -16471,13 +16539,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
16471
16539
|
for (const k of detail?.keys ?? []) if (k in rules) return rules[k] ?? void 0;
|
|
16472
16540
|
return rules[key] ?? void 0;
|
|
16473
16541
|
},
|
|
16474
|
-
burning: () =>
|
|
16475
|
-
|
|
16476
|
-
|
|
16477
|
-
|
|
16478
|
-
|
|
16479
|
-
|
|
16480
|
-
|
|
16542
|
+
burning: () => fires.length,
|
|
16543
|
+
burningBy: () => {
|
|
16544
|
+
const by = {};
|
|
16545
|
+
for (const f of fires) by[f.fuel] = (by[f.fuel] ?? 0) + 1;
|
|
16546
|
+
return by;
|
|
16547
|
+
},
|
|
16548
|
+
reset,
|
|
16549
|
+
dispose: reset
|
|
16481
16550
|
};
|
|
16482
16551
|
}
|
|
16483
16552
|
|
|
@@ -16498,7 +16567,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
16498
16567
|
fire_extinguish: fire(0.7),
|
|
16499
16568
|
gas_leak: fire(0.5),
|
|
16500
16569
|
// the roar: recorded fires chained with a crossfade (the level comes from `createDestructionSound`, by how much burns)
|
|
16501
|
-
fire_loop: { bus: "world", gain: 4, loop: { crossfade: 1 } }
|
|
16570
|
+
fire_loop: { bus: "world", gain: 4, loop: { crossfade: 1 } },
|
|
16571
|
+
// each fuel has its own roar: a gas jet (burners and torches), a pool of fuel, plastic and rubber sizzling under the flames
|
|
16572
|
+
fire_loop_gas: { bus: "world", gain: 6, loop: { crossfade: 1 } },
|
|
16573
|
+
fire_loop_liquid: { bus: "world", gain: 3.5, loop: { crossfade: 1 } },
|
|
16574
|
+
fire_loop_plastic: { bus: "world", gain: 1, loop: { crossfade: 0.6 } }
|
|
16502
16575
|
};
|
|
16503
16576
|
function blastFireSampleBank(manifest, options = {}) {
|
|
16504
16577
|
const sounds = options.sounds ?? BLAST_FIRE_SAMPLES;
|