@adatechnology/game-destruction-three 0.15.4 → 0.17.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 +2 -2
- package/dist/index.d.ts +6 -1
- package/dist/index.global.js +742 -120
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +428 -22
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.global.js
CHANGED
|
@@ -55,6 +55,7 @@ var GameDestructionThree = (() => {
|
|
|
55
55
|
heatColor: () => heatColor,
|
|
56
56
|
impactFamily: () => impactFamily,
|
|
57
57
|
impactKey: () => impactKey,
|
|
58
|
+
impactSoundIds: () => impactSoundIds,
|
|
58
59
|
impactSoundKeys: () => impactSoundKeys,
|
|
59
60
|
ricochetSoundKeys: () => ricochetSoundKeys,
|
|
60
61
|
shotKey: () => shotKey,
|
|
@@ -100,6 +101,7 @@ var GameDestructionThree = (() => {
|
|
|
100
101
|
|
|
101
102
|
// ../game-contracts/dist/index.js
|
|
102
103
|
var DEFAULT_BLAST_SHOVE = { coupling: 0.02, maxSpeed: 20, lift: 0.3, minSpeed: 0.3, falloffPower: 2 };
|
|
104
|
+
var clampShare = (share) => Math.min(1, Math.max(0, share));
|
|
103
105
|
function blastShove(blast3, target, options = {}) {
|
|
104
106
|
const o = { ...DEFAULT_BLAST_SHOVE, ...options };
|
|
105
107
|
const dx = target.position[0] - blast3.point[0];
|
|
@@ -109,7 +111,8 @@ var GameDestructionThree = (() => {
|
|
|
109
111
|
if (!(blast3.radius > 0) || distance > blast3.radius) return void 0;
|
|
110
112
|
const falloff = (1 - distance / blast3.radius) ** o.falloffPower;
|
|
111
113
|
const mass = Math.max(target.mass, 0.05);
|
|
112
|
-
const
|
|
114
|
+
const reach = clampShare(blast3.reach ?? 1);
|
|
115
|
+
const speed = Math.sqrt(reach) * Math.min(o.maxSpeed, Math.sqrt(2 * blast3.energy * o.coupling / mass)) * falloff;
|
|
113
116
|
if (!(speed >= o.minSpeed)) return void 0;
|
|
114
117
|
const direction = distance > 1e-4 ? [dx / distance, dy / distance + o.lift, dz / distance] : [0, 1, 0];
|
|
115
118
|
const length = Math.hypot(direction[0], direction[1], direction[2]);
|
|
@@ -1572,16 +1575,16 @@ var GameDestructionThree = (() => {
|
|
|
1572
1575
|
if (this.spaces.delete(id)) this.list = [...this.spaces.values()];
|
|
1573
1576
|
return;
|
|
1574
1577
|
}
|
|
1575
|
-
const [hx, hy,
|
|
1578
|
+
const [hx, hy, hz2] = enclosure.halfSize;
|
|
1576
1579
|
const space = {
|
|
1577
1580
|
cx: enclosure.center[0],
|
|
1578
1581
|
cy: enclosure.center[1],
|
|
1579
1582
|
cz: enclosure.center[2],
|
|
1580
1583
|
hx: Math.abs(hx),
|
|
1581
1584
|
hy: Math.abs(hy),
|
|
1582
|
-
hz: Math.abs(
|
|
1585
|
+
hz: Math.abs(hz2),
|
|
1583
1586
|
r: rotation(enclosure.orientation),
|
|
1584
|
-
radius: Math.hypot(hx, hy,
|
|
1587
|
+
radius: Math.hypot(hx, hy, hz2),
|
|
1585
1588
|
openings: enclosure.openings ?? [],
|
|
1586
1589
|
leak: Math.max(0, enclosure.leak ?? 0)
|
|
1587
1590
|
};
|
|
@@ -5764,13 +5767,14 @@ var GameDestructionThree = (() => {
|
|
|
5764
5767
|
function composeAfterEffects(context) {
|
|
5765
5768
|
const { core, def, random, x, y, z } = context;
|
|
5766
5769
|
const mark = def.mark;
|
|
5767
|
-
|
|
5770
|
+
const keepsMarks = !core.marklessAt?.(x, z);
|
|
5771
|
+
if (keepsMarks && mark.crater && mark.dent && y < 1.2) {
|
|
5768
5772
|
core.emit({ type: "crater", x, z, radius: mark.crater, depth: mark.dent });
|
|
5769
5773
|
core.trace?.crater?.(x, z, mark.crater, mark.dent);
|
|
5770
5774
|
addCrater(core.marks, core.ground, random, { x, z, radius: mark.crater, depth: mark.dent, marks: core.layers.mark });
|
|
5771
5775
|
if (core.layers.shrapnel) composeRimDebris(context);
|
|
5772
5776
|
}
|
|
5773
|
-
if (mark.scorch && y < 1.2 && core.layers.mark) {
|
|
5777
|
+
if (keepsMarks && mark.scorch && y < 1.2 && core.layers.mark) {
|
|
5774
5778
|
core.trace?.scorch?.(x, z, mark.scorch);
|
|
5775
5779
|
addScorch(core.marks, random, x, z, mark.scorch);
|
|
5776
5780
|
}
|
|
@@ -7479,7 +7483,7 @@ var GameDestructionThree = (() => {
|
|
|
7479
7483
|
const wind = { x: DEFAULT_WIND.x, y: DEFAULT_WIND.y, z: DEFAULT_WIND.z };
|
|
7480
7484
|
if (options.wind) [wind.x, wind.y, wind.z] = options.wind;
|
|
7481
7485
|
const camera2 = { x: 0, y: 1.6, z: 0 };
|
|
7482
|
-
const layers = Object.fromEntries(VFX_LAYERS.map((
|
|
7486
|
+
const layers = Object.fromEntries(VFX_LAYERS.map((layer3) => [layer3, true]));
|
|
7483
7487
|
const ground = options.ground;
|
|
7484
7488
|
const groundAt = options.groundAt ?? (ground ? (x, z) => ground.heightAt(x, z) : () => 0);
|
|
7485
7489
|
const handles = { next: 0, events: 0 };
|
|
@@ -7521,6 +7525,7 @@ var GameDestructionThree = (() => {
|
|
|
7521
7525
|
spawned: { puffs: 0, sparks: 0, chunks: 0, lights: 0 },
|
|
7522
7526
|
clock: { time: 0, lastBlast: -1, lod: 1, forcedLod: null },
|
|
7523
7527
|
groundAt,
|
|
7528
|
+
...options.marklessAt ? { marklessAt: options.marklessAt } : {},
|
|
7524
7529
|
emit: (request) => options.onRequest?.(request)
|
|
7525
7530
|
};
|
|
7526
7531
|
function definitionOf(id) {
|
|
@@ -7547,6 +7552,7 @@ var GameDestructionThree = (() => {
|
|
|
7547
7552
|
return player ?? (player = createEffectPlayer({ engine: workshop(), effects, view }));
|
|
7548
7553
|
}
|
|
7549
7554
|
return {
|
|
7555
|
+
hostGroundAt: options.groundAt,
|
|
7550
7556
|
play(id, point, playOptions = {}) {
|
|
7551
7557
|
if (!(id in effects)) return null;
|
|
7552
7558
|
const seed = playOptions.seed ?? (randomMode === "shared" ? void 0 : Math.imul(++handles.events, AUTO_SEED) + (options.seed ?? 0));
|
|
@@ -7609,10 +7615,10 @@ var GameDestructionThree = (() => {
|
|
|
7609
7615
|
const fires = core.fires;
|
|
7610
7616
|
for (let index = fires.items.length - 1; index >= 0; index--) if (fires.items[index].handle === handle) fires.removeAt(index);
|
|
7611
7617
|
},
|
|
7612
|
-
setLayer(
|
|
7613
|
-
layers[
|
|
7618
|
+
setLayer(layer3, on) {
|
|
7619
|
+
layers[layer3] = on;
|
|
7614
7620
|
},
|
|
7615
|
-
layer: (
|
|
7621
|
+
layer: (layer3) => layers[layer3],
|
|
7616
7622
|
setCamera(position) {
|
|
7617
7623
|
camera2.x = position[0];
|
|
7618
7624
|
camera2.y = position[1];
|
|
@@ -7730,7 +7736,7 @@ var GameDestructionThree = (() => {
|
|
|
7730
7736
|
};
|
|
7731
7737
|
var EFFECT_STEP_SECONDS = 1 / 50;
|
|
7732
7738
|
|
|
7733
|
-
// ../game-vfx-three/dist/chunk-
|
|
7739
|
+
// ../game-vfx-three/dist/chunk-CPRCWTSQ.js
|
|
7734
7740
|
var GLOW_VERTEX = `attribute vec4 aP;attribute vec4 aC;varying vec2 vU;varying vec4 vC;
|
|
7735
7741
|
void main(){vec3 R=vec3(viewMatrix[0][0],viewMatrix[1][0],viewMatrix[2][0]);vec3 Up=vec3(viewMatrix[0][1],viewMatrix[1][1],viewMatrix[2][1]);
|
|
7736
7742
|
vec3 wp=aP.xyz+(R*position.x+Up*position.y)*aP.w;vU=position.xy;vC=aC;gl_Position=projectionMatrix*viewMatrix*vec4(wp,1.);}`;
|
|
@@ -8170,7 +8176,11 @@ void main(){
|
|
|
8170
8176
|
const dirt = texture(paint(make, (context, size) => drawDirt(context, size, random)));
|
|
8171
8177
|
return { scorch, dirt };
|
|
8172
8178
|
}
|
|
8173
|
-
var
|
|
8179
|
+
var FLAT_STEPS_PER_METRE = 8;
|
|
8180
|
+
var MAX_FLAT_STEPS = 48;
|
|
8181
|
+
var DUG_STEPS_PER_METRE = 20;
|
|
8182
|
+
var MAX_DUG_STEPS = 64;
|
|
8183
|
+
var FLAT_TOLERANCE = 2e-3;
|
|
8174
8184
|
var MIN_CONFORM_STEPS = 16;
|
|
8175
8185
|
var BLOOD_GROW_RATE = 1.2;
|
|
8176
8186
|
var BLOOD_START_GROWTH = 0.05;
|
|
@@ -8253,9 +8263,21 @@ void main(){
|
|
|
8253
8263
|
spareGeometries.push(oldest.mesh.geometry);
|
|
8254
8264
|
if (spareGeometries.length > MAX_SPARE_GROUND_GEOMETRIES) spareGeometries.shift().dispose();
|
|
8255
8265
|
}
|
|
8266
|
+
const heightAt = options.heightAt ?? ((x, z) => options.ground?.heightAt(x, z) ?? 0);
|
|
8267
|
+
function groundDipsUnder(x, z, radius) {
|
|
8268
|
+
const centre = heightAt(x, z);
|
|
8269
|
+
for (const share of [0.25, 0.5, 0.75]) {
|
|
8270
|
+
for (let ray = 0; ray < 8; ray++) {
|
|
8271
|
+
const angle = ray / 8 * Math.PI * 2;
|
|
8272
|
+
if (Math.abs(heightAt(x + Math.cos(angle) * radius * share, z + Math.sin(angle) * radius * share) - centre) > FLAT_TOLERANCE) return true;
|
|
8273
|
+
}
|
|
8274
|
+
}
|
|
8275
|
+
return false;
|
|
8276
|
+
}
|
|
8256
8277
|
function conform(mark, material) {
|
|
8257
8278
|
const radius = mark.radius;
|
|
8258
|
-
const
|
|
8279
|
+
const dug = options.heightAt !== void 0 && groundDipsUnder(mark.x, mark.z, radius);
|
|
8280
|
+
const steps = Math.max(MIN_CONFORM_STEPS, Math.min(dug ? MAX_DUG_STEPS : MAX_FLAT_STEPS, Math.round(radius * (dug ? DUG_STEPS_PER_METRE : FLAT_STEPS_PER_METRE))));
|
|
8259
8281
|
if (groundMarks.length >= maxGroundMarks) evictOldestGroundMark();
|
|
8260
8282
|
const geometry = takeSpare((steps + 1) * (steps + 1)) ?? new THREE.PlaneGeometry(2, 2, steps, steps).rotateX(-Math.PI / 2);
|
|
8261
8283
|
const grid = geometry.attributes.position;
|
|
@@ -8267,7 +8289,7 @@ void main(){
|
|
|
8267
8289
|
const localZ = Math.fround(Math.floor(index / (steps + 1)) * cell - 1) * radius;
|
|
8268
8290
|
const worldX = mark.x + localX * cos2 - localZ * sin2;
|
|
8269
8291
|
const worldZ = mark.z + localX * sin2 + localZ * cos2;
|
|
8270
|
-
grid.setXYZ(index, worldX,
|
|
8292
|
+
grid.setXYZ(index, worldX, heightAt(worldX, worldZ) + mark.lift, worldZ);
|
|
8271
8293
|
}
|
|
8272
8294
|
grid.needsUpdate = true;
|
|
8273
8295
|
geometry.computeVertexNormals();
|
|
@@ -8939,7 +8961,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8939
8961
|
decalLayer(THREE, engine.decals),
|
|
8940
8962
|
ringLayer(THREE, engine.rings)
|
|
8941
8963
|
];
|
|
8942
|
-
for (const
|
|
8964
|
+
for (const layer22 of layers) group.add(layer22.object);
|
|
8943
8965
|
const meshes = createMeshLayer(THREE, engine.meshes);
|
|
8944
8966
|
group.add(meshes.group);
|
|
8945
8967
|
const lights = budget ? budgetedLights(engine, lightScale, budget) : legacyLights(THREE, engine, lightScale, group);
|
|
@@ -8947,13 +8969,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8947
8969
|
group,
|
|
8948
8970
|
sync() {
|
|
8949
8971
|
let drawCalls = 0;
|
|
8950
|
-
for (const
|
|
8972
|
+
for (const layer22 of layers) if (layer22.sync()) drawCalls++;
|
|
8951
8973
|
drawCalls += meshes.sync();
|
|
8952
8974
|
lights.sync();
|
|
8953
8975
|
return drawCalls;
|
|
8954
8976
|
},
|
|
8955
8977
|
dispose() {
|
|
8956
|
-
for (const
|
|
8978
|
+
for (const layer22 of layers) layer22.dispose();
|
|
8957
8979
|
meshes.dispose();
|
|
8958
8980
|
lights.dispose();
|
|
8959
8981
|
group.clear();
|
|
@@ -9057,7 +9079,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9057
9079
|
const shocks = createShockLayer(THREE, pools.shocks);
|
|
9058
9080
|
const sprites = createSpriteLayer(THREE, world.sprites(), options.pointScale ?? 700);
|
|
9059
9081
|
const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI, options.lights);
|
|
9060
|
-
const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
|
|
9082
|
+
const marks = createMarkLayer(THREE, { ground: pools.ground, ...world.hostGroundAt ? { heightAt: world.hostGroundAt } : {}, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
|
|
9061
9083
|
root.add(puffs.mesh, sparks.mesh, glows.mesh, chunks.rocks, chunks.plates, shocks.group, lights.group, sprites.additive, sprites.normal, marks.group);
|
|
9062
9084
|
const stopMarks = world.onMarks((event) => marks.handle(event));
|
|
9063
9085
|
const overlay = options.overlay ? createScreenOverlay(options.overlay) : void 0;
|
|
@@ -9190,7 +9212,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9190
9212
|
stopMarks();
|
|
9191
9213
|
overlay?.dispose();
|
|
9192
9214
|
workshopLayers?.dispose();
|
|
9193
|
-
for (const
|
|
9215
|
+
for (const layer22 of [puffs, sparks, glows, chunks, shocks, lights, marks, sprites]) layer22.dispose();
|
|
9194
9216
|
rockGeometry.dispose();
|
|
9195
9217
|
plateGeometry.dispose();
|
|
9196
9218
|
options.scene.remove(root);
|
|
@@ -9274,13 +9296,154 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9274
9296
|
return r << 16 | g << 8 | bl;
|
|
9275
9297
|
}
|
|
9276
9298
|
|
|
9299
|
+
// src/fragment-shapes.ts
|
|
9300
|
+
var FRAGMENT_SHAPE_VARIANTS = 6;
|
|
9301
|
+
var SLIVER_STATIONS = 6;
|
|
9302
|
+
var SLIVER_FRAY = 0.3;
|
|
9303
|
+
var SLIVER_SECTION = [[0.5, 0.5], [0, 0.5], [-0.5, 0.5], [-0.5, -0.5], [0, -0.5], [0.5, -0.5]];
|
|
9304
|
+
var sliverWidth = (t) => t < 0.25 ? 0.6 + 1.6 * t : t > 0.5 ? Math.max(0, (1 - t) / 0.5) ** 0.85 : 1;
|
|
9305
|
+
var pushTriangle = (out, a, b, c) => {
|
|
9306
|
+
out.push(a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2]);
|
|
9307
|
+
};
|
|
9308
|
+
var faceted = (THREE, positions) => {
|
|
9309
|
+
const geometry = new THREE.BufferGeometry();
|
|
9310
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
|
|
9311
|
+
geometry.computeVertexNormals();
|
|
9312
|
+
return geometry;
|
|
9313
|
+
};
|
|
9314
|
+
function buildSliverGeometry(THREE, seed) {
|
|
9315
|
+
const rnd2 = createRng(7e3 + seed);
|
|
9316
|
+
const rings = [];
|
|
9317
|
+
const bendY = (rnd2() - 0.5) * 0.3;
|
|
9318
|
+
const bendZ = (rnd2() - 0.5) * 0.3;
|
|
9319
|
+
for (let i = 0; i < SLIVER_STATIONS - 1; i++) {
|
|
9320
|
+
const t = i / (SLIVER_STATIONS - 1);
|
|
9321
|
+
const x = -0.5 + t + (i > 0 ? (rnd2() - 0.5) * 0.08 : 0);
|
|
9322
|
+
const width = sliverWidth(t);
|
|
9323
|
+
const lift = Math.sin(t * Math.PI);
|
|
9324
|
+
rings.push(SLIVER_SECTION.map(([y, z]) => {
|
|
9325
|
+
const fray = 1 - SLIVER_FRAY + SLIVER_FRAY * 1.4 * rnd2();
|
|
9326
|
+
return [x, Math.max(-0.5, Math.min(0.5, y * width * fray + bendY * lift)), Math.max(-0.5, Math.min(0.5, z * width * fray + bendZ * lift))];
|
|
9327
|
+
}));
|
|
9328
|
+
}
|
|
9329
|
+
const tip = [0.5, (rnd2() - 0.5) * 0.5, (rnd2() - 0.5) * 0.3];
|
|
9330
|
+
const torn = [-0.5 + rnd2() * 0.12, (rnd2() - 0.5) * 0.2, (rnd2() - 0.5) * 0.2];
|
|
9331
|
+
const sides = SLIVER_SECTION.length;
|
|
9332
|
+
const positions = [];
|
|
9333
|
+
for (let k = 0; k < sides; k++) {
|
|
9334
|
+
const next = (k + 1) % sides;
|
|
9335
|
+
pushTriangle(positions, torn, rings[0][next], rings[0][k]);
|
|
9336
|
+
for (let i = 0; i < rings.length - 1; i++) {
|
|
9337
|
+
const a = rings[i][k], b = rings[i][next], c = rings[i + 1][next], d = rings[i + 1][k];
|
|
9338
|
+
pushTriangle(positions, a, b, c);
|
|
9339
|
+
pushTriangle(positions, a, c, d);
|
|
9340
|
+
}
|
|
9341
|
+
const last = rings[rings.length - 1];
|
|
9342
|
+
pushTriangle(positions, last[k], last[next], tip);
|
|
9343
|
+
}
|
|
9344
|
+
return faceted(THREE, positions);
|
|
9345
|
+
}
|
|
9346
|
+
function buildLumpGeometry(THREE, seed) {
|
|
9347
|
+
const rnd2 = createRng(8e3 + seed);
|
|
9348
|
+
const base = new THREE.IcosahedronGeometry(0.5, 0);
|
|
9349
|
+
const source = base.getAttribute("position");
|
|
9350
|
+
const pushOf = /* @__PURE__ */ new Map();
|
|
9351
|
+
const positions = [];
|
|
9352
|
+
for (let i = 0; i < source.count; i++) {
|
|
9353
|
+
const x = source.getX(i), y = source.getY(i), z = source.getZ(i);
|
|
9354
|
+
const key = `${x.toFixed(3)},${y.toFixed(3)},${z.toFixed(3)}`;
|
|
9355
|
+
let push = pushOf.get(key);
|
|
9356
|
+
if (push === void 0) {
|
|
9357
|
+
push = 0.6 + 0.55 * rnd2();
|
|
9358
|
+
pushOf.set(key, push);
|
|
9359
|
+
}
|
|
9360
|
+
positions.push(x * push, y * push, z * push);
|
|
9361
|
+
}
|
|
9362
|
+
base.dispose();
|
|
9363
|
+
const extent = [0, 0, 0];
|
|
9364
|
+
for (let i = 0; i < positions.length; i++) extent[i % 3] = Math.max(extent[i % 3], Math.abs(positions[i]));
|
|
9365
|
+
for (let i = 0; i < positions.length; i++) positions[i] *= 0.5 / Math.max(extent[i % 3], 1e-3);
|
|
9366
|
+
return faceted(THREE, positions);
|
|
9367
|
+
}
|
|
9368
|
+
var VENEER_EDGE = 11;
|
|
9369
|
+
function buildVeneerGeometry(THREE, seed) {
|
|
9370
|
+
const rnd2 = createRng(9e3 + seed);
|
|
9371
|
+
const shape = new THREE.Shape();
|
|
9372
|
+
const outline = [];
|
|
9373
|
+
const edge = (y, inward) => y - inward * (rnd2() * 0.16 + (rnd2() < 0.15 ? 0.12 : 0));
|
|
9374
|
+
for (let i = 0; i <= VENEER_EDGE; i++) outline.push([-0.5 + i / VENEER_EDGE, edge(0.5, 1)]);
|
|
9375
|
+
outline.push([0.5 - rnd2() * 0.12, 0.2 * rnd2()], [0.5, -0.1 + 0.2 * rnd2()], [0.5 - rnd2() * 0.12, -0.25 * rnd2()]);
|
|
9376
|
+
for (let i = VENEER_EDGE; i >= 0; i--) outline.push([-0.5 + i / VENEER_EDGE, edge(-0.5, -1)]);
|
|
9377
|
+
outline.push([-0.5 + rnd2() * 0.12, -0.2 * rnd2()], [-0.5, 0.1 * rnd2()], [-0.5 + rnd2() * 0.12, 0.25 * rnd2()]);
|
|
9378
|
+
shape.moveTo(outline[0][0], outline[0][1]);
|
|
9379
|
+
for (let i = 1; i < outline.length; i++) shape.lineTo(outline[i][0], outline[i][1]);
|
|
9380
|
+
shape.closePath();
|
|
9381
|
+
const geometry = new THREE.ExtrudeGeometry(shape, { depth: 1, bevelEnabled: false });
|
|
9382
|
+
geometry.translate(0, 0, -0.5);
|
|
9383
|
+
return geometry;
|
|
9384
|
+
}
|
|
9385
|
+
var TORN_END = 0.24;
|
|
9386
|
+
var TORN_STRANDS = [4, 7];
|
|
9387
|
+
var pushQuad = (out, quad) => {
|
|
9388
|
+
pushTriangle(out, quad[0], quad[1], quad[2]);
|
|
9389
|
+
pushTriangle(out, quad[0], quad[2], quad[3]);
|
|
9390
|
+
};
|
|
9391
|
+
var unevenCuts = (count, rnd2) => {
|
|
9392
|
+
const shares = Array.from({ length: count }, () => 0.35 + 1.65 * rnd2());
|
|
9393
|
+
const total = shares.reduce((sum, share) => sum + share, 0);
|
|
9394
|
+
const cuts = [-0.5];
|
|
9395
|
+
for (const share of shares) cuts.push(cuts[cuts.length - 1] + share / total);
|
|
9396
|
+
cuts[cuts.length - 1] = 0.5;
|
|
9397
|
+
return cuts;
|
|
9398
|
+
};
|
|
9399
|
+
function buildBrokenBoardGeometry(THREE, seed) {
|
|
9400
|
+
const rnd2 = createRng(1e4 + seed);
|
|
9401
|
+
const end = 0.5 - TORN_END;
|
|
9402
|
+
const positions = [];
|
|
9403
|
+
const lo = -0.5, hi = 0.5;
|
|
9404
|
+
pushQuad(positions, [[lo, lo, hi], [end, lo, hi], [end, hi, hi], [lo, hi, hi]]);
|
|
9405
|
+
pushQuad(positions, [[lo, hi, lo], [end, hi, lo], [end, lo, lo], [lo, lo, lo]]);
|
|
9406
|
+
pushQuad(positions, [[lo, hi, hi], [end, hi, hi], [end, hi, lo], [lo, hi, lo]]);
|
|
9407
|
+
pushQuad(positions, [[lo, lo, lo], [end, lo, lo], [end, lo, hi], [lo, lo, hi]]);
|
|
9408
|
+
pushQuad(positions, [[lo, lo, lo], [lo, lo, hi], [lo, hi, hi], [lo, hi, lo]]);
|
|
9409
|
+
const count = TORN_STRANDS[0] + Math.floor(rnd2() * (TORN_STRANDS[1] - TORN_STRANDS[0] + 1));
|
|
9410
|
+
const cuts = unevenCuts(count, rnd2);
|
|
9411
|
+
for (let k = 0; k < count; k++) {
|
|
9412
|
+
const y0 = cuts[k], y1 = cuts[k + 1];
|
|
9413
|
+
const length = TORN_END * (0.15 + 0.85 * rnd2());
|
|
9414
|
+
const midX = end + length * (0.35 + 0.3 * rnd2());
|
|
9415
|
+
const narrow = 0.55 + 0.35 * rnd2();
|
|
9416
|
+
const shiftY = (rnd2() - 0.5) * (y1 - y0) * 0.6;
|
|
9417
|
+
const shiftZ = (rnd2() - 0.5) * 0.5;
|
|
9418
|
+
const cy = (y0 + y1) / 2 + shiftY, hy = (y1 - y0) / 2 * narrow, hz2 = 0.5 * (0.4 + 0.5 * rnd2());
|
|
9419
|
+
const base = [[end, y0, lo], [end, y1, lo], [end, y1, hi], [end, y0, hi]];
|
|
9420
|
+
const clampZ = (z) => Math.max(lo, Math.min(hi, z));
|
|
9421
|
+
const mid = [[midX, cy - hy, clampZ(shiftZ - hz2)], [midX, cy + hy, clampZ(shiftZ - hz2)], [midX, cy + hy, clampZ(shiftZ + hz2)], [midX, cy - hy, clampZ(shiftZ + hz2)]];
|
|
9422
|
+
const tip = [end + length, y0 + (y1 - y0) * rnd2(), clampZ(shiftZ + (rnd2() - 0.5) * 0.4)];
|
|
9423
|
+
for (let i = 0; i < 4; i++) {
|
|
9424
|
+
const next = (i + 1) % 4;
|
|
9425
|
+
pushQuad(positions, [base[i], base[next], mid[next], mid[i]]);
|
|
9426
|
+
pushTriangle(positions, mid[i], mid[next], tip);
|
|
9427
|
+
}
|
|
9428
|
+
}
|
|
9429
|
+
return faceted(THREE, positions);
|
|
9430
|
+
}
|
|
9431
|
+
|
|
9277
9432
|
// src/fragments.ts
|
|
9433
|
+
var BUILDERS = {
|
|
9434
|
+
sliver: buildSliverGeometry,
|
|
9435
|
+
lump: buildLumpGeometry,
|
|
9436
|
+
veneer: buildVeneerGeometry,
|
|
9437
|
+
board: buildBrokenBoardGeometry
|
|
9438
|
+
};
|
|
9278
9439
|
var SHEET_MAX = 4e-3;
|
|
9279
9440
|
var SHEET_AIR = 2.5;
|
|
9280
9441
|
var SHEET_SWAY = 3;
|
|
9281
9442
|
var BIG_LENGTH = 0.2;
|
|
9282
9443
|
var BIG_WIDTH = 0.03;
|
|
9283
9444
|
var FRAGMENT_SEED = 1374496523;
|
|
9445
|
+
var SUPPORT_CHECKS = 32;
|
|
9446
|
+
var SUPPORT_GAP = 0.02;
|
|
9284
9447
|
var SHARD_SHAPES = 6;
|
|
9285
9448
|
var isBig = (size) => {
|
|
9286
9449
|
const [, middle, longest] = [...size].sort((a, b) => a - b);
|
|
@@ -9295,6 +9458,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9295
9458
|
/** The mesh box of a fragment before its scale. */
|
|
9296
9459
|
__publicField(this, "unitBox", { min: { x: -0.5, y: -0.5, z: -0.5 }, max: { x: 0.5, y: 0.5, z: 0.5 } });
|
|
9297
9460
|
__publicField(this, "shardGeos", []);
|
|
9461
|
+
__publicField(this, "shapeGeos", /* @__PURE__ */ new Map());
|
|
9298
9462
|
__publicField(this, "materials", /* @__PURE__ */ new Map());
|
|
9299
9463
|
__publicField(this, "q");
|
|
9300
9464
|
__publicField(this, "dq");
|
|
@@ -9304,6 +9468,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9304
9468
|
__publicField(this, "rng", createRng(FRAGMENT_SEED));
|
|
9305
9469
|
/** Count of `update` calls: what makes the fragments of one burst tell apart from older ones. */
|
|
9306
9470
|
__publicField(this, "tick", 0);
|
|
9471
|
+
__publicField(this, "supportTurn", 0);
|
|
9307
9472
|
__publicField(this, "markGeometry", null);
|
|
9308
9473
|
__publicField(this, "markMaterial", null);
|
|
9309
9474
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
@@ -9352,21 +9517,54 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9352
9517
|
}
|
|
9353
9518
|
return g;
|
|
9354
9519
|
}
|
|
9520
|
+
sharedGeometry(shape, seed) {
|
|
9521
|
+
const idx = seed % FRAGMENT_SHAPE_VARIANTS;
|
|
9522
|
+
let variants = this.shapeGeos.get(shape);
|
|
9523
|
+
if (!variants) {
|
|
9524
|
+
variants = [];
|
|
9525
|
+
this.shapeGeos.set(shape, variants);
|
|
9526
|
+
}
|
|
9527
|
+
let g = variants[idx];
|
|
9528
|
+
if (!g) {
|
|
9529
|
+
g = BUILDERS[shape](this.THREE, idx);
|
|
9530
|
+
variants[idx] = g;
|
|
9531
|
+
}
|
|
9532
|
+
return g;
|
|
9533
|
+
}
|
|
9534
|
+
/**
|
|
9535
|
+
* The geometry a fragment is drawn with: a splinter is a pointed, frayed sliver; MDF breaks in lumps, plywood peels in ragged veneer, the stub of a broken
|
|
9536
|
+
* board ends in torn strands at +x; the rest are boxes.
|
|
9537
|
+
*/
|
|
9538
|
+
geometryOf(f) {
|
|
9539
|
+
const seed = hashString(f.id);
|
|
9540
|
+
if (f.shape === "shard") return this.shardGeometry(seed);
|
|
9541
|
+
if (f.shape === "splinter") return this.sharedGeometry("sliver", seed);
|
|
9542
|
+
const form = this.o.formOf?.(f.material);
|
|
9543
|
+
if (f.shape === "chunk" && form === "granules") return this.sharedGeometry("lump", seed);
|
|
9544
|
+
if (f.shape === "scrap" && form === "plies") return this.sharedGeometry("veneer", seed);
|
|
9545
|
+
if (f.shape === "slab" && (form === "slivers" || form === "plies")) return this.sharedGeometry("board", seed);
|
|
9546
|
+
return this.box;
|
|
9547
|
+
}
|
|
9355
9548
|
markParts() {
|
|
9356
9549
|
this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
|
|
9357
9550
|
this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
|
|
9358
9551
|
return { geometry: this.markGeometry, material: this.markMaterial };
|
|
9359
9552
|
}
|
|
9360
9553
|
/**
|
|
9361
|
-
* 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
|
|
9554
|
+
* Meshes, never put in a scene by this class, that draw every shared piece of a fragment once: the box, the six shards, the wood splinters, MDF lumps and plywood veneer, the bullet-hole disc, and the opaque and
|
|
9362
9555
|
* glass materials. A host draws them at load so the first blast does not upload these buffers in the middle of a frame.
|
|
9363
9556
|
*/
|
|
9364
9557
|
warmMeshes() {
|
|
9365
9558
|
const opaque = this.materialFor("warm", 8421504, false);
|
|
9366
9559
|
const glass2 = this.materialFor("warm", 8421504, true);
|
|
9367
9560
|
const geometries = [this.box, ...Array.from({ length: SHARD_SHAPES }, (_, index) => this.shardGeometry(index))];
|
|
9561
|
+
const wood = ["sliver", "lump", "veneer", "board"].flatMap((shape) => Array.from({ length: FRAGMENT_SHAPE_VARIANTS }, (_, index) => this.sharedGeometry(shape, index)));
|
|
9368
9562
|
const { geometry, material } = this.markParts();
|
|
9369
|
-
return [
|
|
9563
|
+
return [
|
|
9564
|
+
...geometries.flatMap((shared) => [new this.THREE.Mesh(shared, opaque), new this.THREE.Mesh(shared, glass2)]),
|
|
9565
|
+
...wood.map((shared) => new this.THREE.Mesh(shared, opaque)),
|
|
9566
|
+
new this.THREE.Mesh(geometry, material)
|
|
9567
|
+
];
|
|
9370
9568
|
}
|
|
9371
9569
|
/**
|
|
9372
9570
|
* 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
|
|
@@ -9391,7 +9589,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9391
9589
|
* bigger than the budget (every live fragment was made in this same frame) does not make the rest at all, so a blast never builds a thousand meshes only to fade them
|
|
9392
9590
|
* out. Returns null when the fragment was not made.
|
|
9393
9591
|
*/
|
|
9394
|
-
|
|
9592
|
+
/**
|
|
9593
|
+
* Throws `f` into the scene, within `maxAllowed` live fragments (the oldest small ones make room first). A `lasting` fragment (a steel bar) is debris that
|
|
9594
|
+
* stays: it has a body the player and the pieces run into, however thin it is, and it never fades.
|
|
9595
|
+
*/
|
|
9596
|
+
spawn(f, material, home, maxAllowed, lasting = false) {
|
|
9395
9597
|
if (this.list.length >= maxAllowed) {
|
|
9396
9598
|
let alive = 0;
|
|
9397
9599
|
let oldest;
|
|
@@ -9408,7 +9610,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9408
9610
|
oldest.dying = true;
|
|
9409
9611
|
}
|
|
9410
9612
|
}
|
|
9411
|
-
const geo =
|
|
9613
|
+
const geo = this.geometryOf(f);
|
|
9412
9614
|
const mesh2 = new this.THREE.Mesh(geo, material);
|
|
9413
9615
|
mesh2.position.set(f.center[0], f.center[1], f.center[2]);
|
|
9414
9616
|
mesh2.quaternion.set(f.rotation[0], f.rotation[1], f.rotation[2], f.rotation[3]).normalize();
|
|
@@ -9436,9 +9638,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9436
9638
|
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0,
|
|
9437
9639
|
body: null,
|
|
9438
9640
|
born: this.tick,
|
|
9439
|
-
life: Math.max(0, typeof this.o.life === "function" ? this.o.life(f, home) : this.o.life)
|
|
9641
|
+
life: lasting ? 0 : Math.max(0, typeof this.o.life === "function" ? this.o.life(f, home) : this.o.life)
|
|
9440
9642
|
};
|
|
9441
|
-
if (f.shape !== "shard" && isBig(f.size)) {
|
|
9643
|
+
if (lasting || f.shape !== "shard" && isBig(f.size)) {
|
|
9442
9644
|
live.body = {
|
|
9443
9645
|
mass: f.mass,
|
|
9444
9646
|
push: (x, z) => {
|
|
@@ -9498,6 +9700,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9498
9700
|
}
|
|
9499
9701
|
update(dt) {
|
|
9500
9702
|
this.tick++;
|
|
9703
|
+
this.wakeUnsupported();
|
|
9501
9704
|
const g = this.o.gravity;
|
|
9502
9705
|
for (let i = this.list.length - 1; i >= 0; i--) {
|
|
9503
9706
|
const f = this.list[i];
|
|
@@ -9522,6 +9725,18 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9522
9725
|
this.step(f, dt, g);
|
|
9523
9726
|
}
|
|
9524
9727
|
}
|
|
9728
|
+
/** A few fragments asleep each update (`SUPPORT_CHECKS`, in turn) look at the ground under them again: one left over air falls. */
|
|
9729
|
+
wakeUnsupported() {
|
|
9730
|
+
const n = this.list.length;
|
|
9731
|
+
for (let k = 0; k < Math.min(SUPPORT_CHECKS, n); k++) {
|
|
9732
|
+
this.supportTurn = (this.supportTurn + 1) % n;
|
|
9733
|
+
const f = this.list[this.supportTurn];
|
|
9734
|
+
if (!f.asleep || f.dying) continue;
|
|
9735
|
+
const e = this.m.makeRotationFromQuaternion(f.mesh.quaternion).elements;
|
|
9736
|
+
const ext = Math.abs(e[1]) * f.half.x + Math.abs(e[5]) * f.half.y + Math.abs(e[9]) * f.half.z;
|
|
9737
|
+
if (f.mesh.position.y - ext > this.o.groundAt(f.mesh.position.x, f.mesh.position.z) + SUPPORT_GAP) this.rouse(f);
|
|
9738
|
+
}
|
|
9739
|
+
}
|
|
9525
9740
|
step(f, dt, g) {
|
|
9526
9741
|
const mesh2 = f.mesh;
|
|
9527
9742
|
f.vel.y -= g * dt;
|
|
@@ -9629,6 +9844,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9629
9844
|
this.markMaterial?.dispose();
|
|
9630
9845
|
for (const g of this.shardGeos) g?.dispose();
|
|
9631
9846
|
this.shardGeos = [];
|
|
9847
|
+
for (const variants of this.shapeGeos.values()) for (const g of variants) g?.dispose();
|
|
9848
|
+
this.shapeGeos.clear();
|
|
9632
9849
|
for (const m of this.materials.values()) m.dispose();
|
|
9633
9850
|
this.materials.clear();
|
|
9634
9851
|
}
|
|
@@ -9738,6 +9955,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9738
9955
|
var SOLVER_PASSES = 10;
|
|
9739
9956
|
var SIDE_BOUNCE = 0.1;
|
|
9740
9957
|
var EDGE_POINTS = [-0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75];
|
|
9958
|
+
var SUPPORT_CHECKS2 = 16;
|
|
9959
|
+
var SUPPORT_GAP2 = 0.02;
|
|
9741
9960
|
var boxTopAt = (s, x, z) => {
|
|
9742
9961
|
const e = s.e;
|
|
9743
9962
|
const dx = x - s.c[0], dz = z - s.c[2];
|
|
@@ -9770,6 +9989,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9770
9989
|
__publicField(this, "m");
|
|
9771
9990
|
__publicField(this, "ax");
|
|
9772
9991
|
__publicField(this, "solids", []);
|
|
9992
|
+
__publicField(this, "supportTurn", 0);
|
|
9773
9993
|
this.p = new THREE.Vector3();
|
|
9774
9994
|
this.q = new THREE.Quaternion();
|
|
9775
9995
|
this.dq = new THREE.Quaternion();
|
|
@@ -9865,10 +10085,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9865
10085
|
return this.mulInvInertiaWith(b, this.m.elements, x, y, z);
|
|
9866
10086
|
}
|
|
9867
10087
|
mulInvInertiaWith(b, e, x, y, z) {
|
|
9868
|
-
const [hx, hy,
|
|
10088
|
+
const [hx, hy, hz2] = b.halfExtents;
|
|
9869
10089
|
const k = 3 / b.mass;
|
|
9870
|
-
const ix = k / (hy * hy +
|
|
9871
|
-
const iy = k / (hx * hx +
|
|
10090
|
+
const ix = k / (hy * hy + hz2 * hz2);
|
|
10091
|
+
const iy = k / (hx * hx + hz2 * hz2);
|
|
9872
10092
|
const iz = k / (hx * hx + hy * hy);
|
|
9873
10093
|
const lx = (e[0] * x + e[1] * y + e[2] * z) * ix;
|
|
9874
10094
|
const ly = (e[4] * x + e[5] * y + e[6] * z) * iy;
|
|
@@ -9877,11 +10097,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9877
10097
|
}
|
|
9878
10098
|
/** The points of the box that touch the ground, in its own frame: the 8 corners, then (with `edges`) `EDGE_POINTS` along each of its 12 edges. */
|
|
9879
10099
|
samples(b, edges) {
|
|
9880
|
-
const [hx, hy,
|
|
10100
|
+
const [hx, hy, hz2] = b.halfExtents;
|
|
9881
10101
|
const out = [];
|
|
9882
|
-
for (let i = 0; i < 8; i++) out.push([i & 1 ? hx : -hx, i & 2 ? hy : -hy, i & 4 ?
|
|
10102
|
+
for (let i = 0; i < 8; i++) out.push([i & 1 ? hx : -hx, i & 2 ? hy : -hy, i & 4 ? hz2 : -hz2]);
|
|
9883
10103
|
if (!edges) return out;
|
|
9884
|
-
const h = [hx, hy,
|
|
10104
|
+
const h = [hx, hy, hz2];
|
|
9885
10105
|
for (let axis2 = 0; axis2 < 3; axis2++) {
|
|
9886
10106
|
const a1 = (axis2 + 1) % 3;
|
|
9887
10107
|
const a2 = (axis2 + 2) % 3;
|
|
@@ -9929,6 +10149,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9929
10149
|
const h = body.halfExtents;
|
|
9930
10150
|
return { body, c: [c.x, c.y, c.z], e: [...new this.THREE.Matrix4().makeRotationFromQuaternion(body.root.quaternion).elements], half: h, r: Math.hypot(h[0], h[1], h[2]) };
|
|
9931
10151
|
});
|
|
10152
|
+
this.wakeUnsupported();
|
|
9932
10153
|
for (const b of this.list) {
|
|
9933
10154
|
if (b.held) {
|
|
9934
10155
|
b.velocity = [0, 0, 0];
|
|
@@ -9943,6 +10164,33 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9943
10164
|
}
|
|
9944
10165
|
this.separate(dt);
|
|
9945
10166
|
}
|
|
10167
|
+
/** A few sleeping pieces each update (`SUPPORT_CHECKS`, in turn) look under them again: one whose table broke, or whose shelf fell over, falls. */
|
|
10168
|
+
wakeUnsupported() {
|
|
10169
|
+
const n = this.list.length;
|
|
10170
|
+
for (let k = 0; k < Math.min(SUPPORT_CHECKS2, n); k++) {
|
|
10171
|
+
this.supportTurn = (this.supportTurn + 1) % n;
|
|
10172
|
+
const b = this.list[this.supportTurn];
|
|
10173
|
+
if (!b.sleeping || b.held || this.supported(b, false) || this.supported(b, true)) continue;
|
|
10174
|
+
b.sleeping = false;
|
|
10175
|
+
b.still = 0;
|
|
10176
|
+
b.calmAt = null;
|
|
10177
|
+
}
|
|
10178
|
+
}
|
|
10179
|
+
/**
|
|
10180
|
+
* Whether a point of `b` (its corners, or with `edges` the points along its edges too) has the ground, a piece or a fixed top under it. Any point, not only the lowest:
|
|
10181
|
+
* a book propped on a fallen case rests by a corner high on it while its lowest one hangs beside it.
|
|
10182
|
+
*/
|
|
10183
|
+
supported(b, edges) {
|
|
10184
|
+
const c = this.centerOf(b, this.p);
|
|
10185
|
+
const e = this.m.makeRotationFromQuaternion(b.root.quaternion).elements;
|
|
10186
|
+
for (const [sx, sy, sz] of this.samples(b, edges)) {
|
|
10187
|
+
const y = c.y + e[1] * sx + e[5] * sy + e[9] * sz;
|
|
10188
|
+
const x = c.x + e[0] * sx + e[4] * sy + e[8] * sz;
|
|
10189
|
+
const z = c.z + e[2] * sx + e[6] * sy + e[10] * sz;
|
|
10190
|
+
if (this.supportAt(b, x, y, z, STEP) >= y - SUPPORT_GAP2) return true;
|
|
10191
|
+
}
|
|
10192
|
+
return false;
|
|
10193
|
+
}
|
|
9946
10194
|
/** Where a corner of `self` at `(x, y, z)` lands: the ground, the top of another piece or of something fixed under it. */
|
|
9947
10195
|
supportAt(self, x, y, z, step) {
|
|
9948
10196
|
let top = this.o.groundAt(x, z);
|
|
@@ -10202,8 +10450,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10202
10450
|
unstable = Math.min(wx, wz) < NARROW * height || x0 > 0 || x1 < 0 || z0 > 0 || z1 < 0;
|
|
10203
10451
|
if (b.tipping) {
|
|
10204
10452
|
b.tipping.seconds -= h;
|
|
10205
|
-
const [hx, hy,
|
|
10206
|
-
const upHalf = Math.abs(e[1]) * hx + Math.abs(e[5]) * hy + Math.abs(e[9]) *
|
|
10453
|
+
const [hx, hy, hz2] = b.halfExtents;
|
|
10454
|
+
const upHalf = Math.abs(e[1]) * hx + Math.abs(e[5]) * hy + Math.abs(e[9]) * hz2;
|
|
10207
10455
|
if (upHalf < TIP_LYING || b.tipping.seconds <= 0) b.tipping = null;
|
|
10208
10456
|
else {
|
|
10209
10457
|
w[0] += b.tipping.x * TIP_ACCELERATION * h;
|
|
@@ -10316,6 +10564,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10316
10564
|
constructor(THREE, scene2, radius = 4e-3) {
|
|
10317
10565
|
__publicField(this, "THREE", THREE);
|
|
10318
10566
|
__publicField(this, "scene", scene2);
|
|
10567
|
+
__publicField(this, "radius", radius);
|
|
10319
10568
|
__publicField(this, "cages", /* @__PURE__ */ new Map());
|
|
10320
10569
|
__publicField(this, "geometry");
|
|
10321
10570
|
__publicField(this, "material");
|
|
@@ -10344,28 +10593,62 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10344
10593
|
meshes() {
|
|
10345
10594
|
return [...this.cages.values()];
|
|
10346
10595
|
}
|
|
10347
|
-
/**
|
|
10348
|
-
|
|
10596
|
+
/**
|
|
10597
|
+
* Draw `bars` (world space) as the cage of `key`; an empty list removes it. The cage is a child of `holder` (the drawing of the part's object, the scene
|
|
10598
|
+
* by default), so it goes where the part goes: a beam that comes down takes its bare bars with it.
|
|
10599
|
+
*/
|
|
10600
|
+
set(key, bars, holder) {
|
|
10349
10601
|
this.remove(key);
|
|
10350
10602
|
if (bars.length === 0) return;
|
|
10603
|
+
const parent = holder ?? this.scene;
|
|
10604
|
+
parent.updateWorldMatrix(true, false);
|
|
10605
|
+
const toParent = new this.THREE.Matrix4().copy(parent.matrixWorld).invert();
|
|
10351
10606
|
const mesh2 = new this.THREE.InstancedMesh(this.geometry, this.material, bars.length);
|
|
10352
10607
|
bars.forEach(([a, b], i) => {
|
|
10353
10608
|
this.dir.set(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
10354
10609
|
const length = this.dir.length();
|
|
10355
10610
|
this.mid.set((a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2);
|
|
10356
10611
|
this.q.setFromUnitVectors(this.up, length > 1e-9 ? this.dir.divideScalar(length) : this.up);
|
|
10357
|
-
this.m.compose(this.mid, this.q, this.scale.set(1, Math.max(length, 1e-4), 1));
|
|
10612
|
+
this.m.compose(this.mid, this.q, this.scale.set(1, Math.max(length, 1e-4), 1)).premultiply(toParent);
|
|
10358
10613
|
mesh2.setMatrixAt(i, this.m);
|
|
10359
10614
|
});
|
|
10360
10615
|
mesh2.instanceMatrix.needsUpdate = true;
|
|
10361
10616
|
mesh2.castShadow = true;
|
|
10362
|
-
|
|
10617
|
+
mesh2.name = "rebar-cage";
|
|
10618
|
+
parent.add(mesh2);
|
|
10363
10619
|
this.cages.set(key, mesh2);
|
|
10364
10620
|
}
|
|
10621
|
+
/** The cage of `from` now belongs to `to` (a part that left its object for one of its own) and hangs from `holder`, where it is in the world now. */
|
|
10622
|
+
move(from, to, holder) {
|
|
10623
|
+
const mesh2 = this.cages.get(from);
|
|
10624
|
+
if (!mesh2) return;
|
|
10625
|
+
this.cages.delete(from);
|
|
10626
|
+
this.remove(to);
|
|
10627
|
+
holder.attach(mesh2);
|
|
10628
|
+
this.cages.set(to, mesh2);
|
|
10629
|
+
}
|
|
10630
|
+
/** Takes the cage of `key` away and gives its bars, where they are in the world now: centre, turn (the bar along its y) and length. */
|
|
10631
|
+
take(key) {
|
|
10632
|
+
const mesh2 = this.cages.get(key);
|
|
10633
|
+
if (!mesh2) return [];
|
|
10634
|
+
mesh2.updateWorldMatrix(true, false);
|
|
10635
|
+
const bars = [];
|
|
10636
|
+
for (let i = 0; i < mesh2.count; i++) {
|
|
10637
|
+
mesh2.getMatrixAt(i, this.m);
|
|
10638
|
+
this.m.premultiply(mesh2.matrixWorld).decompose(this.mid, this.q, this.scale);
|
|
10639
|
+
bars.push({ center: [this.mid.x, this.mid.y, this.mid.z], rotation: [this.q.x, this.q.y, this.q.z, this.q.w], length: this.scale.y });
|
|
10640
|
+
}
|
|
10641
|
+
this.remove(key);
|
|
10642
|
+
return bars;
|
|
10643
|
+
}
|
|
10644
|
+
/** Radius of the bars drawn, metres. */
|
|
10645
|
+
get barRadius() {
|
|
10646
|
+
return this.radius;
|
|
10647
|
+
}
|
|
10365
10648
|
remove(key) {
|
|
10366
10649
|
const mesh2 = this.cages.get(key);
|
|
10367
10650
|
if (!mesh2) return;
|
|
10368
|
-
|
|
10651
|
+
mesh2.removeFromParent();
|
|
10369
10652
|
mesh2.dispose();
|
|
10370
10653
|
this.cages.delete(key);
|
|
10371
10654
|
}
|
|
@@ -10445,26 +10728,26 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10445
10728
|
return [s.radius * 2, s.radius * 2, s.radius * 2];
|
|
10446
10729
|
};
|
|
10447
10730
|
var aabbOf = (s, center, q) => {
|
|
10448
|
-
let hx, hy,
|
|
10731
|
+
let hx, hy, hz2;
|
|
10449
10732
|
if (s.kind === "sphere") {
|
|
10450
|
-
hx = hy =
|
|
10733
|
+
hx = hy = hz2 = s.radius;
|
|
10451
10734
|
} else {
|
|
10452
10735
|
const m = quat.toMatrix(q);
|
|
10453
10736
|
if (s.kind === "box") {
|
|
10454
10737
|
const a = s.size[0] / 2, b = s.size[1] / 2, c = s.size[2] / 2;
|
|
10455
10738
|
hx = Math.abs(m[0]) * a + Math.abs(m[1]) * b + Math.abs(m[2]) * c;
|
|
10456
10739
|
hy = Math.abs(m[3]) * a + Math.abs(m[4]) * b + Math.abs(m[5]) * c;
|
|
10457
|
-
|
|
10740
|
+
hz2 = Math.abs(m[6]) * a + Math.abs(m[7]) * b + Math.abs(m[8]) * c;
|
|
10458
10741
|
} else {
|
|
10459
10742
|
const ax = [m[1], m[4], m[7]];
|
|
10460
10743
|
const hh = s.height / 2;
|
|
10461
10744
|
const e = (i) => s.radius * Math.sqrt(Math.max(0, 1 - ax[i] * ax[i])) + hh * Math.abs(ax[i]);
|
|
10462
10745
|
hx = e(0);
|
|
10463
10746
|
hy = e(1);
|
|
10464
|
-
|
|
10747
|
+
hz2 = e(2);
|
|
10465
10748
|
}
|
|
10466
10749
|
}
|
|
10467
|
-
return { min: [center[0] - hx, center[1] - hy, center[2] -
|
|
10750
|
+
return { min: [center[0] - hx, center[1] - hy, center[2] - hz2], max: [center[0] + hx, center[1] + hy, center[2] + hz2] };
|
|
10468
10751
|
};
|
|
10469
10752
|
var FLEXURE_DEFAULTS = {
|
|
10470
10753
|
enabled: true,
|
|
@@ -10649,6 +10932,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10649
10932
|
},
|
|
10650
10933
|
{
|
|
10651
10934
|
id: "wood_soft",
|
|
10935
|
+
splitForm: "slivers",
|
|
10652
10936
|
density: 500,
|
|
10653
10937
|
hpPerKg: 90,
|
|
10654
10938
|
penetration: 9,
|
|
@@ -10694,6 +10978,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10694
10978
|
},
|
|
10695
10979
|
{
|
|
10696
10980
|
id: "wood_hard",
|
|
10981
|
+
splitForm: "slivers",
|
|
10697
10982
|
density: 750,
|
|
10698
10983
|
hpPerKg: 140,
|
|
10699
10984
|
penetration: 14,
|
|
@@ -10738,6 +11023,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10738
11023
|
},
|
|
10739
11024
|
{
|
|
10740
11025
|
id: "plywood",
|
|
11026
|
+
splitForm: "plies",
|
|
10741
11027
|
density: 600,
|
|
10742
11028
|
hpPerKg: 70,
|
|
10743
11029
|
penetration: 7,
|
|
@@ -10785,6 +11071,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10785
11071
|
},
|
|
10786
11072
|
{
|
|
10787
11073
|
id: "mdf",
|
|
11074
|
+
splitForm: "granules",
|
|
10788
11075
|
density: 750,
|
|
10789
11076
|
hpPerKg: 45,
|
|
10790
11077
|
penetration: 6,
|
|
@@ -10951,6 +11238,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10951
11238
|
floor: 60
|
|
10952
11239
|
}
|
|
10953
11240
|
},
|
|
11241
|
+
ricochetDeg: 12,
|
|
10954
11242
|
hitDebris: "chips",
|
|
10955
11243
|
breakDebris: "dust",
|
|
10956
11244
|
visual: { innerColor: 10197652 },
|
|
@@ -10982,6 +11270,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10982
11270
|
0.04,
|
|
10983
11271
|
0.12
|
|
10984
11272
|
],
|
|
11273
|
+
ricochetDeg: 10,
|
|
10985
11274
|
hitDebris: "chips",
|
|
10986
11275
|
breakDebris: "dust",
|
|
10987
11276
|
visual: { innerColor: 12085567 },
|
|
@@ -11039,6 +11328,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11039
11328
|
0.06,
|
|
11040
11329
|
0.3
|
|
11041
11330
|
],
|
|
11331
|
+
ricochetDeg: 15,
|
|
11042
11332
|
hitDebris: "chips",
|
|
11043
11333
|
breakDebris: "dust",
|
|
11044
11334
|
visual: { innerColor: 9407364 },
|
|
@@ -11296,6 +11586,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11296
11586
|
},
|
|
11297
11587
|
{
|
|
11298
11588
|
id: "green_wood",
|
|
11589
|
+
splitForm: "slivers",
|
|
11299
11590
|
density: 650,
|
|
11300
11591
|
hpPerKg: 5,
|
|
11301
11592
|
penetration: 10,
|
|
@@ -11466,6 +11757,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11466
11757
|
0.03,
|
|
11467
11758
|
0.15
|
|
11468
11759
|
],
|
|
11760
|
+
ricochetDeg: 8,
|
|
11469
11761
|
hitDebris: "chips",
|
|
11470
11762
|
breakDebris: "dust",
|
|
11471
11763
|
thermal: {
|
|
@@ -11565,6 +11857,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11565
11857
|
0.02,
|
|
11566
11858
|
0.08
|
|
11567
11859
|
],
|
|
11860
|
+
ricochetDeg: 8,
|
|
11568
11861
|
hitDebris: "chips",
|
|
11569
11862
|
breakDebris: "dust",
|
|
11570
11863
|
thermal: {
|
|
@@ -11576,6 +11869,127 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11576
11869
|
"brittle"
|
|
11577
11870
|
],
|
|
11578
11871
|
compressive: 1e5
|
|
11872
|
+
},
|
|
11873
|
+
{
|
|
11874
|
+
id: "mud",
|
|
11875
|
+
density: 1700,
|
|
11876
|
+
hpPerKg: 1,
|
|
11877
|
+
penetration: 5,
|
|
11878
|
+
ductility: 0.8,
|
|
11879
|
+
fracture: "crumble",
|
|
11880
|
+
fragmentSize: [
|
|
11881
|
+
0.02,
|
|
11882
|
+
0.08
|
|
11883
|
+
],
|
|
11884
|
+
hitDebris: "dust",
|
|
11885
|
+
breakDebris: "dust",
|
|
11886
|
+
thermal: {
|
|
11887
|
+
conductivity: 0.5,
|
|
11888
|
+
heatCapacity: 3.2
|
|
11889
|
+
},
|
|
11890
|
+
tags: [
|
|
11891
|
+
"soil",
|
|
11892
|
+
"wet"
|
|
11893
|
+
],
|
|
11894
|
+
compressive: 5e3
|
|
11895
|
+
},
|
|
11896
|
+
{
|
|
11897
|
+
id: "clay",
|
|
11898
|
+
density: 1800,
|
|
11899
|
+
hpPerKg: 5,
|
|
11900
|
+
penetration: 7,
|
|
11901
|
+
ductility: 0.05,
|
|
11902
|
+
fracture: "crumble",
|
|
11903
|
+
fragmentSize: [
|
|
11904
|
+
0.02,
|
|
11905
|
+
0.1
|
|
11906
|
+
],
|
|
11907
|
+
hitDebris: "chips",
|
|
11908
|
+
breakDebris: "dust",
|
|
11909
|
+
thermal: {
|
|
11910
|
+
conductivity: 0.15,
|
|
11911
|
+
heatCapacity: 2.2
|
|
11912
|
+
},
|
|
11913
|
+
tags: [
|
|
11914
|
+
"soil",
|
|
11915
|
+
"brittle"
|
|
11916
|
+
],
|
|
11917
|
+
compressive: 6e4
|
|
11918
|
+
},
|
|
11919
|
+
{
|
|
11920
|
+
id: "snow",
|
|
11921
|
+
density: 300,
|
|
11922
|
+
hpPerKg: 1,
|
|
11923
|
+
penetration: 3,
|
|
11924
|
+
ductility: 0.6,
|
|
11925
|
+
fracture: "crumble",
|
|
11926
|
+
fragmentSize: [
|
|
11927
|
+
0.01,
|
|
11928
|
+
0.05
|
|
11929
|
+
],
|
|
11930
|
+
hitDebris: "dust",
|
|
11931
|
+
breakDebris: "dust",
|
|
11932
|
+
thermal: {
|
|
11933
|
+
conductivity: 0.1,
|
|
11934
|
+
heatCapacity: 2.1
|
|
11935
|
+
},
|
|
11936
|
+
tags: [
|
|
11937
|
+
"soil",
|
|
11938
|
+
"frozen"
|
|
11939
|
+
],
|
|
11940
|
+
compressive: 1e3
|
|
11941
|
+
},
|
|
11942
|
+
{
|
|
11943
|
+
id: "cobble",
|
|
11944
|
+
density: 2650,
|
|
11945
|
+
hpPerKg: 25,
|
|
11946
|
+
penetration: 25,
|
|
11947
|
+
penetrationClass: "concrete",
|
|
11948
|
+
penetrationScale: 0.9,
|
|
11949
|
+
ductility: 0.03,
|
|
11950
|
+
fracture: "crumble",
|
|
11951
|
+
fragmentSize: [
|
|
11952
|
+
0.05,
|
|
11953
|
+
0.15
|
|
11954
|
+
],
|
|
11955
|
+
ricochetDeg: 15,
|
|
11956
|
+
hitDebris: "chips",
|
|
11957
|
+
breakDebris: "chips",
|
|
11958
|
+
thermal: {
|
|
11959
|
+
conductivity: 0.2,
|
|
11960
|
+
heatCapacity: 2.5
|
|
11961
|
+
},
|
|
11962
|
+
tags: [
|
|
11963
|
+
"masonry",
|
|
11964
|
+
"brittle"
|
|
11965
|
+
],
|
|
11966
|
+
compressive: 3e5
|
|
11967
|
+
},
|
|
11968
|
+
{
|
|
11969
|
+
id: "paver",
|
|
11970
|
+
density: 2300,
|
|
11971
|
+
hpPerKg: 14,
|
|
11972
|
+
penetration: 18,
|
|
11973
|
+
penetrationClass: "concrete",
|
|
11974
|
+
penetrationScale: 1,
|
|
11975
|
+
ductility: 0.03,
|
|
11976
|
+
fracture: "crumble",
|
|
11977
|
+
fragmentSize: [
|
|
11978
|
+
0.04,
|
|
11979
|
+
0.12
|
|
11980
|
+
],
|
|
11981
|
+
ricochetDeg: 12,
|
|
11982
|
+
hitDebris: "chips",
|
|
11983
|
+
breakDebris: "chips",
|
|
11984
|
+
thermal: {
|
|
11985
|
+
conductivity: 0.15,
|
|
11986
|
+
heatCapacity: 2.5
|
|
11987
|
+
},
|
|
11988
|
+
tags: [
|
|
11989
|
+
"masonry",
|
|
11990
|
+
"brittle"
|
|
11991
|
+
],
|
|
11992
|
+
compressive: 35e4
|
|
11579
11993
|
}
|
|
11580
11994
|
];
|
|
11581
11995
|
var material_hints_default = {
|
|
@@ -11714,6 +12128,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11714
12128
|
return out;
|
|
11715
12129
|
};
|
|
11716
12130
|
var RESTING_HOLD_COS = Math.cos(Math.PI / 6);
|
|
12131
|
+
var BLAST_REFERENCE_RESISTANCE = 200 * 1900 * 0.13;
|
|
11717
12132
|
var assemblies_default = [
|
|
11718
12133
|
{
|
|
11719
12134
|
id: "brick_wall_plastered",
|
|
@@ -12099,6 +12514,23 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12099
12514
|
}
|
|
12100
12515
|
};
|
|
12101
12516
|
var tmpV = (THREE) => new THREE.Vector3();
|
|
12517
|
+
function plainPositions(mesh2, v0, v1) {
|
|
12518
|
+
const { collapsedRanges, plainPositions: plain } = mesh2.userData;
|
|
12519
|
+
if (!plain || !collapsedRanges?.some(([a, b]) => a < v1 && b > v0)) return null;
|
|
12520
|
+
return plain;
|
|
12521
|
+
}
|
|
12522
|
+
function showCollapsedRanges(mesh2) {
|
|
12523
|
+
const data = mesh2.userData;
|
|
12524
|
+
const plain = data.plainPositions;
|
|
12525
|
+
const position = mesh2.geometry?.getAttribute(POS_ATTR);
|
|
12526
|
+
if (!plain || !data.collapsedRanges?.length || !position) return;
|
|
12527
|
+
const array = position.array;
|
|
12528
|
+
for (const [v0, v1] of data.collapsedRanges) array.set(plain.subarray(v0 * 3, v1 * 3), v0 * 3);
|
|
12529
|
+
data.collapsedRanges = [];
|
|
12530
|
+
position.needsUpdate = true;
|
|
12531
|
+
mesh2.geometry.computeBoundingBox();
|
|
12532
|
+
mesh2.geometry.computeBoundingSphere();
|
|
12533
|
+
}
|
|
12102
12534
|
var MIN_DENT_VERTICES = 10;
|
|
12103
12535
|
var WIDEN_MARGIN = 1.1;
|
|
12104
12536
|
var SKIN_FOLLOW = 0.85;
|
|
@@ -12864,7 +13296,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12864
13296
|
const a = src.getAttribute(name);
|
|
12865
13297
|
if (a.isInterleavedBufferAttribute) continue;
|
|
12866
13298
|
const size = a.itemSize;
|
|
12867
|
-
const from = (name === POS_ATTR ? host.work : null) ?? a.array;
|
|
13299
|
+
const from = (name === POS_ATTR ? plainPositions(host.mesh, v0, v1) ?? host.work : null) ?? a.array;
|
|
12868
13300
|
const slice = from.slice(v0 * size, v1 * size);
|
|
12869
13301
|
const Ctor = a.array.constructor;
|
|
12870
13302
|
geo.setAttribute(name, new this.THREE.BufferAttribute(new Ctor(slice), size, a.normalized));
|
|
@@ -13022,11 +13454,29 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13022
13454
|
// src/renderer.ts
|
|
13023
13455
|
var rendererCount = 0;
|
|
13024
13456
|
var COLLAPSE_CLOUD_KG = 1e3;
|
|
13457
|
+
var REBAR_COLOR = 7628380;
|
|
13458
|
+
var STEEL_DENSITY = 7850;
|
|
13025
13459
|
var CHIP_DECAL_EDGE = 0.05;
|
|
13026
13460
|
var WOOD_RIM = 1.5;
|
|
13027
13461
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
13028
13462
|
var GLOW_FROM_C = 430;
|
|
13029
13463
|
var QUEUE_CAP = 4e3;
|
|
13464
|
+
var STRUCTURAL_EVENTS = /* @__PURE__ */ new Set([
|
|
13465
|
+
"fracture",
|
|
13466
|
+
"detach",
|
|
13467
|
+
"collapse",
|
|
13468
|
+
"settle",
|
|
13469
|
+
"destroyed",
|
|
13470
|
+
"expose",
|
|
13471
|
+
"stage",
|
|
13472
|
+
"hole",
|
|
13473
|
+
"ignite",
|
|
13474
|
+
"primed",
|
|
13475
|
+
"extinguish",
|
|
13476
|
+
"burnout",
|
|
13477
|
+
"leak",
|
|
13478
|
+
"explode"
|
|
13479
|
+
]);
|
|
13030
13480
|
var MIN_FRAGMENTS_PER_SLICE = 8;
|
|
13031
13481
|
var STATIC_REFRESH = 0.5;
|
|
13032
13482
|
var STATIC_REFRESH_SLICE_MS = 1;
|
|
@@ -13135,6 +13585,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13135
13585
|
__publicField(this, "pieceBoxes", /* @__PURE__ */ new WeakMap());
|
|
13136
13586
|
__publicField(this, "staticAt", 0);
|
|
13137
13587
|
__publicField(this, "queue", []);
|
|
13588
|
+
/** The cover of each body around a part of the world that went off, read as the event is emitted: before the blast tore or moved anything. */
|
|
13589
|
+
__publicField(this, "coverWhenWentOff", /* @__PURE__ */ new WeakMap());
|
|
13138
13590
|
/** Presentation work an event left for later frames (fragments still to spawn, loose groups still to split), run in order within the frame budget. */
|
|
13139
13591
|
__publicField(this, "slices", []);
|
|
13140
13592
|
/** When the work of this frame must stop; `Infinity` outside a drain, where a call finishes what it started. */
|
|
@@ -13214,7 +13666,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13214
13666
|
groundAt: this.groundAt,
|
|
13215
13667
|
gravity: this.gravity,
|
|
13216
13668
|
max: this.maxFragments,
|
|
13217
|
-
life: o.fragmentLife ?? 12
|
|
13669
|
+
life: o.fragmentLife ?? 12,
|
|
13670
|
+
formOf: (id) => this.world.materials.get(id)?.splitForm
|
|
13218
13671
|
});
|
|
13219
13672
|
this.cages = new RebarCages(THREE, o.scene);
|
|
13220
13673
|
this.pieceSys = new PieceSystem(THREE, {
|
|
@@ -13227,7 +13680,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13227
13680
|
onSlam: (p, hit, speed) => this.slam(p, hit, speed)
|
|
13228
13681
|
});
|
|
13229
13682
|
this.unsubscribe = this.world.on("*", (ev) => {
|
|
13230
|
-
if (
|
|
13683
|
+
if (ev.type === "explode") this.coverWhenWentOff.set(ev, this.coverAround(ev));
|
|
13684
|
+
if (this.queue.length < QUEUE_CAP || STRUCTURAL_EVENTS.has(ev.type)) this.queue.push(ev);
|
|
13231
13685
|
});
|
|
13232
13686
|
}
|
|
13233
13687
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -13750,7 +14204,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13750
14204
|
this.onChip(ev);
|
|
13751
14205
|
break;
|
|
13752
14206
|
case "expose":
|
|
13753
|
-
this.cages.set(`${ev.object}|${ev.part}`, ev.bars);
|
|
14207
|
+
if (ev.bars.length > 0) this.cages.set(`${ev.object}|${ev.part}`, ev.bars, this.bounds.get(ev.object)?.root);
|
|
14208
|
+
else this.dropCage(ev.object, ev.part);
|
|
13754
14209
|
if (ev.bars.length > 0) {
|
|
13755
14210
|
const [a, b] = ev.bars[0];
|
|
13756
14211
|
this.playEvent({ kind: "expose" }, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2]);
|
|
@@ -13784,8 +14239,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13784
14239
|
}
|
|
13785
14240
|
case "melt":
|
|
13786
14241
|
this.activate(ev.object, ev.part);
|
|
13787
|
-
if (this.time - (this.throttles.get(`melt:${ev.object}
|
|
13788
|
-
this.throttles.set(`melt:${ev.object}
|
|
14242
|
+
if (this.time - (this.throttles.get(`melt:${ev.object}`) ?? -9) > 1.5) {
|
|
14243
|
+
this.throttles.set(`melt:${ev.object}`, this.time);
|
|
13789
14244
|
this.sound("melt_sizzle", this.rootPoint(ev.object), 0.4);
|
|
13790
14245
|
}
|
|
13791
14246
|
break;
|
|
@@ -14048,7 +14503,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14048
14503
|
const rotation2 = new T.Quaternion(ev.rotation[0], ev.rotation[1], ev.rotation[2], ev.rotation[3]);
|
|
14049
14504
|
for (const part of this.bounds.get(ev.object)?.parts.values() ?? []) for (const mv of part.meshes) this.visuals.clearDecals(mv, center, half, rotation2);
|
|
14050
14505
|
this.vfx.peekWorkshop()?.decals.clearInside(ev.center, [half.x, half.y, half.z], ev.rotation);
|
|
14051
|
-
this.vfx.burst("dust", ev.center, [0, 1, 0], 8, 0.8, { color });
|
|
14506
|
+
this.vfx.burst("dust", ev.center, [0, 1, 0], ev.units ? Math.min(40, 8 + ev.units.length) : 8, ev.units ? 1.6 : 0.8, { color });
|
|
14052
14507
|
this.impactSound(pv?.material ?? "", ev.center, 0.7, ev.blow);
|
|
14053
14508
|
}
|
|
14054
14509
|
onFracture(ev) {
|
|
@@ -14067,6 +14522,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14067
14522
|
this.visuals.hide(pv, "broken");
|
|
14068
14523
|
}
|
|
14069
14524
|
this.spawnFragments(ev, 0, { def, color, opacity, transparent });
|
|
14525
|
+
this.loosenIfFootless(ev.object);
|
|
14070
14526
|
const up = [0, 1, 0];
|
|
14071
14527
|
const sizeK = clamp6(ev.fragments.length / 10, 0.5, 2);
|
|
14072
14528
|
switch (ev.style) {
|
|
@@ -14112,6 +14568,33 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14112
14568
|
this.sound("detach", this.rootPoint(ev.object), 0.5);
|
|
14113
14569
|
this.splitLoose(ev.object);
|
|
14114
14570
|
}
|
|
14571
|
+
/**
|
|
14572
|
+
* The cage of a part gave way (`expose` with no bars): nothing holds its bars any more, so each comes down where it is, as steel debris that stays on the
|
|
14573
|
+
* ground with a body (`FragmentSystem.spawn` with `lasting`), not as bars left standing in the air.
|
|
14574
|
+
*/
|
|
14575
|
+
dropCage(objectId, partId) {
|
|
14576
|
+
const bars = this.cages.take(`${objectId}|${partId}`);
|
|
14577
|
+
if (bars.length === 0) return;
|
|
14578
|
+
const radius = this.cages.barRadius;
|
|
14579
|
+
const density = this.world.materials.get("steel")?.density ?? STEEL_DENSITY;
|
|
14580
|
+
const material = this.fragments.materialFor("steel", REBAR_COLOR, false);
|
|
14581
|
+
const rnd2 = createRng(hashString(`${objectId}|${partId}`));
|
|
14582
|
+
bars.forEach((bar, i) => {
|
|
14583
|
+
const fragment = {
|
|
14584
|
+
id: `${partId}#bar${i}`,
|
|
14585
|
+
part: partId,
|
|
14586
|
+
material: "steel",
|
|
14587
|
+
shape: "chunk",
|
|
14588
|
+
center: bar.center,
|
|
14589
|
+
size: [2 * radius, bar.length, 2 * radius],
|
|
14590
|
+
rotation: bar.rotation,
|
|
14591
|
+
velocity: [(rnd2() - 0.5) * 0.4, 0, (rnd2() - 0.5) * 0.4],
|
|
14592
|
+
angularVelocity: [(rnd2() - 0.5) * 1.5, (rnd2() - 0.5) * 0.5, (rnd2() - 0.5) * 1.5],
|
|
14593
|
+
mass: density * Math.PI * radius * radius * bar.length
|
|
14594
|
+
};
|
|
14595
|
+
this.fragments.spawn(fragment, material, objectId, this.maxFragments, true);
|
|
14596
|
+
});
|
|
14597
|
+
}
|
|
14115
14598
|
/** Turn every loose group of the object (a detach, a collapse) into a piece of its own. */
|
|
14116
14599
|
splitLoose(objectId) {
|
|
14117
14600
|
if (!this.autoSplit) return;
|
|
@@ -14166,10 +14649,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14166
14649
|
if (!pv) continue;
|
|
14167
14650
|
from.parts.delete(p.id);
|
|
14168
14651
|
nb.parts.set(p.id, pv);
|
|
14652
|
+
this.cages.move(`${from.id}|${p.id}`, `${newId}|${p.id}`, group);
|
|
14169
14653
|
for (const mv of pv.meshes) {
|
|
14170
14654
|
if (mv.host && !mv.loose) this.visuals.extract(mv, group);
|
|
14171
14655
|
else {
|
|
14172
14656
|
group.attach(mv.mesh);
|
|
14657
|
+
showCollapsedRanges(mv.mesh);
|
|
14173
14658
|
mv.moved = true;
|
|
14174
14659
|
}
|
|
14175
14660
|
}
|
|
@@ -14196,6 +14681,45 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14196
14681
|
if (kitOriginOf(from.root)?.kind === "furniture") this.tipIfTopHeavy(nb);
|
|
14197
14682
|
this.o.onPiece?.(piece);
|
|
14198
14683
|
}
|
|
14684
|
+
/**
|
|
14685
|
+
* Furniture that lost what it stood on falls: with every leg of a table broken the top is loose, and with the legs of one side gone what is left topples. Feet are the
|
|
14686
|
+
* meshes at the bottom of the object as it was whole; while the ones left still span the same floor (a cupboard with a broken door, a table missing one leg) it stands.
|
|
14687
|
+
*/
|
|
14688
|
+
loosenIfFootless(objectId) {
|
|
14689
|
+
const b = this.bounds.get(objectId);
|
|
14690
|
+
if (!b || !this.autoSplit) return;
|
|
14691
|
+
const left = this.meshesOf(b).filter((mv) => this.visuals.shown(mv));
|
|
14692
|
+
if (left.length === 0) return;
|
|
14693
|
+
const body = b.piece ?? this.pieceSys.get(objectId);
|
|
14694
|
+
if (body) {
|
|
14695
|
+
const { half, center } = this.measure(b.root, left);
|
|
14696
|
+
body.halfExtents = half;
|
|
14697
|
+
this.pieceSys.setCenter(body, center);
|
|
14698
|
+
body.mass = Math.max(0.05, this.massOf(objectId));
|
|
14699
|
+
body.sleeping = false;
|
|
14700
|
+
return;
|
|
14701
|
+
}
|
|
14702
|
+
if (kitOriginOf(b.root)?.kind !== "furniture") return;
|
|
14703
|
+
const boxOf = (mv) => this.visuals.worldBox(mv, new this.THREE.Box3());
|
|
14704
|
+
const whole = [...b.parts.values()].flatMap((pv) => pv.meshes).map(boxOf).filter((one) => !one.isEmpty());
|
|
14705
|
+
const floor2 = Math.min(...whole.map((one) => one.min.y));
|
|
14706
|
+
const feetOf = (boxes) => boxes.filter((one) => one.min.y < floor2 + FEET_BAND).reduce((feet, one) => feet.union(one), new this.THREE.Box3());
|
|
14707
|
+
const was = feetOf(whole);
|
|
14708
|
+
const now = feetOf(left.map(boxOf).filter((one) => !one.isEmpty()));
|
|
14709
|
+
const same = !now.isEmpty() && now.min.x <= was.min.x + FEET_BAND && now.max.x >= was.max.x - FEET_BAND && now.min.z <= was.min.z + FEET_BAND && now.max.z >= was.max.z - FEET_BAND;
|
|
14710
|
+
if (same) return;
|
|
14711
|
+
if (!now.isEmpty()) {
|
|
14712
|
+
const box = left.map(boxOf).reduce((all, one) => all.union(one), new this.THREE.Box3());
|
|
14713
|
+
const centreX = (box.min.x + box.max.x) / 2;
|
|
14714
|
+
const centreZ = (box.min.z + box.max.z) / 2;
|
|
14715
|
+
const height = box.max.y - box.min.y;
|
|
14716
|
+
const narrow = height >= TIP_MIN_HEIGHT && Math.min(now.max.x - now.min.x, now.max.z - now.min.z) < TIP_NARROW * height;
|
|
14717
|
+
if (!narrow && centreX > now.min.x && centreX < now.max.x && centreZ > now.min.z && centreZ < now.max.z) return;
|
|
14718
|
+
}
|
|
14719
|
+
const piece = this.makePiece(objectId);
|
|
14720
|
+
this.tipIfTopHeavy(b);
|
|
14721
|
+
this.o.onLoosened?.(objectId, piece, "apart");
|
|
14722
|
+
}
|
|
14199
14723
|
/**
|
|
14200
14724
|
* A piece of furniture standing on feet too close for its height (a board on two legs in a line, a lone leg) does not stay up: the box of a piece hides that its weight is up
|
|
14201
14725
|
* high and its feet are thin, so it gets the start of the fall, across the line of its feet.
|
|
@@ -14227,7 +14751,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14227
14751
|
const report = options.report;
|
|
14228
14752
|
const confinement = report?.find((r) => r.blast !== void 0)?.blast?.confinement ?? 1;
|
|
14229
14753
|
const energy = ev.energy * confinement;
|
|
14230
|
-
const
|
|
14754
|
+
const wentOff = this.coverWhenWentOff.get(ev);
|
|
14755
|
+
const reachOf = (objectId, at3) => report?.find((r) => r.object === objectId)?.blast?.shelter ?? wentOff?.get(objectId) ?? this.world.blastReach?.(p, at3, { ignore: ev.object ? [ev.object, objectId] : [objectId] }) ?? 1;
|
|
14231
14756
|
const shove = (pc) => {
|
|
14232
14757
|
this.pieceSys.centerOf(pc, c);
|
|
14233
14758
|
if (c.distanceTo(this.v2.set(p[0], p[1], p[2])) > ev.radius) return;
|
|
@@ -14247,6 +14772,29 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14247
14772
|
...report ? { confinement } : {}
|
|
14248
14773
|
});
|
|
14249
14774
|
}
|
|
14775
|
+
/** The cover between a part of the world that goes off and the centre of each piece and each shoveable body within its radius, as the world stands at that moment. */
|
|
14776
|
+
coverAround(ev) {
|
|
14777
|
+
const cover = /* @__PURE__ */ new Map();
|
|
14778
|
+
const reach = this.world.blastReach;
|
|
14779
|
+
if (!reach) return cover;
|
|
14780
|
+
const point = new this.THREE.Vector3(ev.point[0], ev.point[1], ev.point[2]);
|
|
14781
|
+
const centre = new this.THREE.Vector3();
|
|
14782
|
+
const read = (objectId) => {
|
|
14783
|
+
if (centre.distanceTo(point) <= ev.radius) cover.set(objectId, reach.call(this.world, ev.point, [centre.x, centre.y, centre.z], { ignore: [ev.object, objectId] }));
|
|
14784
|
+
};
|
|
14785
|
+
for (const pc of this.pieceSys.list) {
|
|
14786
|
+
this.pieceSys.centerOf(pc, centre);
|
|
14787
|
+
read(pc.objectId);
|
|
14788
|
+
}
|
|
14789
|
+
const shoveable = this.o.shoveable;
|
|
14790
|
+
if (!shoveable) return cover;
|
|
14791
|
+
for (const b of this.bounds.values()) {
|
|
14792
|
+
if (b.piece || cover.has(b.id) || !shoveable(b.id) || !this.world.has(b.id)) continue;
|
|
14793
|
+
centre.copy(this.measure(b.root, this.meshesOf(b)).center).applyQuaternion(b.root.quaternion).add(b.root.position);
|
|
14794
|
+
read(b.id);
|
|
14795
|
+
}
|
|
14796
|
+
return cover;
|
|
14797
|
+
}
|
|
14250
14798
|
/** 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. */
|
|
14251
14799
|
loosen(point, radius, energy, reachOf) {
|
|
14252
14800
|
const shoveable = this.o.shoveable;
|
|
@@ -14542,7 +15090,63 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14542
15090
|
}
|
|
14543
15091
|
};
|
|
14544
15092
|
|
|
14545
|
-
// ../game-audio/dist/chunk-
|
|
15093
|
+
// ../game-audio/dist/chunk-JV5XXCQE.js
|
|
15094
|
+
var SFX_TIERS = { light: 0, medium: 0.34, heavy: 0.67 };
|
|
15095
|
+
function splitTier(id) {
|
|
15096
|
+
const match = /^(.*)_(light|medium|heavy)$/.exec(id);
|
|
15097
|
+
return match ? { base: match[1], tier: match[2] } : { base: id };
|
|
15098
|
+
}
|
|
15099
|
+
var refsOf = (entries2, baseUrl) => (entries2 ?? []).map((entry) => ({ url: `${baseUrl}${entry.file}` }));
|
|
15100
|
+
function sampleBankFromManifest(manifest, options) {
|
|
15101
|
+
const baseUrl = options.baseUrl ?? "";
|
|
15102
|
+
const samples = [];
|
|
15103
|
+
const tiersOf = /* @__PURE__ */ new Map();
|
|
15104
|
+
for (const sound of manifest.sounds) {
|
|
15105
|
+
const { base, tier } = splitTier(sound.id);
|
|
15106
|
+
if (tier) tiersOf.set(base, [...tiersOf.get(base) ?? [], sound]);
|
|
15107
|
+
}
|
|
15108
|
+
const bases = /* @__PURE__ */ new Map();
|
|
15109
|
+
for (const sound of manifest.sounds) {
|
|
15110
|
+
if (!splitTier(sound.id).tier) bases.set(sound.id, sound);
|
|
15111
|
+
}
|
|
15112
|
+
for (const [base, tiers] of tiersOf) {
|
|
15113
|
+
if (!bases.has(base)) bases.set(base, { id: base, category: tiers[0]?.category ?? "", layers: { close: tiers[0]?.layers.close ?? [] } });
|
|
15114
|
+
}
|
|
15115
|
+
for (const sound of bases.values()) {
|
|
15116
|
+
const numbers = options.sounds?.[sound.id] ?? options.defaults?.(sound);
|
|
15117
|
+
const close = refsOf(sound.layers.close, baseUrl);
|
|
15118
|
+
if (!numbers || close.length === 0) continue;
|
|
15119
|
+
const tiers = (tiersOf.get(sound.id) ?? []).map((tier) => {
|
|
15120
|
+
const name = splitTier(tier.id).tier;
|
|
15121
|
+
const tierFar = refsOf(tier.layers.far, baseUrl);
|
|
15122
|
+
return { name, from: SFX_TIERS[name] ?? 0, close: refsOf(tier.layers.close, baseUrl), ...tierFar.length > 0 ? { far: tierFar } : {} };
|
|
15123
|
+
}).sort((a, b) => a.from - b.from);
|
|
15124
|
+
const far = refsOf(sound.layers.far, baseUrl);
|
|
15125
|
+
const indoor = refsOf(sound.layers.tail_in, baseUrl);
|
|
15126
|
+
const outdoor = refsOf(sound.layers.tail_out, baseUrl);
|
|
15127
|
+
const files = [sound, ...tiersOf.get(sound.id) ?? []].flatMap((entry) => Object.values(entry.layers).flatMap((entries2) => entries2 ?? []));
|
|
15128
|
+
samples.push({
|
|
15129
|
+
id: sound.id,
|
|
15130
|
+
bus: numbers.bus,
|
|
15131
|
+
close,
|
|
15132
|
+
...far.length > 0 ? { far } : {},
|
|
15133
|
+
...tiers.length > 0 ? { tiers } : {},
|
|
15134
|
+
...numbers.crossover ? { crossover: numbers.crossover } : {},
|
|
15135
|
+
...indoor.length > 0 || outdoor.length > 0 ? { tail: { ...indoor.length > 0 ? { indoor } : {}, ...outdoor.length > 0 ? { outdoor } : {}, ...numbers.tailGain === void 0 ? {} : { gain: numbers.tailGain } } } : {},
|
|
15136
|
+
...numbers.pitch === void 0 ? {} : { pitch: numbers.pitch },
|
|
15137
|
+
...numbers.gain === void 0 ? {} : { gain: numbers.gain },
|
|
15138
|
+
...numbers.falloff ? { falloff: numbers.falloff } : {},
|
|
15139
|
+
...numbers.cutoffK === void 0 ? {} : { cutoffK: numbers.cutoffK },
|
|
15140
|
+
...numbers.group ? { group: numbers.group } : {},
|
|
15141
|
+
...numbers.priority === void 0 ? {} : { priority: numbers.priority },
|
|
15142
|
+
...numbers.fallback ? { fallback: numbers.fallback } : {},
|
|
15143
|
+
sources: files.map((file) => `${file.file}: ${file.source} ${file.author} ${file.license}`)
|
|
15144
|
+
});
|
|
15145
|
+
}
|
|
15146
|
+
return { id: options.id, samples };
|
|
15147
|
+
}
|
|
15148
|
+
|
|
15149
|
+
// ../game-audio/dist/chunk-UUWKSG4J.js
|
|
14546
15150
|
var DISTANCE_CURVES = {
|
|
14547
15151
|
/** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
|
|
14548
15152
|
game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
|
|
@@ -14977,60 +15581,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14977
15581
|
burst: 20,
|
|
14978
15582
|
burstSpacing: 0.06
|
|
14979
15583
|
};
|
|
14980
|
-
var
|
|
14981
|
-
function splitTier(id) {
|
|
14982
|
-
const match = /^(.*)_(light|medium|heavy)$/.exec(id);
|
|
14983
|
-
return match ? { base: match[1], tier: match[2] } : { base: id };
|
|
14984
|
-
}
|
|
14985
|
-
var refsOf = (entries2, baseUrl) => (entries2 ?? []).map((entry) => ({ url: `${baseUrl}${entry.file}` }));
|
|
14986
|
-
function sampleBankFromManifest(manifest, options) {
|
|
14987
|
-
const baseUrl = options.baseUrl ?? "";
|
|
14988
|
-
const samples = [];
|
|
14989
|
-
const tiersOf = /* @__PURE__ */ new Map();
|
|
14990
|
-
for (const sound of manifest.sounds) {
|
|
14991
|
-
const { base, tier } = splitTier(sound.id);
|
|
14992
|
-
if (tier) tiersOf.set(base, [...tiersOf.get(base) ?? [], sound]);
|
|
14993
|
-
}
|
|
14994
|
-
const bases = /* @__PURE__ */ new Map();
|
|
14995
|
-
for (const sound of manifest.sounds) {
|
|
14996
|
-
if (!splitTier(sound.id).tier) bases.set(sound.id, sound);
|
|
14997
|
-
}
|
|
14998
|
-
for (const [base, tiers] of tiersOf) {
|
|
14999
|
-
if (!bases.has(base)) bases.set(base, { id: base, category: tiers[0]?.category ?? "", layers: { close: tiers[0]?.layers.close ?? [] } });
|
|
15000
|
-
}
|
|
15001
|
-
for (const sound of bases.values()) {
|
|
15002
|
-
const numbers = options.sounds?.[sound.id] ?? options.defaults?.(sound);
|
|
15003
|
-
const close = refsOf(sound.layers.close, baseUrl);
|
|
15004
|
-
if (!numbers || close.length === 0) continue;
|
|
15005
|
-
const tiers = (tiersOf.get(sound.id) ?? []).map((tier) => {
|
|
15006
|
-
const name = splitTier(tier.id).tier;
|
|
15007
|
-
const tierFar = refsOf(tier.layers.far, baseUrl);
|
|
15008
|
-
return { name, from: SFX_TIERS[name] ?? 0, close: refsOf(tier.layers.close, baseUrl), ...tierFar.length > 0 ? { far: tierFar } : {} };
|
|
15009
|
-
}).sort((a, b) => a.from - b.from);
|
|
15010
|
-
const far = refsOf(sound.layers.far, baseUrl);
|
|
15011
|
-
const indoor = refsOf(sound.layers.tail_in, baseUrl);
|
|
15012
|
-
const outdoor = refsOf(sound.layers.tail_out, baseUrl);
|
|
15013
|
-
const files = [sound, ...tiersOf.get(sound.id) ?? []].flatMap((entry) => Object.values(entry.layers).flatMap((entries2) => entries2 ?? []));
|
|
15014
|
-
samples.push({
|
|
15015
|
-
id: sound.id,
|
|
15016
|
-
bus: numbers.bus,
|
|
15017
|
-
close,
|
|
15018
|
-
...far.length > 0 ? { far } : {},
|
|
15019
|
-
...tiers.length > 0 ? { tiers } : {},
|
|
15020
|
-
...numbers.crossover ? { crossover: numbers.crossover } : {},
|
|
15021
|
-
...indoor.length > 0 || outdoor.length > 0 ? { tail: { ...indoor.length > 0 ? { indoor } : {}, ...outdoor.length > 0 ? { outdoor } : {}, ...numbers.tailGain === void 0 ? {} : { gain: numbers.tailGain } } } : {},
|
|
15022
|
-
...numbers.pitch === void 0 ? {} : { pitch: numbers.pitch },
|
|
15023
|
-
...numbers.gain === void 0 ? {} : { gain: numbers.gain },
|
|
15024
|
-
...numbers.falloff ? { falloff: numbers.falloff } : {},
|
|
15025
|
-
...numbers.cutoffK === void 0 ? {} : { cutoffK: numbers.cutoffK },
|
|
15026
|
-
...numbers.group ? { group: numbers.group } : {},
|
|
15027
|
-
...numbers.priority === void 0 ? {} : { priority: numbers.priority },
|
|
15028
|
-
...numbers.fallback ? { fallback: numbers.fallback } : {},
|
|
15029
|
-
sources: files.map((file) => `${file.file}: ${file.source} ${file.author} ${file.license}`)
|
|
15030
|
-
});
|
|
15031
|
-
}
|
|
15032
|
-
return { id: options.id, samples };
|
|
15033
|
-
}
|
|
15584
|
+
var CATALOG = new Map([...SFX_CATALOG, ...SFX_IMPACTS].map((entry) => [entry.id, entry]));
|
|
15034
15585
|
var PT = {
|
|
15035
15586
|
group: "Som",
|
|
15036
15587
|
name: "Painel de som (arquivos e C1\u2013C10)",
|
|
@@ -15105,9 +15656,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15105
15656
|
strengths: { light: "leve", medium: "m\xE9dio", heavy: "forte" },
|
|
15106
15657
|
states: { file: "A", synth: "S", missing: "F" },
|
|
15107
15658
|
criteria: { C1: "C1 Sincronia", C2: "C2 Certo p/ a\xE7\xE3o", C3: "C3 Dist\xE2ncia", C4: "C4 Posi\xE7\xE3o", C5: "C5 Varia\xE7\xE3o", C6: "C6 Volume", C7: "C7 Intensidade", C8: "C8 Cauda", C9: "C9 Rajada", C10: "C10 Escuta" },
|
|
15108
|
-
metrics: { source: "1 \xB7 Fonte do que tocou", chosen: "1 \xB7 Escolhido", matrix: "2 \xB7 Matriz", last: "\xDAltimo tocado", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Prefer\xEAncias", state: "5 \xB7 Medi\xE7\xE3o", count: "1 \xB7 Cat\xE1logo" },
|
|
15659
|
+
metrics: { source: "1 \xB7 Fonte do que tocou", chosen: "1 \xB7 Escolhido", matrix: "2 \xB7 Matriz", last: "\xDAltimo tocado", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Prefer\xEAncias", state: "5 \xB7 Medi\xE7\xE3o", count: "1 \xB7 Cat\xE1logo", unlicensed: "1 \xB7 Arquivo sem licen\xE7a" },
|
|
15109
15660
|
run: { idle: "parada", measuring: "medindo\u2026", done: "pronta", failed: "falhou", recording: "gravando\u2026", recorded: "grava\xE7\xE3o baixada" },
|
|
15110
|
-
words: { none: "\u2014", hidden: "? (revele)", prefers: "prefere", approved: "aprovado pelo dono" },
|
|
15661
|
+
words: { none: "\u2014", hidden: "? (revele)", prefers: "prefere", approved: "aprovado pelo dono", local_file: "arquivo do pacote local", sdk_file: "grava\xE7\xE3o do SDK", synth: "s\xEDntese do SDK" },
|
|
15111
15662
|
sources: { sdk: "SDK", user: "seu som", synth: "sintetizado" },
|
|
15112
15663
|
probe: "Fonte do som",
|
|
15113
15664
|
wall: "Parede (oclus\xE3o)",
|
|
@@ -15178,9 +15729,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15178
15729
|
environments: { indoor: "indoor", outdoor: "outdoor" },
|
|
15179
15730
|
strengths: { light: "light", medium: "medium", heavy: "heavy" },
|
|
15180
15731
|
criteria: { C1: "C1 Sync", C2: "C2 Right sound", C3: "C3 Distance", C4: "C4 Position", C5: "C5 Variation", C6: "C6 Level", C7: "C7 Strength", C8: "C8 Tail", C9: "C9 Burst", C10: "C10 Listening" },
|
|
15181
|
-
metrics: { source: "1 \xB7 Source of what played", chosen: "1 \xB7 Chosen", matrix: "2 \xB7 Matrix", last: "Last played", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Preferences", state: "5 \xB7 Measurement", count: "1 \xB7 Catalogue" },
|
|
15732
|
+
metrics: { source: "1 \xB7 Source of what played", chosen: "1 \xB7 Chosen", matrix: "2 \xB7 Matrix", last: "Last played", ab: "4 \xB7 A \xD7 B", blind: "4 \xB7 X", verdicts: "4 \xB7 Preferences", state: "5 \xB7 Measurement", count: "1 \xB7 Catalogue", unlicensed: "1 \xB7 File without licence" },
|
|
15182
15733
|
run: { idle: "idle", measuring: "measuring\u2026", done: "done", failed: "failed", recording: "recording\u2026", recorded: "recording downloaded" },
|
|
15183
|
-
words: { none: "\u2014", hidden: "? (reveal)", prefers: "prefers", approved: "approved by the owner" },
|
|
15734
|
+
words: { none: "\u2014", hidden: "? (reveal)", prefers: "prefers", approved: "approved by the owner", local_file: "local pack file", sdk_file: "SDK recording", synth: "SDK synthesis" },
|
|
15184
15735
|
sources: { sdk: "SDK", user: "your sound", synth: "synthesized" },
|
|
15185
15736
|
probe: "Sound source",
|
|
15186
15737
|
wall: "Wall (occlusion)",
|
|
@@ -15263,6 +15814,66 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15263
15814
|
sources: [`cena:${line}`]
|
|
15264
15815
|
};
|
|
15265
15816
|
}
|
|
15817
|
+
var hz = (midi) => 440 * 2 ** ((midi - 69) / 12);
|
|
15818
|
+
var layer2 = (note, beat, wave, level) => {
|
|
15819
|
+
const [at3, midi, beats] = note;
|
|
15820
|
+
const length = beats * beat;
|
|
15821
|
+
return {
|
|
15822
|
+
at: at3 * beat,
|
|
15823
|
+
duration: length,
|
|
15824
|
+
source: { kind: "osc", wave, frequency: { set: hz(midi) } },
|
|
15825
|
+
envelope: [[0, 1e-4, "set"], [0.01, level, "linear"], [Math.max(0.02, length * 0.9), level * 0.35, "linear"], [length, 1e-4, "exp", "fixed"]]
|
|
15826
|
+
};
|
|
15827
|
+
};
|
|
15828
|
+
function radioTrack(spec) {
|
|
15829
|
+
const beat = 60 / spec.bpm;
|
|
15830
|
+
const recipes = Array.from({ length: spec.bars }, (_, bar) => {
|
|
15831
|
+
const inBar = (notes) => notes.filter((n) => n[0] >= bar * 4 && n[0] < bar * 4 + 4).map((n) => [n[0] - bar * 4, n[1], n[2]]);
|
|
15832
|
+
const layers = [
|
|
15833
|
+
...inBar(spec.bass).map((n) => layer2(n, beat, "triangle", 0.16)),
|
|
15834
|
+
...inBar(spec.chords).map((n) => layer2(n, beat, "sine", 0.05)),
|
|
15835
|
+
...inBar(spec.melody).map((n) => layer2(n, beat, spec.wave, 0.07))
|
|
15836
|
+
];
|
|
15837
|
+
return { id: `${spec.id}_${bar}`, bus: "world", duration: 4 * beat, layers, falloff: { model: "none" } };
|
|
15838
|
+
});
|
|
15839
|
+
return { song: { id: spec.id, barSeconds: 4 * beat, bars: recipes.map((r) => r.id) }, recipes };
|
|
15840
|
+
}
|
|
15841
|
+
var chord = (bar, root, minor = false) => [[bar * 4, root, 4], [bar * 4, root + (minor ? 3 : 4), 4], [bar * 4, root + 7, 4]];
|
|
15842
|
+
var bassLine = (roots, pattern) => roots.flatMap((root, bar) => pattern.map((b) => [bar * 4 + b, root - 12, 0.9]));
|
|
15843
|
+
var TRACKS = [
|
|
15844
|
+
// a bright pop loop: I–V–vi–IV in C
|
|
15845
|
+
radioTrack({
|
|
15846
|
+
id: "radio_track_pop",
|
|
15847
|
+
bpm: 112,
|
|
15848
|
+
bars: 4,
|
|
15849
|
+
wave: "square",
|
|
15850
|
+
bass: bassLine([48, 55, 57, 53], [0, 1.5, 2, 3]),
|
|
15851
|
+
chords: [...chord(0, 60), ...chord(1, 55), ...chord(2, 57, true), ...chord(3, 53)],
|
|
15852
|
+
melody: [[0, 76, 1], [1, 74, 0.5], [1.5, 72, 1.5], [4, 74, 1], [5, 76, 1], [6, 79, 2], [8, 76, 1], [9, 74, 1], [10, 72, 2], [12, 72, 1], [13, 74, 1], [14, 72, 2]]
|
|
15853
|
+
}),
|
|
15854
|
+
// a syncopated loop in A minor, the bass on the off-beats
|
|
15855
|
+
radioTrack({
|
|
15856
|
+
id: "radio_track_groove",
|
|
15857
|
+
bpm: 100,
|
|
15858
|
+
bars: 4,
|
|
15859
|
+
wave: "triangle",
|
|
15860
|
+
bass: bassLine([57, 62, 64, 57], [0, 0.75, 1.5, 2.5, 3.25]),
|
|
15861
|
+
chords: [...chord(0, 57, true), ...chord(1, 62, true), ...chord(2, 64), ...chord(3, 57, true)],
|
|
15862
|
+
melody: [[0.5, 72, 0.5], [1.25, 76, 0.75], [2.5, 74, 1], [4.5, 77, 0.5], [5.25, 74, 0.75], [6.5, 72, 1.5], [8.5, 71, 0.5], [9.25, 74, 0.75], [10.5, 76, 1.5], [12.5, 72, 0.5], [13.25, 69, 2]]
|
|
15863
|
+
}),
|
|
15864
|
+
// a slow blues-like loop in F with a swung melody
|
|
15865
|
+
radioTrack({
|
|
15866
|
+
id: "radio_track_slow",
|
|
15867
|
+
bpm: 76,
|
|
15868
|
+
bars: 4,
|
|
15869
|
+
wave: "sine",
|
|
15870
|
+
bass: bassLine([53, 58, 53, 60], [0, 2]),
|
|
15871
|
+
chords: [...chord(0, 65), ...chord(1, 58), ...chord(2, 65), ...chord(3, 60)],
|
|
15872
|
+
melody: [[0, 72, 1.5], [1.67, 75, 0.33], [2, 77, 2], [4, 74, 1], [5, 72, 1], [6, 70, 2], [8, 72, 1.5], [9.67, 69, 0.33], [10, 65, 2], [12, 67, 1], [13, 70, 1], [14, 72, 2]]
|
|
15873
|
+
})
|
|
15874
|
+
];
|
|
15875
|
+
var RADIO_TRACKS = TRACKS.map((t) => t.song);
|
|
15876
|
+
var RADIO_BANK = { id: "vehicle_radio", sounds: TRACKS.flatMap((t) => t.recipes), loops: [] };
|
|
15266
15877
|
var FOLEY_FALLOFF = {
|
|
15267
15878
|
model: "inverse_square",
|
|
15268
15879
|
k: 0.02,
|
|
@@ -15869,11 +16480,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15869
16480
|
...s.splash ? [sceneNoise({ type: "bandpass", f: s.splash.f * take.f, f2: s.splash.f2 * take.f, q: 1.4, d: s.splash.d * take.t * weight ** 0.4, vol: s.splash.vol * gait.vol * take.v * weight ** 0.5 })] : []
|
|
15870
16481
|
];
|
|
15871
16482
|
}
|
|
15872
|
-
var quieter = (level, layers) => layers.map((
|
|
15873
|
-
const gain =
|
|
15874
|
-
if (typeof gain === "number") return { ...
|
|
15875
|
-
if (Array.isArray(gain)) return { ...
|
|
15876
|
-
return
|
|
16483
|
+
var quieter = (level, layers) => layers.map((layer22) => {
|
|
16484
|
+
const gain = layer22.gain ?? 1;
|
|
16485
|
+
if (typeof gain === "number") return { ...layer22, gain: gain * level };
|
|
16486
|
+
if (Array.isArray(gain)) return { ...layer22, gain: [gain[0] * level, gain[1] * level] };
|
|
16487
|
+
return layer22;
|
|
15877
16488
|
});
|
|
15878
16489
|
var recipe = (id, layers, falloff, bus = "world") => ({ id, bus, pan: true, layers, falloff, sources: ["sdk"] });
|
|
15879
16490
|
var takeId = (id, index) => `${id}_${String(index + 1).padStart(2, "0")}`;
|
|
@@ -15889,7 +16500,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15889
16500
|
(take) => quieter(LANDING_LEVEL, [
|
|
15890
16501
|
// both feet, a hair apart, then the knees taking the weight (a low body thump) and the clothes settling
|
|
15891
16502
|
...footLayers(surface, take, GAITS.run, LANDINGS[landing2].weight),
|
|
15892
|
-
...footLayers(surface, { ...take, v: take.v * 0.6 }, GAITS.run, LANDINGS[landing2].weight * 0.6).map((
|
|
16503
|
+
...footLayers(surface, { ...take, v: take.v * 0.6 }, GAITS.run, LANDINGS[landing2].weight * 0.6).map((layer22) => ({ ...layer22, at: (typeof layer22.at === "number" ? layer22.at : 0) + 0.018 * take.t })),
|
|
15893
16504
|
sceneTone({ f: 60 * take.f, f2: 38, d: 0.14 * LANDINGS[landing2].weight * take.t, vol: 0.22 * LANDINGS[landing2].weight ** 0.7 * take.v, delay: 0.02 }),
|
|
15894
16505
|
sceneNoise({ type: "bandpass", f: [1800, 2600], q: 0.8, d: 0.18, vol: LANDINGS[landing2].cloth * take.v, delay: 0.04 })
|
|
15895
16506
|
]),
|
|
@@ -15935,7 +16546,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15935
16546
|
fall_damage: (take) => [
|
|
15936
16547
|
...BODY.body_fall(take),
|
|
15937
16548
|
sceneNoise({ type: "highpass", f: 2800, d: 0.02, vol: 0.35 * take.v, delay: 0.01 }),
|
|
15938
|
-
...voice({ f0: 170, f1: 110, d: 0.32, fA: 650, fB: 1150, noise: 0.25, vol: 0.32 })(take).map((
|
|
16549
|
+
...voice({ f0: 170, f1: 110, d: 0.32, fA: 650, fB: 1150, noise: 0.25, vol: 0.32 })(take).map((layer22) => ({ ...layer22, at: 0.08 }))
|
|
15939
16550
|
],
|
|
15940
16551
|
player_hurt: voice({ f0: 150, f1: 105, d: 0.26, fA: 620, fB: 1100, noise: 0.3, vol: 0.35 }),
|
|
15941
16552
|
player_breath_run: (take) => [
|
|
@@ -16231,7 +16842,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
16231
16842
|
};
|
|
16232
16843
|
|
|
16233
16844
|
// src/audio/recipes.ts
|
|
16234
|
-
var scene = (id, layers, line, vars) => sceneRecipe(id, layers, line, vars ? { vars } : {});
|
|
16845
|
+
var scene = (id, layers, line, vars, group) => sceneRecipe(id, layers, line, { ...vars ? { vars } : {}, ...group ? { group } : {} });
|
|
16235
16846
|
var boomLayers = (big) => [
|
|
16236
16847
|
sceneNoise({ f: 1200, f2: 120, d: 1.8, vol: 2, wet: 1.2 }),
|
|
16237
16848
|
sceneTone({ f: 65, f2: 24, d: 1, vol: 1.8 }),
|
|
@@ -16250,7 +16861,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
16250
16861
|
[sceneNoise({ type: "bandpass", f: 850, q: 1, d: 0.25, vol: 0.9, wet: 0.3 }), sceneTone({ f: 210, f2: 90, d: 0.1, vol: 0.6 }), sceneNoise({ type: "highpass", f: 2e3, d: 0.04, vol: 0.5 })],
|
|
16251
16862
|
"L1360 wood"
|
|
16252
16863
|
),
|
|
16253
|
-
|
|
16864
|
+
// many parts coming loose at once share the `debris_knock` voices (game-audio voice-groups.json)
|
|
16865
|
+
scene("thunk", [sceneNoise({ type: "bandpass", f: 700, q: 2, d: 0.07, vol: 0.4 })], "L1361 thunk", void 0, "debris_knock"),
|
|
16254
16866
|
scene("ping", [sceneTone({ f: [2600, 3500], f2: 1400, d: 0.14, vol: 0.18, wet: 0.3 })], "L1362 ping"),
|
|
16255
16867
|
scene("clang", [sceneTone({ f: [520, 720], f2: 480, d: 0.5, vol: 0.35, wet: 0.4 }), sceneTone({ f: 1370, f2: 1300, d: 0.3, vol: 0.15 }), sceneNoise({ type: "highpass", f: 2e3, d: 0.05, vol: 0.5 })], "L1363 clang"),
|
|
16256
16868
|
scene("chip", [sceneNoise({ type: "bandpass", f: 1600, q: 1.2, d: 0.06, vol: 0.5 })], "L1364 chip"),
|
|
@@ -16262,7 +16874,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
16262
16874
|
// `thud(x, z, v)`: `v` is the `scale` param (default 1)
|
|
16263
16875
|
scene("thud", [sceneTone({ f: 75, f2: 38, d: 0.22, vol: { param: "scale", scale: 0.6 } }), sceneNoise({ f: 420, d: 0.16, vol: { param: "scale", scale: 0.45 } })], "L1358 thud", { scale: 1 }),
|
|
16264
16876
|
// `hiss(x, z, d)`: `seconds` is the param `d` (default 1.2)
|
|
16265
|
-
|
|
16877
|
+
// every melting part shares one `sizzle` voice
|
|
16878
|
+
scene("hiss", [sceneNoise({ type: "highpass", f: 3500, a: 0.05, d: { param: "seconds" }, vol: 0.35 })], "L1366 hiss", { seconds: 1.2 }, "sizzle"),
|
|
16266
16879
|
scene("boom", boomLayers(false), "L1367 boom"),
|
|
16267
16880
|
scene("boom_big", boomLayers(true), "L1367 boom (big)"),
|
|
16268
16881
|
scene("burst", [sceneNoise({ f: 700, f2: 120, d: 0.7, vol: 1.4, wet: 0.5 }), sceneNoise({ type: "highpass", f: 3e3, a: 0.05, d: 1.2, vol: 0.4, delay: 0.1 })], "L1357 burst"),
|
|
@@ -16417,6 +17030,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
16417
17030
|
break_paper: { sound: "paper_tear" },
|
|
16418
17031
|
break_plant: { sound: "branch_cut" }
|
|
16419
17032
|
};
|
|
17033
|
+
var impactSoundIds = (rules = IMPACT_SOUND_KEYS) => {
|
|
17034
|
+
const ids = /* @__PURE__ */ new Set();
|
|
17035
|
+
for (const rule of Object.values(rules)) {
|
|
17036
|
+
ids.add(rule.sound);
|
|
17037
|
+
if (rule.above) ids.add(rule.above.sound);
|
|
17038
|
+
if (rule.fallback) ids.add(rule.fallback);
|
|
17039
|
+
}
|
|
17040
|
+
return [...ids];
|
|
17041
|
+
};
|
|
16420
17042
|
var DESTRUCTION_SOUND_KEYS = {
|
|
16421
17043
|
art_scene: {
|
|
16422
17044
|
impact_metal: { sound: "clang" },
|