@adatechnology/game-destruction-three 0.15.3 → 0.16.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.global.js +729 -67
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +411 -17
- package/dist/index.js.map +1 -1
- package/package.json +6 -6
package/dist/index.global.js
CHANGED
|
@@ -100,6 +100,7 @@ var GameDestructionThree = (() => {
|
|
|
100
100
|
|
|
101
101
|
// ../game-contracts/dist/index.js
|
|
102
102
|
var DEFAULT_BLAST_SHOVE = { coupling: 0.02, maxSpeed: 20, lift: 0.3, minSpeed: 0.3, falloffPower: 2 };
|
|
103
|
+
var clampShare = (share) => Math.min(1, Math.max(0, share));
|
|
103
104
|
function blastShove(blast3, target, options = {}) {
|
|
104
105
|
const o = { ...DEFAULT_BLAST_SHOVE, ...options };
|
|
105
106
|
const dx = target.position[0] - blast3.point[0];
|
|
@@ -109,7 +110,8 @@ var GameDestructionThree = (() => {
|
|
|
109
110
|
if (!(blast3.radius > 0) || distance > blast3.radius) return void 0;
|
|
110
111
|
const falloff = (1 - distance / blast3.radius) ** o.falloffPower;
|
|
111
112
|
const mass = Math.max(target.mass, 0.05);
|
|
112
|
-
const
|
|
113
|
+
const reach = clampShare(blast3.reach ?? 1);
|
|
114
|
+
const speed = Math.sqrt(reach) * Math.min(o.maxSpeed, Math.sqrt(2 * blast3.energy * o.coupling / mass)) * falloff;
|
|
113
115
|
if (!(speed >= o.minSpeed)) return void 0;
|
|
114
116
|
const direction = distance > 1e-4 ? [dx / distance, dy / distance + o.lift, dz / distance] : [0, 1, 0];
|
|
115
117
|
const length = Math.hypot(direction[0], direction[1], direction[2]);
|
|
@@ -1572,16 +1574,16 @@ var GameDestructionThree = (() => {
|
|
|
1572
1574
|
if (this.spaces.delete(id)) this.list = [...this.spaces.values()];
|
|
1573
1575
|
return;
|
|
1574
1576
|
}
|
|
1575
|
-
const [hx, hy,
|
|
1577
|
+
const [hx, hy, hz2] = enclosure.halfSize;
|
|
1576
1578
|
const space = {
|
|
1577
1579
|
cx: enclosure.center[0],
|
|
1578
1580
|
cy: enclosure.center[1],
|
|
1579
1581
|
cz: enclosure.center[2],
|
|
1580
1582
|
hx: Math.abs(hx),
|
|
1581
1583
|
hy: Math.abs(hy),
|
|
1582
|
-
hz: Math.abs(
|
|
1584
|
+
hz: Math.abs(hz2),
|
|
1583
1585
|
r: rotation(enclosure.orientation),
|
|
1584
|
-
radius: Math.hypot(hx, hy,
|
|
1586
|
+
radius: Math.hypot(hx, hy, hz2),
|
|
1585
1587
|
openings: enclosure.openings ?? [],
|
|
1586
1588
|
leak: Math.max(0, enclosure.leak ?? 0)
|
|
1587
1589
|
};
|
|
@@ -5764,13 +5766,14 @@ var GameDestructionThree = (() => {
|
|
|
5764
5766
|
function composeAfterEffects(context) {
|
|
5765
5767
|
const { core, def, random, x, y, z } = context;
|
|
5766
5768
|
const mark = def.mark;
|
|
5767
|
-
|
|
5769
|
+
const keepsMarks = !core.marklessAt?.(x, z);
|
|
5770
|
+
if (keepsMarks && mark.crater && mark.dent && y < 1.2) {
|
|
5768
5771
|
core.emit({ type: "crater", x, z, radius: mark.crater, depth: mark.dent });
|
|
5769
5772
|
core.trace?.crater?.(x, z, mark.crater, mark.dent);
|
|
5770
5773
|
addCrater(core.marks, core.ground, random, { x, z, radius: mark.crater, depth: mark.dent, marks: core.layers.mark });
|
|
5771
5774
|
if (core.layers.shrapnel) composeRimDebris(context);
|
|
5772
5775
|
}
|
|
5773
|
-
if (mark.scorch && y < 1.2 && core.layers.mark) {
|
|
5776
|
+
if (keepsMarks && mark.scorch && y < 1.2 && core.layers.mark) {
|
|
5774
5777
|
core.trace?.scorch?.(x, z, mark.scorch);
|
|
5775
5778
|
addScorch(core.marks, random, x, z, mark.scorch);
|
|
5776
5779
|
}
|
|
@@ -7479,7 +7482,7 @@ var GameDestructionThree = (() => {
|
|
|
7479
7482
|
const wind = { x: DEFAULT_WIND.x, y: DEFAULT_WIND.y, z: DEFAULT_WIND.z };
|
|
7480
7483
|
if (options.wind) [wind.x, wind.y, wind.z] = options.wind;
|
|
7481
7484
|
const camera2 = { x: 0, y: 1.6, z: 0 };
|
|
7482
|
-
const layers = Object.fromEntries(VFX_LAYERS.map((
|
|
7485
|
+
const layers = Object.fromEntries(VFX_LAYERS.map((layer3) => [layer3, true]));
|
|
7483
7486
|
const ground = options.ground;
|
|
7484
7487
|
const groundAt = options.groundAt ?? (ground ? (x, z) => ground.heightAt(x, z) : () => 0);
|
|
7485
7488
|
const handles = { next: 0, events: 0 };
|
|
@@ -7521,6 +7524,7 @@ var GameDestructionThree = (() => {
|
|
|
7521
7524
|
spawned: { puffs: 0, sparks: 0, chunks: 0, lights: 0 },
|
|
7522
7525
|
clock: { time: 0, lastBlast: -1, lod: 1, forcedLod: null },
|
|
7523
7526
|
groundAt,
|
|
7527
|
+
...options.marklessAt ? { marklessAt: options.marklessAt } : {},
|
|
7524
7528
|
emit: (request) => options.onRequest?.(request)
|
|
7525
7529
|
};
|
|
7526
7530
|
function definitionOf(id) {
|
|
@@ -7547,6 +7551,7 @@ var GameDestructionThree = (() => {
|
|
|
7547
7551
|
return player ?? (player = createEffectPlayer({ engine: workshop(), effects, view }));
|
|
7548
7552
|
}
|
|
7549
7553
|
return {
|
|
7554
|
+
hostGroundAt: options.groundAt,
|
|
7550
7555
|
play(id, point, playOptions = {}) {
|
|
7551
7556
|
if (!(id in effects)) return null;
|
|
7552
7557
|
const seed = playOptions.seed ?? (randomMode === "shared" ? void 0 : Math.imul(++handles.events, AUTO_SEED) + (options.seed ?? 0));
|
|
@@ -7609,10 +7614,10 @@ var GameDestructionThree = (() => {
|
|
|
7609
7614
|
const fires = core.fires;
|
|
7610
7615
|
for (let index = fires.items.length - 1; index >= 0; index--) if (fires.items[index].handle === handle) fires.removeAt(index);
|
|
7611
7616
|
},
|
|
7612
|
-
setLayer(
|
|
7613
|
-
layers[
|
|
7617
|
+
setLayer(layer3, on) {
|
|
7618
|
+
layers[layer3] = on;
|
|
7614
7619
|
},
|
|
7615
|
-
layer: (
|
|
7620
|
+
layer: (layer3) => layers[layer3],
|
|
7616
7621
|
setCamera(position) {
|
|
7617
7622
|
camera2.x = position[0];
|
|
7618
7623
|
camera2.y = position[1];
|
|
@@ -7730,7 +7735,7 @@ var GameDestructionThree = (() => {
|
|
|
7730
7735
|
};
|
|
7731
7736
|
var EFFECT_STEP_SECONDS = 1 / 50;
|
|
7732
7737
|
|
|
7733
|
-
// ../game-vfx-three/dist/chunk-
|
|
7738
|
+
// ../game-vfx-three/dist/chunk-CPRCWTSQ.js
|
|
7734
7739
|
var GLOW_VERTEX = `attribute vec4 aP;attribute vec4 aC;varying vec2 vU;varying vec4 vC;
|
|
7735
7740
|
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
7741
|
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 +8175,11 @@ void main(){
|
|
|
8170
8175
|
const dirt = texture(paint(make, (context, size) => drawDirt(context, size, random)));
|
|
8171
8176
|
return { scorch, dirt };
|
|
8172
8177
|
}
|
|
8173
|
-
var
|
|
8178
|
+
var FLAT_STEPS_PER_METRE = 8;
|
|
8179
|
+
var MAX_FLAT_STEPS = 48;
|
|
8180
|
+
var DUG_STEPS_PER_METRE = 20;
|
|
8181
|
+
var MAX_DUG_STEPS = 64;
|
|
8182
|
+
var FLAT_TOLERANCE = 2e-3;
|
|
8174
8183
|
var MIN_CONFORM_STEPS = 16;
|
|
8175
8184
|
var BLOOD_GROW_RATE = 1.2;
|
|
8176
8185
|
var BLOOD_START_GROWTH = 0.05;
|
|
@@ -8253,9 +8262,21 @@ void main(){
|
|
|
8253
8262
|
spareGeometries.push(oldest.mesh.geometry);
|
|
8254
8263
|
if (spareGeometries.length > MAX_SPARE_GROUND_GEOMETRIES) spareGeometries.shift().dispose();
|
|
8255
8264
|
}
|
|
8265
|
+
const heightAt = options.heightAt ?? ((x, z) => options.ground?.heightAt(x, z) ?? 0);
|
|
8266
|
+
function groundDipsUnder(x, z, radius) {
|
|
8267
|
+
const centre = heightAt(x, z);
|
|
8268
|
+
for (const share of [0.25, 0.5, 0.75]) {
|
|
8269
|
+
for (let ray = 0; ray < 8; ray++) {
|
|
8270
|
+
const angle = ray / 8 * Math.PI * 2;
|
|
8271
|
+
if (Math.abs(heightAt(x + Math.cos(angle) * radius * share, z + Math.sin(angle) * radius * share) - centre) > FLAT_TOLERANCE) return true;
|
|
8272
|
+
}
|
|
8273
|
+
}
|
|
8274
|
+
return false;
|
|
8275
|
+
}
|
|
8256
8276
|
function conform(mark, material) {
|
|
8257
8277
|
const radius = mark.radius;
|
|
8258
|
-
const
|
|
8278
|
+
const dug = options.heightAt !== void 0 && groundDipsUnder(mark.x, mark.z, radius);
|
|
8279
|
+
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
8280
|
if (groundMarks.length >= maxGroundMarks) evictOldestGroundMark();
|
|
8260
8281
|
const geometry = takeSpare((steps + 1) * (steps + 1)) ?? new THREE.PlaneGeometry(2, 2, steps, steps).rotateX(-Math.PI / 2);
|
|
8261
8282
|
const grid = geometry.attributes.position;
|
|
@@ -8267,7 +8288,7 @@ void main(){
|
|
|
8267
8288
|
const localZ = Math.fround(Math.floor(index / (steps + 1)) * cell - 1) * radius;
|
|
8268
8289
|
const worldX = mark.x + localX * cos2 - localZ * sin2;
|
|
8269
8290
|
const worldZ = mark.z + localX * sin2 + localZ * cos2;
|
|
8270
|
-
grid.setXYZ(index, worldX,
|
|
8291
|
+
grid.setXYZ(index, worldX, heightAt(worldX, worldZ) + mark.lift, worldZ);
|
|
8271
8292
|
}
|
|
8272
8293
|
grid.needsUpdate = true;
|
|
8273
8294
|
geometry.computeVertexNormals();
|
|
@@ -8939,7 +8960,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8939
8960
|
decalLayer(THREE, engine.decals),
|
|
8940
8961
|
ringLayer(THREE, engine.rings)
|
|
8941
8962
|
];
|
|
8942
|
-
for (const
|
|
8963
|
+
for (const layer22 of layers) group.add(layer22.object);
|
|
8943
8964
|
const meshes = createMeshLayer(THREE, engine.meshes);
|
|
8944
8965
|
group.add(meshes.group);
|
|
8945
8966
|
const lights = budget ? budgetedLights(engine, lightScale, budget) : legacyLights(THREE, engine, lightScale, group);
|
|
@@ -8947,13 +8968,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
8947
8968
|
group,
|
|
8948
8969
|
sync() {
|
|
8949
8970
|
let drawCalls = 0;
|
|
8950
|
-
for (const
|
|
8971
|
+
for (const layer22 of layers) if (layer22.sync()) drawCalls++;
|
|
8951
8972
|
drawCalls += meshes.sync();
|
|
8952
8973
|
lights.sync();
|
|
8953
8974
|
return drawCalls;
|
|
8954
8975
|
},
|
|
8955
8976
|
dispose() {
|
|
8956
|
-
for (const
|
|
8977
|
+
for (const layer22 of layers) layer22.dispose();
|
|
8957
8978
|
meshes.dispose();
|
|
8958
8979
|
lights.dispose();
|
|
8959
8980
|
group.clear();
|
|
@@ -9057,7 +9078,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9057
9078
|
const shocks = createShockLayer(THREE, pools.shocks);
|
|
9058
9079
|
const sprites = createSpriteLayer(THREE, world.sprites(), options.pointScale ?? 700);
|
|
9059
9080
|
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 });
|
|
9081
|
+
const marks = createMarkLayer(THREE, { ground: pools.ground, ...world.hostGroundAt ? { heightAt: world.hostGroundAt } : {}, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
|
|
9061
9082
|
root.add(puffs.mesh, sparks.mesh, glows.mesh, chunks.rocks, chunks.plates, shocks.group, lights.group, sprites.additive, sprites.normal, marks.group);
|
|
9062
9083
|
const stopMarks = world.onMarks((event) => marks.handle(event));
|
|
9063
9084
|
const overlay = options.overlay ? createScreenOverlay(options.overlay) : void 0;
|
|
@@ -9190,7 +9211,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9190
9211
|
stopMarks();
|
|
9191
9212
|
overlay?.dispose();
|
|
9192
9213
|
workshopLayers?.dispose();
|
|
9193
|
-
for (const
|
|
9214
|
+
for (const layer22 of [puffs, sparks, glows, chunks, shocks, lights, marks, sprites]) layer22.dispose();
|
|
9194
9215
|
rockGeometry.dispose();
|
|
9195
9216
|
plateGeometry.dispose();
|
|
9196
9217
|
options.scene.remove(root);
|
|
@@ -9274,13 +9295,154 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9274
9295
|
return r << 16 | g << 8 | bl;
|
|
9275
9296
|
}
|
|
9276
9297
|
|
|
9298
|
+
// src/fragment-shapes.ts
|
|
9299
|
+
var FRAGMENT_SHAPE_VARIANTS = 6;
|
|
9300
|
+
var SLIVER_STATIONS = 6;
|
|
9301
|
+
var SLIVER_FRAY = 0.3;
|
|
9302
|
+
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]];
|
|
9303
|
+
var sliverWidth = (t) => t < 0.25 ? 0.6 + 1.6 * t : t > 0.5 ? Math.max(0, (1 - t) / 0.5) ** 0.85 : 1;
|
|
9304
|
+
var pushTriangle = (out, a, b, c) => {
|
|
9305
|
+
out.push(a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2]);
|
|
9306
|
+
};
|
|
9307
|
+
var faceted = (THREE, positions) => {
|
|
9308
|
+
const geometry = new THREE.BufferGeometry();
|
|
9309
|
+
geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
|
|
9310
|
+
geometry.computeVertexNormals();
|
|
9311
|
+
return geometry;
|
|
9312
|
+
};
|
|
9313
|
+
function buildSliverGeometry(THREE, seed) {
|
|
9314
|
+
const rnd2 = createRng(7e3 + seed);
|
|
9315
|
+
const rings = [];
|
|
9316
|
+
const bendY = (rnd2() - 0.5) * 0.3;
|
|
9317
|
+
const bendZ = (rnd2() - 0.5) * 0.3;
|
|
9318
|
+
for (let i = 0; i < SLIVER_STATIONS - 1; i++) {
|
|
9319
|
+
const t = i / (SLIVER_STATIONS - 1);
|
|
9320
|
+
const x = -0.5 + t + (i > 0 ? (rnd2() - 0.5) * 0.08 : 0);
|
|
9321
|
+
const width = sliverWidth(t);
|
|
9322
|
+
const lift = Math.sin(t * Math.PI);
|
|
9323
|
+
rings.push(SLIVER_SECTION.map(([y, z]) => {
|
|
9324
|
+
const fray = 1 - SLIVER_FRAY + SLIVER_FRAY * 1.4 * rnd2();
|
|
9325
|
+
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))];
|
|
9326
|
+
}));
|
|
9327
|
+
}
|
|
9328
|
+
const tip = [0.5, (rnd2() - 0.5) * 0.5, (rnd2() - 0.5) * 0.3];
|
|
9329
|
+
const torn = [-0.5 + rnd2() * 0.12, (rnd2() - 0.5) * 0.2, (rnd2() - 0.5) * 0.2];
|
|
9330
|
+
const sides = SLIVER_SECTION.length;
|
|
9331
|
+
const positions = [];
|
|
9332
|
+
for (let k = 0; k < sides; k++) {
|
|
9333
|
+
const next = (k + 1) % sides;
|
|
9334
|
+
pushTriangle(positions, torn, rings[0][next], rings[0][k]);
|
|
9335
|
+
for (let i = 0; i < rings.length - 1; i++) {
|
|
9336
|
+
const a = rings[i][k], b = rings[i][next], c = rings[i + 1][next], d = rings[i + 1][k];
|
|
9337
|
+
pushTriangle(positions, a, b, c);
|
|
9338
|
+
pushTriangle(positions, a, c, d);
|
|
9339
|
+
}
|
|
9340
|
+
const last = rings[rings.length - 1];
|
|
9341
|
+
pushTriangle(positions, last[k], last[next], tip);
|
|
9342
|
+
}
|
|
9343
|
+
return faceted(THREE, positions);
|
|
9344
|
+
}
|
|
9345
|
+
function buildLumpGeometry(THREE, seed) {
|
|
9346
|
+
const rnd2 = createRng(8e3 + seed);
|
|
9347
|
+
const base = new THREE.IcosahedronGeometry(0.5, 0);
|
|
9348
|
+
const source = base.getAttribute("position");
|
|
9349
|
+
const pushOf = /* @__PURE__ */ new Map();
|
|
9350
|
+
const positions = [];
|
|
9351
|
+
for (let i = 0; i < source.count; i++) {
|
|
9352
|
+
const x = source.getX(i), y = source.getY(i), z = source.getZ(i);
|
|
9353
|
+
const key = `${x.toFixed(3)},${y.toFixed(3)},${z.toFixed(3)}`;
|
|
9354
|
+
let push = pushOf.get(key);
|
|
9355
|
+
if (push === void 0) {
|
|
9356
|
+
push = 0.6 + 0.55 * rnd2();
|
|
9357
|
+
pushOf.set(key, push);
|
|
9358
|
+
}
|
|
9359
|
+
positions.push(x * push, y * push, z * push);
|
|
9360
|
+
}
|
|
9361
|
+
base.dispose();
|
|
9362
|
+
const extent = [0, 0, 0];
|
|
9363
|
+
for (let i = 0; i < positions.length; i++) extent[i % 3] = Math.max(extent[i % 3], Math.abs(positions[i]));
|
|
9364
|
+
for (let i = 0; i < positions.length; i++) positions[i] *= 0.5 / Math.max(extent[i % 3], 1e-3);
|
|
9365
|
+
return faceted(THREE, positions);
|
|
9366
|
+
}
|
|
9367
|
+
var VENEER_EDGE = 11;
|
|
9368
|
+
function buildVeneerGeometry(THREE, seed) {
|
|
9369
|
+
const rnd2 = createRng(9e3 + seed);
|
|
9370
|
+
const shape = new THREE.Shape();
|
|
9371
|
+
const outline = [];
|
|
9372
|
+
const edge = (y, inward) => y - inward * (rnd2() * 0.16 + (rnd2() < 0.15 ? 0.12 : 0));
|
|
9373
|
+
for (let i = 0; i <= VENEER_EDGE; i++) outline.push([-0.5 + i / VENEER_EDGE, edge(0.5, 1)]);
|
|
9374
|
+
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()]);
|
|
9375
|
+
for (let i = VENEER_EDGE; i >= 0; i--) outline.push([-0.5 + i / VENEER_EDGE, edge(-0.5, -1)]);
|
|
9376
|
+
outline.push([-0.5 + rnd2() * 0.12, -0.2 * rnd2()], [-0.5, 0.1 * rnd2()], [-0.5 + rnd2() * 0.12, 0.25 * rnd2()]);
|
|
9377
|
+
shape.moveTo(outline[0][0], outline[0][1]);
|
|
9378
|
+
for (let i = 1; i < outline.length; i++) shape.lineTo(outline[i][0], outline[i][1]);
|
|
9379
|
+
shape.closePath();
|
|
9380
|
+
const geometry = new THREE.ExtrudeGeometry(shape, { depth: 1, bevelEnabled: false });
|
|
9381
|
+
geometry.translate(0, 0, -0.5);
|
|
9382
|
+
return geometry;
|
|
9383
|
+
}
|
|
9384
|
+
var TORN_END = 0.24;
|
|
9385
|
+
var TORN_STRANDS = [4, 7];
|
|
9386
|
+
var pushQuad = (out, quad) => {
|
|
9387
|
+
pushTriangle(out, quad[0], quad[1], quad[2]);
|
|
9388
|
+
pushTriangle(out, quad[0], quad[2], quad[3]);
|
|
9389
|
+
};
|
|
9390
|
+
var unevenCuts = (count, rnd2) => {
|
|
9391
|
+
const shares = Array.from({ length: count }, () => 0.35 + 1.65 * rnd2());
|
|
9392
|
+
const total = shares.reduce((sum, share) => sum + share, 0);
|
|
9393
|
+
const cuts = [-0.5];
|
|
9394
|
+
for (const share of shares) cuts.push(cuts[cuts.length - 1] + share / total);
|
|
9395
|
+
cuts[cuts.length - 1] = 0.5;
|
|
9396
|
+
return cuts;
|
|
9397
|
+
};
|
|
9398
|
+
function buildBrokenBoardGeometry(THREE, seed) {
|
|
9399
|
+
const rnd2 = createRng(1e4 + seed);
|
|
9400
|
+
const end = 0.5 - TORN_END;
|
|
9401
|
+
const positions = [];
|
|
9402
|
+
const lo = -0.5, hi = 0.5;
|
|
9403
|
+
pushQuad(positions, [[lo, lo, hi], [end, lo, hi], [end, hi, hi], [lo, hi, hi]]);
|
|
9404
|
+
pushQuad(positions, [[lo, hi, lo], [end, hi, lo], [end, lo, lo], [lo, lo, lo]]);
|
|
9405
|
+
pushQuad(positions, [[lo, hi, hi], [end, hi, hi], [end, hi, lo], [lo, hi, lo]]);
|
|
9406
|
+
pushQuad(positions, [[lo, lo, lo], [end, lo, lo], [end, lo, hi], [lo, lo, hi]]);
|
|
9407
|
+
pushQuad(positions, [[lo, lo, lo], [lo, lo, hi], [lo, hi, hi], [lo, hi, lo]]);
|
|
9408
|
+
const count = TORN_STRANDS[0] + Math.floor(rnd2() * (TORN_STRANDS[1] - TORN_STRANDS[0] + 1));
|
|
9409
|
+
const cuts = unevenCuts(count, rnd2);
|
|
9410
|
+
for (let k = 0; k < count; k++) {
|
|
9411
|
+
const y0 = cuts[k], y1 = cuts[k + 1];
|
|
9412
|
+
const length = TORN_END * (0.15 + 0.85 * rnd2());
|
|
9413
|
+
const midX = end + length * (0.35 + 0.3 * rnd2());
|
|
9414
|
+
const narrow = 0.55 + 0.35 * rnd2();
|
|
9415
|
+
const shiftY = (rnd2() - 0.5) * (y1 - y0) * 0.6;
|
|
9416
|
+
const shiftZ = (rnd2() - 0.5) * 0.5;
|
|
9417
|
+
const cy = (y0 + y1) / 2 + shiftY, hy = (y1 - y0) / 2 * narrow, hz2 = 0.5 * (0.4 + 0.5 * rnd2());
|
|
9418
|
+
const base = [[end, y0, lo], [end, y1, lo], [end, y1, hi], [end, y0, hi]];
|
|
9419
|
+
const clampZ = (z) => Math.max(lo, Math.min(hi, z));
|
|
9420
|
+
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)]];
|
|
9421
|
+
const tip = [end + length, y0 + (y1 - y0) * rnd2(), clampZ(shiftZ + (rnd2() - 0.5) * 0.4)];
|
|
9422
|
+
for (let i = 0; i < 4; i++) {
|
|
9423
|
+
const next = (i + 1) % 4;
|
|
9424
|
+
pushQuad(positions, [base[i], base[next], mid[next], mid[i]]);
|
|
9425
|
+
pushTriangle(positions, mid[i], mid[next], tip);
|
|
9426
|
+
}
|
|
9427
|
+
}
|
|
9428
|
+
return faceted(THREE, positions);
|
|
9429
|
+
}
|
|
9430
|
+
|
|
9277
9431
|
// src/fragments.ts
|
|
9432
|
+
var BUILDERS = {
|
|
9433
|
+
sliver: buildSliverGeometry,
|
|
9434
|
+
lump: buildLumpGeometry,
|
|
9435
|
+
veneer: buildVeneerGeometry,
|
|
9436
|
+
board: buildBrokenBoardGeometry
|
|
9437
|
+
};
|
|
9278
9438
|
var SHEET_MAX = 4e-3;
|
|
9279
9439
|
var SHEET_AIR = 2.5;
|
|
9280
9440
|
var SHEET_SWAY = 3;
|
|
9281
9441
|
var BIG_LENGTH = 0.2;
|
|
9282
9442
|
var BIG_WIDTH = 0.03;
|
|
9283
9443
|
var FRAGMENT_SEED = 1374496523;
|
|
9444
|
+
var SUPPORT_CHECKS = 32;
|
|
9445
|
+
var SUPPORT_GAP = 0.02;
|
|
9284
9446
|
var SHARD_SHAPES = 6;
|
|
9285
9447
|
var isBig = (size) => {
|
|
9286
9448
|
const [, middle, longest] = [...size].sort((a, b) => a - b);
|
|
@@ -9295,6 +9457,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9295
9457
|
/** The mesh box of a fragment before its scale. */
|
|
9296
9458
|
__publicField(this, "unitBox", { min: { x: -0.5, y: -0.5, z: -0.5 }, max: { x: 0.5, y: 0.5, z: 0.5 } });
|
|
9297
9459
|
__publicField(this, "shardGeos", []);
|
|
9460
|
+
__publicField(this, "shapeGeos", /* @__PURE__ */ new Map());
|
|
9298
9461
|
__publicField(this, "materials", /* @__PURE__ */ new Map());
|
|
9299
9462
|
__publicField(this, "q");
|
|
9300
9463
|
__publicField(this, "dq");
|
|
@@ -9304,6 +9467,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9304
9467
|
__publicField(this, "rng", createRng(FRAGMENT_SEED));
|
|
9305
9468
|
/** Count of `update` calls: what makes the fragments of one burst tell apart from older ones. */
|
|
9306
9469
|
__publicField(this, "tick", 0);
|
|
9470
|
+
__publicField(this, "supportTurn", 0);
|
|
9307
9471
|
__publicField(this, "markGeometry", null);
|
|
9308
9472
|
__publicField(this, "markMaterial", null);
|
|
9309
9473
|
this.box = new THREE.BoxGeometry(1, 1, 1);
|
|
@@ -9352,21 +9516,54 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9352
9516
|
}
|
|
9353
9517
|
return g;
|
|
9354
9518
|
}
|
|
9519
|
+
sharedGeometry(shape, seed) {
|
|
9520
|
+
const idx = seed % FRAGMENT_SHAPE_VARIANTS;
|
|
9521
|
+
let variants = this.shapeGeos.get(shape);
|
|
9522
|
+
if (!variants) {
|
|
9523
|
+
variants = [];
|
|
9524
|
+
this.shapeGeos.set(shape, variants);
|
|
9525
|
+
}
|
|
9526
|
+
let g = variants[idx];
|
|
9527
|
+
if (!g) {
|
|
9528
|
+
g = BUILDERS[shape](this.THREE, idx);
|
|
9529
|
+
variants[idx] = g;
|
|
9530
|
+
}
|
|
9531
|
+
return g;
|
|
9532
|
+
}
|
|
9533
|
+
/**
|
|
9534
|
+
* 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
|
|
9535
|
+
* board ends in torn strands at +x; the rest are boxes.
|
|
9536
|
+
*/
|
|
9537
|
+
geometryOf(f) {
|
|
9538
|
+
const seed = hashString(f.id);
|
|
9539
|
+
if (f.shape === "shard") return this.shardGeometry(seed);
|
|
9540
|
+
if (f.shape === "splinter") return this.sharedGeometry("sliver", seed);
|
|
9541
|
+
const form = this.o.formOf?.(f.material);
|
|
9542
|
+
if (f.shape === "chunk" && form === "granules") return this.sharedGeometry("lump", seed);
|
|
9543
|
+
if (f.shape === "scrap" && form === "plies") return this.sharedGeometry("veneer", seed);
|
|
9544
|
+
if (f.shape === "slab" && (form === "slivers" || form === "plies")) return this.sharedGeometry("board", seed);
|
|
9545
|
+
return this.box;
|
|
9546
|
+
}
|
|
9355
9547
|
markParts() {
|
|
9356
9548
|
this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
|
|
9357
9549
|
this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
|
|
9358
9550
|
return { geometry: this.markGeometry, material: this.markMaterial };
|
|
9359
9551
|
}
|
|
9360
9552
|
/**
|
|
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
|
|
9553
|
+
* 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
9554
|
* glass materials. A host draws them at load so the first blast does not upload these buffers in the middle of a frame.
|
|
9363
9555
|
*/
|
|
9364
9556
|
warmMeshes() {
|
|
9365
9557
|
const opaque = this.materialFor("warm", 8421504, false);
|
|
9366
9558
|
const glass2 = this.materialFor("warm", 8421504, true);
|
|
9367
9559
|
const geometries = [this.box, ...Array.from({ length: SHARD_SHAPES }, (_, index) => this.shardGeometry(index))];
|
|
9560
|
+
const wood = ["sliver", "lump", "veneer", "board"].flatMap((shape) => Array.from({ length: FRAGMENT_SHAPE_VARIANTS }, (_, index) => this.sharedGeometry(shape, index)));
|
|
9368
9561
|
const { geometry, material } = this.markParts();
|
|
9369
|
-
return [
|
|
9562
|
+
return [
|
|
9563
|
+
...geometries.flatMap((shared) => [new this.THREE.Mesh(shared, opaque), new this.THREE.Mesh(shared, glass2)]),
|
|
9564
|
+
...wood.map((shared) => new this.THREE.Mesh(shared, opaque)),
|
|
9565
|
+
new this.THREE.Mesh(geometry, material)
|
|
9566
|
+
];
|
|
9370
9567
|
}
|
|
9371
9568
|
/**
|
|
9372
9569
|
* 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 +9588,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9391
9588
|
* 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
9589
|
* out. Returns null when the fragment was not made.
|
|
9393
9590
|
*/
|
|
9394
|
-
|
|
9591
|
+
/**
|
|
9592
|
+
* 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
|
|
9593
|
+
* stays: it has a body the player and the pieces run into, however thin it is, and it never fades.
|
|
9594
|
+
*/
|
|
9595
|
+
spawn(f, material, home, maxAllowed, lasting = false) {
|
|
9395
9596
|
if (this.list.length >= maxAllowed) {
|
|
9396
9597
|
let alive = 0;
|
|
9397
9598
|
let oldest;
|
|
@@ -9408,7 +9609,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9408
9609
|
oldest.dying = true;
|
|
9409
9610
|
}
|
|
9410
9611
|
}
|
|
9411
|
-
const geo =
|
|
9612
|
+
const geo = this.geometryOf(f);
|
|
9412
9613
|
const mesh2 = new this.THREE.Mesh(geo, material);
|
|
9413
9614
|
mesh2.position.set(f.center[0], f.center[1], f.center[2]);
|
|
9414
9615
|
mesh2.quaternion.set(f.rotation[0], f.rotation[1], f.rotation[2], f.rotation[3]).normalize();
|
|
@@ -9436,9 +9637,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9436
9637
|
air: f.shape === "scrap" && sz[2] <= SHEET_MAX ? SHEET_AIR : 0,
|
|
9437
9638
|
body: null,
|
|
9438
9639
|
born: this.tick,
|
|
9439
|
-
life: Math.max(0, typeof this.o.life === "function" ? this.o.life(f, home) : this.o.life)
|
|
9640
|
+
life: lasting ? 0 : Math.max(0, typeof this.o.life === "function" ? this.o.life(f, home) : this.o.life)
|
|
9440
9641
|
};
|
|
9441
|
-
if (f.shape !== "shard" && isBig(f.size)) {
|
|
9642
|
+
if (lasting || f.shape !== "shard" && isBig(f.size)) {
|
|
9442
9643
|
live.body = {
|
|
9443
9644
|
mass: f.mass,
|
|
9444
9645
|
push: (x, z) => {
|
|
@@ -9498,6 +9699,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9498
9699
|
}
|
|
9499
9700
|
update(dt) {
|
|
9500
9701
|
this.tick++;
|
|
9702
|
+
this.wakeUnsupported();
|
|
9501
9703
|
const g = this.o.gravity;
|
|
9502
9704
|
for (let i = this.list.length - 1; i >= 0; i--) {
|
|
9503
9705
|
const f = this.list[i];
|
|
@@ -9522,6 +9724,18 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9522
9724
|
this.step(f, dt, g);
|
|
9523
9725
|
}
|
|
9524
9726
|
}
|
|
9727
|
+
/** A few fragments asleep each update (`SUPPORT_CHECKS`, in turn) look at the ground under them again: one left over air falls. */
|
|
9728
|
+
wakeUnsupported() {
|
|
9729
|
+
const n = this.list.length;
|
|
9730
|
+
for (let k = 0; k < Math.min(SUPPORT_CHECKS, n); k++) {
|
|
9731
|
+
this.supportTurn = (this.supportTurn + 1) % n;
|
|
9732
|
+
const f = this.list[this.supportTurn];
|
|
9733
|
+
if (!f.asleep || f.dying) continue;
|
|
9734
|
+
const e = this.m.makeRotationFromQuaternion(f.mesh.quaternion).elements;
|
|
9735
|
+
const ext = Math.abs(e[1]) * f.half.x + Math.abs(e[5]) * f.half.y + Math.abs(e[9]) * f.half.z;
|
|
9736
|
+
if (f.mesh.position.y - ext > this.o.groundAt(f.mesh.position.x, f.mesh.position.z) + SUPPORT_GAP) this.rouse(f);
|
|
9737
|
+
}
|
|
9738
|
+
}
|
|
9525
9739
|
step(f, dt, g) {
|
|
9526
9740
|
const mesh2 = f.mesh;
|
|
9527
9741
|
f.vel.y -= g * dt;
|
|
@@ -9629,6 +9843,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9629
9843
|
this.markMaterial?.dispose();
|
|
9630
9844
|
for (const g of this.shardGeos) g?.dispose();
|
|
9631
9845
|
this.shardGeos = [];
|
|
9846
|
+
for (const variants of this.shapeGeos.values()) for (const g of variants) g?.dispose();
|
|
9847
|
+
this.shapeGeos.clear();
|
|
9632
9848
|
for (const m of this.materials.values()) m.dispose();
|
|
9633
9849
|
this.materials.clear();
|
|
9634
9850
|
}
|
|
@@ -9738,6 +9954,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9738
9954
|
var SOLVER_PASSES = 10;
|
|
9739
9955
|
var SIDE_BOUNCE = 0.1;
|
|
9740
9956
|
var EDGE_POINTS = [-0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75];
|
|
9957
|
+
var SUPPORT_CHECKS2 = 16;
|
|
9958
|
+
var SUPPORT_GAP2 = 0.02;
|
|
9741
9959
|
var boxTopAt = (s, x, z) => {
|
|
9742
9960
|
const e = s.e;
|
|
9743
9961
|
const dx = x - s.c[0], dz = z - s.c[2];
|
|
@@ -9770,6 +9988,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9770
9988
|
__publicField(this, "m");
|
|
9771
9989
|
__publicField(this, "ax");
|
|
9772
9990
|
__publicField(this, "solids", []);
|
|
9991
|
+
__publicField(this, "supportTurn", 0);
|
|
9773
9992
|
this.p = new THREE.Vector3();
|
|
9774
9993
|
this.q = new THREE.Quaternion();
|
|
9775
9994
|
this.dq = new THREE.Quaternion();
|
|
@@ -9865,10 +10084,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9865
10084
|
return this.mulInvInertiaWith(b, this.m.elements, x, y, z);
|
|
9866
10085
|
}
|
|
9867
10086
|
mulInvInertiaWith(b, e, x, y, z) {
|
|
9868
|
-
const [hx, hy,
|
|
10087
|
+
const [hx, hy, hz2] = b.halfExtents;
|
|
9869
10088
|
const k = 3 / b.mass;
|
|
9870
|
-
const ix = k / (hy * hy +
|
|
9871
|
-
const iy = k / (hx * hx +
|
|
10089
|
+
const ix = k / (hy * hy + hz2 * hz2);
|
|
10090
|
+
const iy = k / (hx * hx + hz2 * hz2);
|
|
9872
10091
|
const iz = k / (hx * hx + hy * hy);
|
|
9873
10092
|
const lx = (e[0] * x + e[1] * y + e[2] * z) * ix;
|
|
9874
10093
|
const ly = (e[4] * x + e[5] * y + e[6] * z) * iy;
|
|
@@ -9877,11 +10096,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9877
10096
|
}
|
|
9878
10097
|
/** 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
10098
|
samples(b, edges) {
|
|
9880
|
-
const [hx, hy,
|
|
10099
|
+
const [hx, hy, hz2] = b.halfExtents;
|
|
9881
10100
|
const out = [];
|
|
9882
|
-
for (let i = 0; i < 8; i++) out.push([i & 1 ? hx : -hx, i & 2 ? hy : -hy, i & 4 ?
|
|
10101
|
+
for (let i = 0; i < 8; i++) out.push([i & 1 ? hx : -hx, i & 2 ? hy : -hy, i & 4 ? hz2 : -hz2]);
|
|
9883
10102
|
if (!edges) return out;
|
|
9884
|
-
const h = [hx, hy,
|
|
10103
|
+
const h = [hx, hy, hz2];
|
|
9885
10104
|
for (let axis2 = 0; axis2 < 3; axis2++) {
|
|
9886
10105
|
const a1 = (axis2 + 1) % 3;
|
|
9887
10106
|
const a2 = (axis2 + 2) % 3;
|
|
@@ -9929,6 +10148,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9929
10148
|
const h = body.halfExtents;
|
|
9930
10149
|
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
10150
|
});
|
|
10151
|
+
this.wakeUnsupported();
|
|
9932
10152
|
for (const b of this.list) {
|
|
9933
10153
|
if (b.held) {
|
|
9934
10154
|
b.velocity = [0, 0, 0];
|
|
@@ -9943,6 +10163,33 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
9943
10163
|
}
|
|
9944
10164
|
this.separate(dt);
|
|
9945
10165
|
}
|
|
10166
|
+
/** A few sleeping pieces each update (`SUPPORT_CHECKS`, in turn) look under them again: one whose table broke, or whose shelf fell over, falls. */
|
|
10167
|
+
wakeUnsupported() {
|
|
10168
|
+
const n = this.list.length;
|
|
10169
|
+
for (let k = 0; k < Math.min(SUPPORT_CHECKS2, n); k++) {
|
|
10170
|
+
this.supportTurn = (this.supportTurn + 1) % n;
|
|
10171
|
+
const b = this.list[this.supportTurn];
|
|
10172
|
+
if (!b.sleeping || b.held || this.supported(b, false) || this.supported(b, true)) continue;
|
|
10173
|
+
b.sleeping = false;
|
|
10174
|
+
b.still = 0;
|
|
10175
|
+
b.calmAt = null;
|
|
10176
|
+
}
|
|
10177
|
+
}
|
|
10178
|
+
/**
|
|
10179
|
+
* 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:
|
|
10180
|
+
* a book propped on a fallen case rests by a corner high on it while its lowest one hangs beside it.
|
|
10181
|
+
*/
|
|
10182
|
+
supported(b, edges) {
|
|
10183
|
+
const c = this.centerOf(b, this.p);
|
|
10184
|
+
const e = this.m.makeRotationFromQuaternion(b.root.quaternion).elements;
|
|
10185
|
+
for (const [sx, sy, sz] of this.samples(b, edges)) {
|
|
10186
|
+
const y = c.y + e[1] * sx + e[5] * sy + e[9] * sz;
|
|
10187
|
+
const x = c.x + e[0] * sx + e[4] * sy + e[8] * sz;
|
|
10188
|
+
const z = c.z + e[2] * sx + e[6] * sy + e[10] * sz;
|
|
10189
|
+
if (this.supportAt(b, x, y, z, STEP) >= y - SUPPORT_GAP2) return true;
|
|
10190
|
+
}
|
|
10191
|
+
return false;
|
|
10192
|
+
}
|
|
9946
10193
|
/** Where a corner of `self` at `(x, y, z)` lands: the ground, the top of another piece or of something fixed under it. */
|
|
9947
10194
|
supportAt(self, x, y, z, step) {
|
|
9948
10195
|
let top = this.o.groundAt(x, z);
|
|
@@ -10202,8 +10449,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10202
10449
|
unstable = Math.min(wx, wz) < NARROW * height || x0 > 0 || x1 < 0 || z0 > 0 || z1 < 0;
|
|
10203
10450
|
if (b.tipping) {
|
|
10204
10451
|
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]) *
|
|
10452
|
+
const [hx, hy, hz2] = b.halfExtents;
|
|
10453
|
+
const upHalf = Math.abs(e[1]) * hx + Math.abs(e[5]) * hy + Math.abs(e[9]) * hz2;
|
|
10207
10454
|
if (upHalf < TIP_LYING || b.tipping.seconds <= 0) b.tipping = null;
|
|
10208
10455
|
else {
|
|
10209
10456
|
w[0] += b.tipping.x * TIP_ACCELERATION * h;
|
|
@@ -10316,6 +10563,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10316
10563
|
constructor(THREE, scene2, radius = 4e-3) {
|
|
10317
10564
|
__publicField(this, "THREE", THREE);
|
|
10318
10565
|
__publicField(this, "scene", scene2);
|
|
10566
|
+
__publicField(this, "radius", radius);
|
|
10319
10567
|
__publicField(this, "cages", /* @__PURE__ */ new Map());
|
|
10320
10568
|
__publicField(this, "geometry");
|
|
10321
10569
|
__publicField(this, "material");
|
|
@@ -10344,28 +10592,62 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10344
10592
|
meshes() {
|
|
10345
10593
|
return [...this.cages.values()];
|
|
10346
10594
|
}
|
|
10347
|
-
/**
|
|
10348
|
-
|
|
10595
|
+
/**
|
|
10596
|
+
* 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
|
|
10597
|
+
* by default), so it goes where the part goes: a beam that comes down takes its bare bars with it.
|
|
10598
|
+
*/
|
|
10599
|
+
set(key, bars, holder) {
|
|
10349
10600
|
this.remove(key);
|
|
10350
10601
|
if (bars.length === 0) return;
|
|
10602
|
+
const parent = holder ?? this.scene;
|
|
10603
|
+
parent.updateWorldMatrix(true, false);
|
|
10604
|
+
const toParent = new this.THREE.Matrix4().copy(parent.matrixWorld).invert();
|
|
10351
10605
|
const mesh2 = new this.THREE.InstancedMesh(this.geometry, this.material, bars.length);
|
|
10352
10606
|
bars.forEach(([a, b], i) => {
|
|
10353
10607
|
this.dir.set(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
|
|
10354
10608
|
const length = this.dir.length();
|
|
10355
10609
|
this.mid.set((a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2);
|
|
10356
10610
|
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));
|
|
10611
|
+
this.m.compose(this.mid, this.q, this.scale.set(1, Math.max(length, 1e-4), 1)).premultiply(toParent);
|
|
10358
10612
|
mesh2.setMatrixAt(i, this.m);
|
|
10359
10613
|
});
|
|
10360
10614
|
mesh2.instanceMatrix.needsUpdate = true;
|
|
10361
10615
|
mesh2.castShadow = true;
|
|
10362
|
-
|
|
10616
|
+
mesh2.name = "rebar-cage";
|
|
10617
|
+
parent.add(mesh2);
|
|
10363
10618
|
this.cages.set(key, mesh2);
|
|
10364
10619
|
}
|
|
10620
|
+
/** 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. */
|
|
10621
|
+
move(from, to, holder) {
|
|
10622
|
+
const mesh2 = this.cages.get(from);
|
|
10623
|
+
if (!mesh2) return;
|
|
10624
|
+
this.cages.delete(from);
|
|
10625
|
+
this.remove(to);
|
|
10626
|
+
holder.attach(mesh2);
|
|
10627
|
+
this.cages.set(to, mesh2);
|
|
10628
|
+
}
|
|
10629
|
+
/** 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. */
|
|
10630
|
+
take(key) {
|
|
10631
|
+
const mesh2 = this.cages.get(key);
|
|
10632
|
+
if (!mesh2) return [];
|
|
10633
|
+
mesh2.updateWorldMatrix(true, false);
|
|
10634
|
+
const bars = [];
|
|
10635
|
+
for (let i = 0; i < mesh2.count; i++) {
|
|
10636
|
+
mesh2.getMatrixAt(i, this.m);
|
|
10637
|
+
this.m.premultiply(mesh2.matrixWorld).decompose(this.mid, this.q, this.scale);
|
|
10638
|
+
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 });
|
|
10639
|
+
}
|
|
10640
|
+
this.remove(key);
|
|
10641
|
+
return bars;
|
|
10642
|
+
}
|
|
10643
|
+
/** Radius of the bars drawn, metres. */
|
|
10644
|
+
get barRadius() {
|
|
10645
|
+
return this.radius;
|
|
10646
|
+
}
|
|
10365
10647
|
remove(key) {
|
|
10366
10648
|
const mesh2 = this.cages.get(key);
|
|
10367
10649
|
if (!mesh2) return;
|
|
10368
|
-
|
|
10650
|
+
mesh2.removeFromParent();
|
|
10369
10651
|
mesh2.dispose();
|
|
10370
10652
|
this.cages.delete(key);
|
|
10371
10653
|
}
|
|
@@ -10445,26 +10727,26 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10445
10727
|
return [s.radius * 2, s.radius * 2, s.radius * 2];
|
|
10446
10728
|
};
|
|
10447
10729
|
var aabbOf = (s, center, q) => {
|
|
10448
|
-
let hx, hy,
|
|
10730
|
+
let hx, hy, hz2;
|
|
10449
10731
|
if (s.kind === "sphere") {
|
|
10450
|
-
hx = hy =
|
|
10732
|
+
hx = hy = hz2 = s.radius;
|
|
10451
10733
|
} else {
|
|
10452
10734
|
const m = quat.toMatrix(q);
|
|
10453
10735
|
if (s.kind === "box") {
|
|
10454
10736
|
const a = s.size[0] / 2, b = s.size[1] / 2, c = s.size[2] / 2;
|
|
10455
10737
|
hx = Math.abs(m[0]) * a + Math.abs(m[1]) * b + Math.abs(m[2]) * c;
|
|
10456
10738
|
hy = Math.abs(m[3]) * a + Math.abs(m[4]) * b + Math.abs(m[5]) * c;
|
|
10457
|
-
|
|
10739
|
+
hz2 = Math.abs(m[6]) * a + Math.abs(m[7]) * b + Math.abs(m[8]) * c;
|
|
10458
10740
|
} else {
|
|
10459
10741
|
const ax = [m[1], m[4], m[7]];
|
|
10460
10742
|
const hh = s.height / 2;
|
|
10461
10743
|
const e = (i) => s.radius * Math.sqrt(Math.max(0, 1 - ax[i] * ax[i])) + hh * Math.abs(ax[i]);
|
|
10462
10744
|
hx = e(0);
|
|
10463
10745
|
hy = e(1);
|
|
10464
|
-
|
|
10746
|
+
hz2 = e(2);
|
|
10465
10747
|
}
|
|
10466
10748
|
}
|
|
10467
|
-
return { min: [center[0] - hx, center[1] - hy, center[2] -
|
|
10749
|
+
return { min: [center[0] - hx, center[1] - hy, center[2] - hz2], max: [center[0] + hx, center[1] + hy, center[2] + hz2] };
|
|
10468
10750
|
};
|
|
10469
10751
|
var FLEXURE_DEFAULTS = {
|
|
10470
10752
|
enabled: true,
|
|
@@ -10649,6 +10931,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10649
10931
|
},
|
|
10650
10932
|
{
|
|
10651
10933
|
id: "wood_soft",
|
|
10934
|
+
splitForm: "slivers",
|
|
10652
10935
|
density: 500,
|
|
10653
10936
|
hpPerKg: 90,
|
|
10654
10937
|
penetration: 9,
|
|
@@ -10694,6 +10977,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10694
10977
|
},
|
|
10695
10978
|
{
|
|
10696
10979
|
id: "wood_hard",
|
|
10980
|
+
splitForm: "slivers",
|
|
10697
10981
|
density: 750,
|
|
10698
10982
|
hpPerKg: 140,
|
|
10699
10983
|
penetration: 14,
|
|
@@ -10738,6 +11022,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10738
11022
|
},
|
|
10739
11023
|
{
|
|
10740
11024
|
id: "plywood",
|
|
11025
|
+
splitForm: "plies",
|
|
10741
11026
|
density: 600,
|
|
10742
11027
|
hpPerKg: 70,
|
|
10743
11028
|
penetration: 7,
|
|
@@ -10785,6 +11070,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
10785
11070
|
},
|
|
10786
11071
|
{
|
|
10787
11072
|
id: "mdf",
|
|
11073
|
+
splitForm: "granules",
|
|
10788
11074
|
density: 750,
|
|
10789
11075
|
hpPerKg: 45,
|
|
10790
11076
|
penetration: 6,
|
|
@@ -11296,6 +11582,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11296
11582
|
},
|
|
11297
11583
|
{
|
|
11298
11584
|
id: "green_wood",
|
|
11585
|
+
splitForm: "slivers",
|
|
11299
11586
|
density: 650,
|
|
11300
11587
|
hpPerKg: 5,
|
|
11301
11588
|
penetration: 10,
|
|
@@ -11576,6 +11863,125 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11576
11863
|
"brittle"
|
|
11577
11864
|
],
|
|
11578
11865
|
compressive: 1e5
|
|
11866
|
+
},
|
|
11867
|
+
{
|
|
11868
|
+
id: "mud",
|
|
11869
|
+
density: 1700,
|
|
11870
|
+
hpPerKg: 1,
|
|
11871
|
+
penetration: 5,
|
|
11872
|
+
ductility: 0.8,
|
|
11873
|
+
fracture: "crumble",
|
|
11874
|
+
fragmentSize: [
|
|
11875
|
+
0.02,
|
|
11876
|
+
0.08
|
|
11877
|
+
],
|
|
11878
|
+
hitDebris: "dust",
|
|
11879
|
+
breakDebris: "dust",
|
|
11880
|
+
thermal: {
|
|
11881
|
+
conductivity: 0.5,
|
|
11882
|
+
heatCapacity: 3.2
|
|
11883
|
+
},
|
|
11884
|
+
tags: [
|
|
11885
|
+
"soil",
|
|
11886
|
+
"wet"
|
|
11887
|
+
],
|
|
11888
|
+
compressive: 5e3
|
|
11889
|
+
},
|
|
11890
|
+
{
|
|
11891
|
+
id: "clay",
|
|
11892
|
+
density: 1800,
|
|
11893
|
+
hpPerKg: 5,
|
|
11894
|
+
penetration: 7,
|
|
11895
|
+
ductility: 0.05,
|
|
11896
|
+
fracture: "crumble",
|
|
11897
|
+
fragmentSize: [
|
|
11898
|
+
0.02,
|
|
11899
|
+
0.1
|
|
11900
|
+
],
|
|
11901
|
+
hitDebris: "chips",
|
|
11902
|
+
breakDebris: "dust",
|
|
11903
|
+
thermal: {
|
|
11904
|
+
conductivity: 0.15,
|
|
11905
|
+
heatCapacity: 2.2
|
|
11906
|
+
},
|
|
11907
|
+
tags: [
|
|
11908
|
+
"soil",
|
|
11909
|
+
"brittle"
|
|
11910
|
+
],
|
|
11911
|
+
compressive: 6e4
|
|
11912
|
+
},
|
|
11913
|
+
{
|
|
11914
|
+
id: "snow",
|
|
11915
|
+
density: 300,
|
|
11916
|
+
hpPerKg: 1,
|
|
11917
|
+
penetration: 3,
|
|
11918
|
+
ductility: 0.6,
|
|
11919
|
+
fracture: "crumble",
|
|
11920
|
+
fragmentSize: [
|
|
11921
|
+
0.01,
|
|
11922
|
+
0.05
|
|
11923
|
+
],
|
|
11924
|
+
hitDebris: "dust",
|
|
11925
|
+
breakDebris: "dust",
|
|
11926
|
+
thermal: {
|
|
11927
|
+
conductivity: 0.1,
|
|
11928
|
+
heatCapacity: 2.1
|
|
11929
|
+
},
|
|
11930
|
+
tags: [
|
|
11931
|
+
"soil",
|
|
11932
|
+
"frozen"
|
|
11933
|
+
],
|
|
11934
|
+
compressive: 1e3
|
|
11935
|
+
},
|
|
11936
|
+
{
|
|
11937
|
+
id: "cobble",
|
|
11938
|
+
density: 2650,
|
|
11939
|
+
hpPerKg: 25,
|
|
11940
|
+
penetration: 25,
|
|
11941
|
+
penetrationClass: "concrete",
|
|
11942
|
+
penetrationScale: 0.9,
|
|
11943
|
+
ductility: 0.03,
|
|
11944
|
+
fracture: "crumble",
|
|
11945
|
+
fragmentSize: [
|
|
11946
|
+
0.05,
|
|
11947
|
+
0.15
|
|
11948
|
+
],
|
|
11949
|
+
hitDebris: "chips",
|
|
11950
|
+
breakDebris: "chips",
|
|
11951
|
+
thermal: {
|
|
11952
|
+
conductivity: 0.2,
|
|
11953
|
+
heatCapacity: 2.5
|
|
11954
|
+
},
|
|
11955
|
+
tags: [
|
|
11956
|
+
"masonry",
|
|
11957
|
+
"brittle"
|
|
11958
|
+
],
|
|
11959
|
+
compressive: 3e5
|
|
11960
|
+
},
|
|
11961
|
+
{
|
|
11962
|
+
id: "paver",
|
|
11963
|
+
density: 2300,
|
|
11964
|
+
hpPerKg: 14,
|
|
11965
|
+
penetration: 18,
|
|
11966
|
+
penetrationClass: "concrete",
|
|
11967
|
+
penetrationScale: 1,
|
|
11968
|
+
ductility: 0.03,
|
|
11969
|
+
fracture: "crumble",
|
|
11970
|
+
fragmentSize: [
|
|
11971
|
+
0.04,
|
|
11972
|
+
0.12
|
|
11973
|
+
],
|
|
11974
|
+
hitDebris: "chips",
|
|
11975
|
+
breakDebris: "chips",
|
|
11976
|
+
thermal: {
|
|
11977
|
+
conductivity: 0.15,
|
|
11978
|
+
heatCapacity: 2.5
|
|
11979
|
+
},
|
|
11980
|
+
tags: [
|
|
11981
|
+
"masonry",
|
|
11982
|
+
"brittle"
|
|
11983
|
+
],
|
|
11984
|
+
compressive: 35e4
|
|
11579
11985
|
}
|
|
11580
11986
|
];
|
|
11581
11987
|
var material_hints_default = {
|
|
@@ -11714,6 +12120,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
11714
12120
|
return out;
|
|
11715
12121
|
};
|
|
11716
12122
|
var RESTING_HOLD_COS = Math.cos(Math.PI / 6);
|
|
12123
|
+
var BLAST_REFERENCE_RESISTANCE = 200 * 1900 * 0.13;
|
|
11717
12124
|
var assemblies_default = [
|
|
11718
12125
|
{
|
|
11719
12126
|
id: "brick_wall_plastered",
|
|
@@ -12099,6 +12506,23 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12099
12506
|
}
|
|
12100
12507
|
};
|
|
12101
12508
|
var tmpV = (THREE) => new THREE.Vector3();
|
|
12509
|
+
function plainPositions(mesh2, v0, v1) {
|
|
12510
|
+
const { collapsedRanges, plainPositions: plain } = mesh2.userData;
|
|
12511
|
+
if (!plain || !collapsedRanges?.some(([a, b]) => a < v1 && b > v0)) return null;
|
|
12512
|
+
return plain;
|
|
12513
|
+
}
|
|
12514
|
+
function showCollapsedRanges(mesh2) {
|
|
12515
|
+
const data = mesh2.userData;
|
|
12516
|
+
const plain = data.plainPositions;
|
|
12517
|
+
const position = mesh2.geometry?.getAttribute(POS_ATTR);
|
|
12518
|
+
if (!plain || !data.collapsedRanges?.length || !position) return;
|
|
12519
|
+
const array = position.array;
|
|
12520
|
+
for (const [v0, v1] of data.collapsedRanges) array.set(plain.subarray(v0 * 3, v1 * 3), v0 * 3);
|
|
12521
|
+
data.collapsedRanges = [];
|
|
12522
|
+
position.needsUpdate = true;
|
|
12523
|
+
mesh2.geometry.computeBoundingBox();
|
|
12524
|
+
mesh2.geometry.computeBoundingSphere();
|
|
12525
|
+
}
|
|
12102
12526
|
var MIN_DENT_VERTICES = 10;
|
|
12103
12527
|
var WIDEN_MARGIN = 1.1;
|
|
12104
12528
|
var SKIN_FOLLOW = 0.85;
|
|
@@ -12864,7 +13288,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
12864
13288
|
const a = src.getAttribute(name);
|
|
12865
13289
|
if (a.isInterleavedBufferAttribute) continue;
|
|
12866
13290
|
const size = a.itemSize;
|
|
12867
|
-
const from = (name === POS_ATTR ? host.work : null) ?? a.array;
|
|
13291
|
+
const from = (name === POS_ATTR ? plainPositions(host.mesh, v0, v1) ?? host.work : null) ?? a.array;
|
|
12868
13292
|
const slice = from.slice(v0 * size, v1 * size);
|
|
12869
13293
|
const Ctor = a.array.constructor;
|
|
12870
13294
|
geo.setAttribute(name, new this.THREE.BufferAttribute(new Ctor(slice), size, a.normalized));
|
|
@@ -13022,11 +13446,29 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13022
13446
|
// src/renderer.ts
|
|
13023
13447
|
var rendererCount = 0;
|
|
13024
13448
|
var COLLAPSE_CLOUD_KG = 1e3;
|
|
13449
|
+
var REBAR_COLOR = 7628380;
|
|
13450
|
+
var STEEL_DENSITY = 7850;
|
|
13025
13451
|
var CHIP_DECAL_EDGE = 0.05;
|
|
13026
13452
|
var WOOD_RIM = 1.5;
|
|
13027
13453
|
var IMPACT_TAGS = ["metal", "glass", "wood"];
|
|
13028
13454
|
var GLOW_FROM_C = 430;
|
|
13029
13455
|
var QUEUE_CAP = 4e3;
|
|
13456
|
+
var STRUCTURAL_EVENTS = /* @__PURE__ */ new Set([
|
|
13457
|
+
"fracture",
|
|
13458
|
+
"detach",
|
|
13459
|
+
"collapse",
|
|
13460
|
+
"settle",
|
|
13461
|
+
"destroyed",
|
|
13462
|
+
"expose",
|
|
13463
|
+
"stage",
|
|
13464
|
+
"hole",
|
|
13465
|
+
"ignite",
|
|
13466
|
+
"primed",
|
|
13467
|
+
"extinguish",
|
|
13468
|
+
"burnout",
|
|
13469
|
+
"leak",
|
|
13470
|
+
"explode"
|
|
13471
|
+
]);
|
|
13030
13472
|
var MIN_FRAGMENTS_PER_SLICE = 8;
|
|
13031
13473
|
var STATIC_REFRESH = 0.5;
|
|
13032
13474
|
var STATIC_REFRESH_SLICE_MS = 1;
|
|
@@ -13135,6 +13577,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13135
13577
|
__publicField(this, "pieceBoxes", /* @__PURE__ */ new WeakMap());
|
|
13136
13578
|
__publicField(this, "staticAt", 0);
|
|
13137
13579
|
__publicField(this, "queue", []);
|
|
13580
|
+
/** 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. */
|
|
13581
|
+
__publicField(this, "coverWhenWentOff", /* @__PURE__ */ new WeakMap());
|
|
13138
13582
|
/** 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
13583
|
__publicField(this, "slices", []);
|
|
13140
13584
|
/** When the work of this frame must stop; `Infinity` outside a drain, where a call finishes what it started. */
|
|
@@ -13214,7 +13658,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13214
13658
|
groundAt: this.groundAt,
|
|
13215
13659
|
gravity: this.gravity,
|
|
13216
13660
|
max: this.maxFragments,
|
|
13217
|
-
life: o.fragmentLife ?? 12
|
|
13661
|
+
life: o.fragmentLife ?? 12,
|
|
13662
|
+
formOf: (id) => this.world.materials.get(id)?.splitForm
|
|
13218
13663
|
});
|
|
13219
13664
|
this.cages = new RebarCages(THREE, o.scene);
|
|
13220
13665
|
this.pieceSys = new PieceSystem(THREE, {
|
|
@@ -13227,7 +13672,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13227
13672
|
onSlam: (p, hit, speed) => this.slam(p, hit, speed)
|
|
13228
13673
|
});
|
|
13229
13674
|
this.unsubscribe = this.world.on("*", (ev) => {
|
|
13230
|
-
if (
|
|
13675
|
+
if (ev.type === "explode") this.coverWhenWentOff.set(ev, this.coverAround(ev));
|
|
13676
|
+
if (this.queue.length < QUEUE_CAP || STRUCTURAL_EVENTS.has(ev.type)) this.queue.push(ev);
|
|
13231
13677
|
});
|
|
13232
13678
|
}
|
|
13233
13679
|
// ----------------------------------------------------------------------------------------------------------------------------------
|
|
@@ -13750,7 +14196,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
13750
14196
|
this.onChip(ev);
|
|
13751
14197
|
break;
|
|
13752
14198
|
case "expose":
|
|
13753
|
-
this.cages.set(`${ev.object}|${ev.part}`, ev.bars);
|
|
14199
|
+
if (ev.bars.length > 0) this.cages.set(`${ev.object}|${ev.part}`, ev.bars, this.bounds.get(ev.object)?.root);
|
|
14200
|
+
else this.dropCage(ev.object, ev.part);
|
|
13754
14201
|
if (ev.bars.length > 0) {
|
|
13755
14202
|
const [a, b] = ev.bars[0];
|
|
13756
14203
|
this.playEvent({ kind: "expose" }, [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2]);
|
|
@@ -14048,7 +14495,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14048
14495
|
const rotation2 = new T.Quaternion(ev.rotation[0], ev.rotation[1], ev.rotation[2], ev.rotation[3]);
|
|
14049
14496
|
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
14497
|
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 });
|
|
14498
|
+
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
14499
|
this.impactSound(pv?.material ?? "", ev.center, 0.7, ev.blow);
|
|
14053
14500
|
}
|
|
14054
14501
|
onFracture(ev) {
|
|
@@ -14067,6 +14514,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14067
14514
|
this.visuals.hide(pv, "broken");
|
|
14068
14515
|
}
|
|
14069
14516
|
this.spawnFragments(ev, 0, { def, color, opacity, transparent });
|
|
14517
|
+
this.loosenIfFootless(ev.object);
|
|
14070
14518
|
const up = [0, 1, 0];
|
|
14071
14519
|
const sizeK = clamp6(ev.fragments.length / 10, 0.5, 2);
|
|
14072
14520
|
switch (ev.style) {
|
|
@@ -14112,6 +14560,33 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14112
14560
|
this.sound("detach", this.rootPoint(ev.object), 0.5);
|
|
14113
14561
|
this.splitLoose(ev.object);
|
|
14114
14562
|
}
|
|
14563
|
+
/**
|
|
14564
|
+
* 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
|
|
14565
|
+
* ground with a body (`FragmentSystem.spawn` with `lasting`), not as bars left standing in the air.
|
|
14566
|
+
*/
|
|
14567
|
+
dropCage(objectId, partId) {
|
|
14568
|
+
const bars = this.cages.take(`${objectId}|${partId}`);
|
|
14569
|
+
if (bars.length === 0) return;
|
|
14570
|
+
const radius = this.cages.barRadius;
|
|
14571
|
+
const density = this.world.materials.get("steel")?.density ?? STEEL_DENSITY;
|
|
14572
|
+
const material = this.fragments.materialFor("steel", REBAR_COLOR, false);
|
|
14573
|
+
const rnd2 = createRng(hashString(`${objectId}|${partId}`));
|
|
14574
|
+
bars.forEach((bar, i) => {
|
|
14575
|
+
const fragment = {
|
|
14576
|
+
id: `${partId}#bar${i}`,
|
|
14577
|
+
part: partId,
|
|
14578
|
+
material: "steel",
|
|
14579
|
+
shape: "chunk",
|
|
14580
|
+
center: bar.center,
|
|
14581
|
+
size: [2 * radius, bar.length, 2 * radius],
|
|
14582
|
+
rotation: bar.rotation,
|
|
14583
|
+
velocity: [(rnd2() - 0.5) * 0.4, 0, (rnd2() - 0.5) * 0.4],
|
|
14584
|
+
angularVelocity: [(rnd2() - 0.5) * 1.5, (rnd2() - 0.5) * 0.5, (rnd2() - 0.5) * 1.5],
|
|
14585
|
+
mass: density * Math.PI * radius * radius * bar.length
|
|
14586
|
+
};
|
|
14587
|
+
this.fragments.spawn(fragment, material, objectId, this.maxFragments, true);
|
|
14588
|
+
});
|
|
14589
|
+
}
|
|
14115
14590
|
/** Turn every loose group of the object (a detach, a collapse) into a piece of its own. */
|
|
14116
14591
|
splitLoose(objectId) {
|
|
14117
14592
|
if (!this.autoSplit) return;
|
|
@@ -14166,10 +14641,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14166
14641
|
if (!pv) continue;
|
|
14167
14642
|
from.parts.delete(p.id);
|
|
14168
14643
|
nb.parts.set(p.id, pv);
|
|
14644
|
+
this.cages.move(`${from.id}|${p.id}`, `${newId}|${p.id}`, group);
|
|
14169
14645
|
for (const mv of pv.meshes) {
|
|
14170
14646
|
if (mv.host && !mv.loose) this.visuals.extract(mv, group);
|
|
14171
14647
|
else {
|
|
14172
14648
|
group.attach(mv.mesh);
|
|
14649
|
+
showCollapsedRanges(mv.mesh);
|
|
14173
14650
|
mv.moved = true;
|
|
14174
14651
|
}
|
|
14175
14652
|
}
|
|
@@ -14196,6 +14673,45 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14196
14673
|
if (kitOriginOf(from.root)?.kind === "furniture") this.tipIfTopHeavy(nb);
|
|
14197
14674
|
this.o.onPiece?.(piece);
|
|
14198
14675
|
}
|
|
14676
|
+
/**
|
|
14677
|
+
* 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
|
|
14678
|
+
* 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.
|
|
14679
|
+
*/
|
|
14680
|
+
loosenIfFootless(objectId) {
|
|
14681
|
+
const b = this.bounds.get(objectId);
|
|
14682
|
+
if (!b || !this.autoSplit) return;
|
|
14683
|
+
const left = this.meshesOf(b).filter((mv) => this.visuals.shown(mv));
|
|
14684
|
+
if (left.length === 0) return;
|
|
14685
|
+
const body = b.piece ?? this.pieceSys.get(objectId);
|
|
14686
|
+
if (body) {
|
|
14687
|
+
const { half, center } = this.measure(b.root, left);
|
|
14688
|
+
body.halfExtents = half;
|
|
14689
|
+
this.pieceSys.setCenter(body, center);
|
|
14690
|
+
body.mass = Math.max(0.05, this.massOf(objectId));
|
|
14691
|
+
body.sleeping = false;
|
|
14692
|
+
return;
|
|
14693
|
+
}
|
|
14694
|
+
if (kitOriginOf(b.root)?.kind !== "furniture") return;
|
|
14695
|
+
const boxOf = (mv) => this.visuals.worldBox(mv, new this.THREE.Box3());
|
|
14696
|
+
const whole = [...b.parts.values()].flatMap((pv) => pv.meshes).map(boxOf).filter((one) => !one.isEmpty());
|
|
14697
|
+
const floor2 = Math.min(...whole.map((one) => one.min.y));
|
|
14698
|
+
const feetOf = (boxes) => boxes.filter((one) => one.min.y < floor2 + FEET_BAND).reduce((feet, one) => feet.union(one), new this.THREE.Box3());
|
|
14699
|
+
const was = feetOf(whole);
|
|
14700
|
+
const now = feetOf(left.map(boxOf).filter((one) => !one.isEmpty()));
|
|
14701
|
+
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;
|
|
14702
|
+
if (same) return;
|
|
14703
|
+
if (!now.isEmpty()) {
|
|
14704
|
+
const box = left.map(boxOf).reduce((all, one) => all.union(one), new this.THREE.Box3());
|
|
14705
|
+
const centreX = (box.min.x + box.max.x) / 2;
|
|
14706
|
+
const centreZ = (box.min.z + box.max.z) / 2;
|
|
14707
|
+
const height = box.max.y - box.min.y;
|
|
14708
|
+
const narrow = height >= TIP_MIN_HEIGHT && Math.min(now.max.x - now.min.x, now.max.z - now.min.z) < TIP_NARROW * height;
|
|
14709
|
+
if (!narrow && centreX > now.min.x && centreX < now.max.x && centreZ > now.min.z && centreZ < now.max.z) return;
|
|
14710
|
+
}
|
|
14711
|
+
const piece = this.makePiece(objectId);
|
|
14712
|
+
this.tipIfTopHeavy(b);
|
|
14713
|
+
this.o.onLoosened?.(objectId, piece, "apart");
|
|
14714
|
+
}
|
|
14199
14715
|
/**
|
|
14200
14716
|
* 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
14717
|
* high and its feet are thin, so it gets the start of the fall, across the line of its feet.
|
|
@@ -14227,7 +14743,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14227
14743
|
const report = options.report;
|
|
14228
14744
|
const confinement = report?.find((r) => r.blast !== void 0)?.blast?.confinement ?? 1;
|
|
14229
14745
|
const energy = ev.energy * confinement;
|
|
14230
|
-
const
|
|
14746
|
+
const wentOff = this.coverWhenWentOff.get(ev);
|
|
14747
|
+
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
14748
|
const shove = (pc) => {
|
|
14232
14749
|
this.pieceSys.centerOf(pc, c);
|
|
14233
14750
|
if (c.distanceTo(this.v2.set(p[0], p[1], p[2])) > ev.radius) return;
|
|
@@ -14247,6 +14764,29 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14247
14764
|
...report ? { confinement } : {}
|
|
14248
14765
|
});
|
|
14249
14766
|
}
|
|
14767
|
+
/** 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. */
|
|
14768
|
+
coverAround(ev) {
|
|
14769
|
+
const cover = /* @__PURE__ */ new Map();
|
|
14770
|
+
const reach = this.world.blastReach;
|
|
14771
|
+
if (!reach) return cover;
|
|
14772
|
+
const point = new this.THREE.Vector3(ev.point[0], ev.point[1], ev.point[2]);
|
|
14773
|
+
const centre = new this.THREE.Vector3();
|
|
14774
|
+
const read = (objectId) => {
|
|
14775
|
+
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] }));
|
|
14776
|
+
};
|
|
14777
|
+
for (const pc of this.pieceSys.list) {
|
|
14778
|
+
this.pieceSys.centerOf(pc, centre);
|
|
14779
|
+
read(pc.objectId);
|
|
14780
|
+
}
|
|
14781
|
+
const shoveable = this.o.shoveable;
|
|
14782
|
+
if (!shoveable) return cover;
|
|
14783
|
+
for (const b of this.bounds.values()) {
|
|
14784
|
+
if (b.piece || cover.has(b.id) || !shoveable(b.id) || !this.world.has(b.id)) continue;
|
|
14785
|
+
centre.copy(this.measure(b.root, this.meshesOf(b)).center).applyQuaternion(b.root.quaternion).add(b.root.position);
|
|
14786
|
+
read(b.id);
|
|
14787
|
+
}
|
|
14788
|
+
return cover;
|
|
14789
|
+
}
|
|
14250
14790
|
/** 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
14791
|
loosen(point, radius, energy, reachOf) {
|
|
14252
14792
|
const shoveable = this.o.shoveable;
|
|
@@ -14542,7 +15082,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14542
15082
|
}
|
|
14543
15083
|
};
|
|
14544
15084
|
|
|
14545
|
-
// ../game-audio/dist/chunk-
|
|
15085
|
+
// ../game-audio/dist/chunk-C2K7XN2K.js
|
|
14546
15086
|
var DISTANCE_CURVES = {
|
|
14547
15087
|
/** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
|
|
14548
15088
|
game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
|
|
@@ -14759,7 +15299,22 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14759
15299
|
["smg_45_auto", 2, "sfx_pistol"],
|
|
14760
15300
|
["weapon_draw", 2],
|
|
14761
15301
|
["suppressor_attach", 3],
|
|
14762
|
-
["suppressor_detach", 3]
|
|
15302
|
+
["suppressor_detach", 3],
|
|
15303
|
+
["minigun_loop", 3],
|
|
15304
|
+
["smg_5_7x28", 3],
|
|
15305
|
+
["mg_belt_adjust", 3],
|
|
15306
|
+
["mg_belt_box_attach", 3],
|
|
15307
|
+
["mg_cover_open", 3],
|
|
15308
|
+
["mg_cover_close", 3],
|
|
15309
|
+
["weapon_holster", 3],
|
|
15310
|
+
["scope_zoom", 3]
|
|
15311
|
+
]),
|
|
15312
|
+
...entries("impactos", [
|
|
15313
|
+
["knife_hit_wall", 3],
|
|
15314
|
+
["helmet_hit", 3],
|
|
15315
|
+
["vest_hit", 3],
|
|
15316
|
+
["headshot", 3],
|
|
15317
|
+
["blood_squirt", 3]
|
|
14763
15318
|
]),
|
|
14764
15319
|
...entries("explosoes-fogo", [
|
|
14765
15320
|
["explosion_grenade", 1, "boom"],
|
|
@@ -14777,7 +15332,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14777
15332
|
["grenade_bounce", 1],
|
|
14778
15333
|
["c4_plant", 2],
|
|
14779
15334
|
["c4_beep", 2],
|
|
14780
|
-
["c4_disarm", 2]
|
|
15335
|
+
["c4_disarm", 2],
|
|
15336
|
+
["c4_disarming_loop", 3],
|
|
15337
|
+
["c4_remote_detonate", 3],
|
|
15338
|
+
["smoke_grenade_hiss", 3],
|
|
15339
|
+
["grenade_pin_pull", 3],
|
|
15340
|
+
["electric_shock", 3],
|
|
15341
|
+
["electric_spark", 3]
|
|
14781
15342
|
]),
|
|
14782
15343
|
...entries("personagem", [
|
|
14783
15344
|
["footstep_concrete_walk", 1, "sfx_step"],
|
|
@@ -14805,7 +15366,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14805
15366
|
["zombie_attack", 1],
|
|
14806
15367
|
["zombie_pain", 1],
|
|
14807
15368
|
["zombie_claw_hit", 1, "sfx_flesh"],
|
|
14808
|
-
["zombie_claw_miss", 2]
|
|
15369
|
+
["zombie_claw_miss", 2],
|
|
15370
|
+
["swing_whoosh", 3],
|
|
15371
|
+
["heartbeat_critical", 3],
|
|
15372
|
+
["fall_water", 3],
|
|
15373
|
+
["fall_damage", 3],
|
|
15374
|
+
["fall_scream", 3],
|
|
15375
|
+
["footstep_glass_walk", 3]
|
|
14809
15376
|
]),
|
|
14810
15377
|
...entries("veiculos", [
|
|
14811
15378
|
["engine_loop_car", 1],
|
|
@@ -14815,7 +15382,12 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14815
15382
|
["car_door_open", 1, "latch"],
|
|
14816
15383
|
["car_door_close", 1, "shut_metal"],
|
|
14817
15384
|
["horn_car", 2, "horn"],
|
|
14818
|
-
["tire_skid_asphalt", 2]
|
|
15385
|
+
["tire_skid_asphalt", 2],
|
|
15386
|
+
["jet_flyby", 3],
|
|
15387
|
+
["helicopter_turbine", 3],
|
|
15388
|
+
["tire_burnout", 3],
|
|
15389
|
+
["handbrake_turn", 3],
|
|
15390
|
+
["siren_police", 3]
|
|
14819
15391
|
]),
|
|
14820
15392
|
...entries("itens-portas", [
|
|
14821
15393
|
["item_pickup_metal", 1],
|
|
@@ -14826,7 +15398,27 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14826
15398
|
["door_creak", 1, "creak_wood"],
|
|
14827
15399
|
["door_locked_rattle", 1, "rattle"],
|
|
14828
15400
|
["drawer_open", 2, "slide"],
|
|
14829
|
-
["fridge_close", 2, "shut_metal"]
|
|
15401
|
+
["fridge_close", 2, "shut_metal"],
|
|
15402
|
+
["inventory_open", 3],
|
|
15403
|
+
["inventory_close", 3],
|
|
15404
|
+
["inventory_select", 3],
|
|
15405
|
+
["inventory_place", 3],
|
|
15406
|
+
["pickup_chime", 3],
|
|
15407
|
+
["item_pickup_coins", 3],
|
|
15408
|
+
["bandage_use", 3],
|
|
15409
|
+
["bandage_refuse", 3],
|
|
15410
|
+
["flashlight_toggle", 3],
|
|
15411
|
+
["gear_draw", 3],
|
|
15412
|
+
["gear_holster", 3],
|
|
15413
|
+
["button_press", 3],
|
|
15414
|
+
["lock_desk_open", 3],
|
|
15415
|
+
["elevator_move_loop", 3],
|
|
15416
|
+
["elevator_stop", 3],
|
|
15417
|
+
["door_sliding", 3],
|
|
15418
|
+
["door_kick", 3],
|
|
15419
|
+
["door_wood_break", 3],
|
|
15420
|
+
["gate_iron_open", 3],
|
|
15421
|
+
["gate_iron_close", 3]
|
|
14830
15422
|
]),
|
|
14831
15423
|
...entries("ambiente", [
|
|
14832
15424
|
["rain_loop", 2],
|
|
@@ -14834,7 +15426,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14834
15426
|
["thunder_near", 2],
|
|
14835
15427
|
["thunder_far", 2],
|
|
14836
15428
|
["water_splash", 2],
|
|
14837
|
-
["helicopter_loop", 3]
|
|
15429
|
+
["helicopter_loop", 3],
|
|
15430
|
+
["bird_flush", 3],
|
|
15431
|
+
["dog_growl", 3],
|
|
15432
|
+
["dog_whimper", 3],
|
|
15433
|
+
["dog_howl", 3],
|
|
15434
|
+
["zombie_dog_bark", 3],
|
|
15435
|
+
["zombie_dog_howl_far", 3],
|
|
15436
|
+
["insect_squash", 3],
|
|
15437
|
+
["insect_crawl", 3],
|
|
15438
|
+
["fan_loop", 3]
|
|
14838
15439
|
])
|
|
14839
15440
|
];
|
|
14840
15441
|
var SFX_MATERIALS = [
|
|
@@ -14970,6 +15571,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
14970
15571
|
}
|
|
14971
15572
|
return { id: options.id, samples };
|
|
14972
15573
|
}
|
|
15574
|
+
var CATALOG = new Map([...SFX_CATALOG, ...SFX_IMPACTS].map((entry) => [entry.id, entry]));
|
|
14973
15575
|
var PT = {
|
|
14974
15576
|
group: "Som",
|
|
14975
15577
|
name: "Painel de som (arquivos e C1\u2013C10)",
|
|
@@ -15044,9 +15646,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15044
15646
|
strengths: { light: "leve", medium: "m\xE9dio", heavy: "forte" },
|
|
15045
15647
|
states: { file: "A", synth: "S", missing: "F" },
|
|
15046
15648
|
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" },
|
|
15047
|
-
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" },
|
|
15649
|
+
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" },
|
|
15048
15650
|
run: { idle: "parada", measuring: "medindo\u2026", done: "pronta", failed: "falhou", recording: "gravando\u2026", recorded: "grava\xE7\xE3o baixada" },
|
|
15049
|
-
words: { none: "\u2014", hidden: "? (revele)", prefers: "prefere", approved: "aprovado pelo dono" },
|
|
15651
|
+
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" },
|
|
15050
15652
|
sources: { sdk: "SDK", user: "seu som", synth: "sintetizado" },
|
|
15051
15653
|
probe: "Fonte do som",
|
|
15052
15654
|
wall: "Parede (oclus\xE3o)",
|
|
@@ -15117,9 +15719,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15117
15719
|
environments: { indoor: "indoor", outdoor: "outdoor" },
|
|
15118
15720
|
strengths: { light: "light", medium: "medium", heavy: "heavy" },
|
|
15119
15721
|
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" },
|
|
15120
|
-
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" },
|
|
15722
|
+
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" },
|
|
15121
15723
|
run: { idle: "idle", measuring: "measuring\u2026", done: "done", failed: "failed", recording: "recording\u2026", recorded: "recording downloaded" },
|
|
15122
|
-
words: { none: "\u2014", hidden: "? (reveal)", prefers: "prefers", approved: "approved by the owner" },
|
|
15724
|
+
words: { none: "\u2014", hidden: "? (reveal)", prefers: "prefers", approved: "approved by the owner", local_file: "local pack file", sdk_file: "SDK recording", synth: "SDK synthesis" },
|
|
15123
15725
|
sources: { sdk: "SDK", user: "your sound", synth: "synthesized" },
|
|
15124
15726
|
probe: "Sound source",
|
|
15125
15727
|
wall: "Wall (occlusion)",
|
|
@@ -15202,6 +15804,66 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15202
15804
|
sources: [`cena:${line}`]
|
|
15203
15805
|
};
|
|
15204
15806
|
}
|
|
15807
|
+
var hz = (midi) => 440 * 2 ** ((midi - 69) / 12);
|
|
15808
|
+
var layer2 = (note, beat, wave, level) => {
|
|
15809
|
+
const [at3, midi, beats] = note;
|
|
15810
|
+
const length = beats * beat;
|
|
15811
|
+
return {
|
|
15812
|
+
at: at3 * beat,
|
|
15813
|
+
duration: length,
|
|
15814
|
+
source: { kind: "osc", wave, frequency: { set: hz(midi) } },
|
|
15815
|
+
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"]]
|
|
15816
|
+
};
|
|
15817
|
+
};
|
|
15818
|
+
function radioTrack(spec) {
|
|
15819
|
+
const beat = 60 / spec.bpm;
|
|
15820
|
+
const recipes = Array.from({ length: spec.bars }, (_, bar) => {
|
|
15821
|
+
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]]);
|
|
15822
|
+
const layers = [
|
|
15823
|
+
...inBar(spec.bass).map((n) => layer2(n, beat, "triangle", 0.16)),
|
|
15824
|
+
...inBar(spec.chords).map((n) => layer2(n, beat, "sine", 0.05)),
|
|
15825
|
+
...inBar(spec.melody).map((n) => layer2(n, beat, spec.wave, 0.07))
|
|
15826
|
+
];
|
|
15827
|
+
return { id: `${spec.id}_${bar}`, bus: "world", duration: 4 * beat, layers, falloff: { model: "none" } };
|
|
15828
|
+
});
|
|
15829
|
+
return { song: { id: spec.id, barSeconds: 4 * beat, bars: recipes.map((r) => r.id) }, recipes };
|
|
15830
|
+
}
|
|
15831
|
+
var chord = (bar, root, minor = false) => [[bar * 4, root, 4], [bar * 4, root + (minor ? 3 : 4), 4], [bar * 4, root + 7, 4]];
|
|
15832
|
+
var bassLine = (roots, pattern) => roots.flatMap((root, bar) => pattern.map((b) => [bar * 4 + b, root - 12, 0.9]));
|
|
15833
|
+
var TRACKS = [
|
|
15834
|
+
// a bright pop loop: I–V–vi–IV in C
|
|
15835
|
+
radioTrack({
|
|
15836
|
+
id: "radio_track_pop",
|
|
15837
|
+
bpm: 112,
|
|
15838
|
+
bars: 4,
|
|
15839
|
+
wave: "square",
|
|
15840
|
+
bass: bassLine([48, 55, 57, 53], [0, 1.5, 2, 3]),
|
|
15841
|
+
chords: [...chord(0, 60), ...chord(1, 55), ...chord(2, 57, true), ...chord(3, 53)],
|
|
15842
|
+
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]]
|
|
15843
|
+
}),
|
|
15844
|
+
// a syncopated loop in A minor, the bass on the off-beats
|
|
15845
|
+
radioTrack({
|
|
15846
|
+
id: "radio_track_groove",
|
|
15847
|
+
bpm: 100,
|
|
15848
|
+
bars: 4,
|
|
15849
|
+
wave: "triangle",
|
|
15850
|
+
bass: bassLine([57, 62, 64, 57], [0, 0.75, 1.5, 2.5, 3.25]),
|
|
15851
|
+
chords: [...chord(0, 57, true), ...chord(1, 62, true), ...chord(2, 64), ...chord(3, 57, true)],
|
|
15852
|
+
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]]
|
|
15853
|
+
}),
|
|
15854
|
+
// a slow blues-like loop in F with a swung melody
|
|
15855
|
+
radioTrack({
|
|
15856
|
+
id: "radio_track_slow",
|
|
15857
|
+
bpm: 76,
|
|
15858
|
+
bars: 4,
|
|
15859
|
+
wave: "sine",
|
|
15860
|
+
bass: bassLine([53, 58, 53, 60], [0, 2]),
|
|
15861
|
+
chords: [...chord(0, 65), ...chord(1, 58), ...chord(2, 65), ...chord(3, 60)],
|
|
15862
|
+
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]]
|
|
15863
|
+
})
|
|
15864
|
+
];
|
|
15865
|
+
var RADIO_TRACKS = TRACKS.map((t) => t.song);
|
|
15866
|
+
var RADIO_BANK = { id: "vehicle_radio", sounds: TRACKS.flatMap((t) => t.recipes), loops: [] };
|
|
15205
15867
|
var FOLEY_FALLOFF = {
|
|
15206
15868
|
model: "inverse_square",
|
|
15207
15869
|
k: 0.02,
|
|
@@ -15808,11 +16470,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15808
16470
|
...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 })] : []
|
|
15809
16471
|
];
|
|
15810
16472
|
}
|
|
15811
|
-
var quieter = (level, layers) => layers.map((
|
|
15812
|
-
const gain =
|
|
15813
|
-
if (typeof gain === "number") return { ...
|
|
15814
|
-
if (Array.isArray(gain)) return { ...
|
|
15815
|
-
return
|
|
16473
|
+
var quieter = (level, layers) => layers.map((layer22) => {
|
|
16474
|
+
const gain = layer22.gain ?? 1;
|
|
16475
|
+
if (typeof gain === "number") return { ...layer22, gain: gain * level };
|
|
16476
|
+
if (Array.isArray(gain)) return { ...layer22, gain: [gain[0] * level, gain[1] * level] };
|
|
16477
|
+
return layer22;
|
|
15816
16478
|
});
|
|
15817
16479
|
var recipe = (id, layers, falloff, bus = "world") => ({ id, bus, pan: true, layers, falloff, sources: ["sdk"] });
|
|
15818
16480
|
var takeId = (id, index) => `${id}_${String(index + 1).padStart(2, "0")}`;
|
|
@@ -15828,7 +16490,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15828
16490
|
(take) => quieter(LANDING_LEVEL, [
|
|
15829
16491
|
// both feet, a hair apart, then the knees taking the weight (a low body thump) and the clothes settling
|
|
15830
16492
|
...footLayers(surface, take, GAITS.run, LANDINGS[landing2].weight),
|
|
15831
|
-
...footLayers(surface, { ...take, v: take.v * 0.6 }, GAITS.run, LANDINGS[landing2].weight * 0.6).map((
|
|
16493
|
+
...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 })),
|
|
15832
16494
|
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 }),
|
|
15833
16495
|
sceneNoise({ type: "bandpass", f: [1800, 2600], q: 0.8, d: 0.18, vol: LANDINGS[landing2].cloth * take.v, delay: 0.04 })
|
|
15834
16496
|
]),
|
|
@@ -15874,7 +16536,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
|
|
|
15874
16536
|
fall_damage: (take) => [
|
|
15875
16537
|
...BODY.body_fall(take),
|
|
15876
16538
|
sceneNoise({ type: "highpass", f: 2800, d: 0.02, vol: 0.35 * take.v, delay: 0.01 }),
|
|
15877
|
-
...voice({ f0: 170, f1: 110, d: 0.32, fA: 650, fB: 1150, noise: 0.25, vol: 0.32 })(take).map((
|
|
16539
|
+
...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 }))
|
|
15878
16540
|
],
|
|
15879
16541
|
player_hurt: voice({ f0: 150, f1: 105, d: 0.26, fA: 620, fB: 1100, noise: 0.3, vol: 0.35 }),
|
|
15880
16542
|
player_breath_run: (take) => [
|