@adatechnology/game-test-room 0.10.0 → 0.11.1

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.
@@ -4587,6 +4587,56 @@ var GameTestRoom = (() => {
4587
4587
  };
4588
4588
  }
4589
4589
  var GAME_SFX_BANK = { id: "game_sfx", sounds: [...GAME_SFX_CLASSES, GAME_SFX_FALLBACK].map((row) => noiseClassRecipe(row)) };
4590
+ var ATTACK = 2e-3;
4591
+ function lengthOf(d, attack) {
4592
+ if (typeof d === "number") return attack + d;
4593
+ if (Array.isArray(d)) return [attack + d[0], attack + d[1]];
4594
+ return { ...d, add: (d.add ?? 0) + attack };
4595
+ }
4596
+ var envelope = (attack) => [
4597
+ [0, 1e-4, "set", "fixed"],
4598
+ [attack, 1, "exp"],
4599
+ ["1d", 1e-4, "exp", "fixed"]
4600
+ ];
4601
+ function sceneNoise(o) {
4602
+ const a = o.a ?? ATTACK;
4603
+ const over = typeof o.d === "number" ? o.d : 0;
4604
+ return {
4605
+ filters: {
4606
+ mode: "serial",
4607
+ bank: [{ type: o.type ?? "lowpass", frequency: o.f2 === void 0 ? { set: o.f } : { set: o.f, to: o.f2, curve: "exp", over }, q: o.q ?? 0.7 }]
4608
+ },
4609
+ duration: lengthOf(o.d, a),
4610
+ gain: o.vol,
4611
+ ...o.delay === void 0 ? {} : { at: o.delay },
4612
+ ...o.wet === void 0 ? {} : { wet: o.wet },
4613
+ source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
4614
+ envelope: envelope(a)
4615
+ };
4616
+ }
4617
+ function sceneTone(o) {
4618
+ return {
4619
+ source: { kind: "osc", wave: o.wave ?? "sine", frequency: { from: o.f, to: o.f2 ?? "from", curve: "exp", over: "duration" } },
4620
+ duration: lengthOf(o.d, ATTACK),
4621
+ gain: o.vol,
4622
+ ...o.delay === void 0 ? {} : { at: o.delay },
4623
+ ...o.wet === void 0 ? {} : { wet: o.wet },
4624
+ envelope: envelope(ATTACK)
4625
+ };
4626
+ }
4627
+ var sceneMany = (count, make) => Array.from({ length: count }, make);
4628
+ function sceneRecipe(id, layers, line, options = {}) {
4629
+ return {
4630
+ id,
4631
+ bus: options.bus ?? "world",
4632
+ pan: true,
4633
+ layers,
4634
+ falloff: DISTANCE_CURVES.art_scene,
4635
+ ...options.vars ? { vars: options.vars } : {},
4636
+ ...options.group ? { group: options.group } : {},
4637
+ sources: [`cena:${line}`]
4638
+ };
4639
+ }
4590
4640
  function fillNoise(random, length3) {
4591
4641
  const samples = new Float32Array(length3);
4592
4642
  for (let index = 0; index < length3; index++) samples[index] = random() * 2 - 1;
@@ -6859,77 +6909,77 @@ var GameTestRoom = (() => {
6859
6909
  }));
6860
6910
  }
6861
6911
  function spawnPuff(pool, random, init) {
6862
- const puff2 = pool.acquire();
6863
- puff2.x = init.x ?? 0;
6864
- puff2.y = init.y ?? 0;
6865
- puff2.z = init.z ?? 0;
6866
- puff2.vx = init.vx ?? 0;
6867
- puff2.vy = init.vy ?? 0;
6868
- puff2.vz = init.vz ?? 0;
6869
- puff2.s0 = init.s0 ?? 0.2;
6870
- puff2.s1 = init.s1 ?? 1;
6871
- puff2.sg = init.sg ?? 0.1;
6872
- puff2.gk = init.gk ?? 6;
6873
- puff2.life = init.life ?? 3;
6874
- puff2.t = 0;
6875
- puff2.h0 = init.h0 ?? 0;
6876
- puff2.hl = init.hl ?? 0.4;
6877
- puff2.drag = init.drag ?? 3;
6878
- puff2.buoy = init.buoy ?? 0.5;
6879
- puff2.grav = init.grav ?? 0;
6880
- puff2.kind = init.kind ?? 0;
6881
- puff2.seed = random.next();
6882
- puff2.wind = init.wind ?? 1;
6883
- puff2.color = init.color ?? SMOKE_COLORS.grey;
6884
- puff2.ring = init.ring ?? null;
6885
- puff2.ph = init.ph ?? 0;
6886
- puff2.th = init.th ?? 0;
6887
- puff2.rng = random;
6888
- puff2.age = 0;
6889
- puff2.heat = 0;
6890
- puff2.size = 0;
6891
- return puff2;
6892
- }
6893
- function ageOrKeep2(puff2, dt) {
6894
- puff2.t += dt;
6895
- return puff2.t < puff2.life;
6896
- }
6897
- function moveOnRing(puff2, ring2, wind) {
6912
+ const puff3 = pool.acquire();
6913
+ puff3.x = init.x ?? 0;
6914
+ puff3.y = init.y ?? 0;
6915
+ puff3.z = init.z ?? 0;
6916
+ puff3.vx = init.vx ?? 0;
6917
+ puff3.vy = init.vy ?? 0;
6918
+ puff3.vz = init.vz ?? 0;
6919
+ puff3.s0 = init.s0 ?? 0.2;
6920
+ puff3.s1 = init.s1 ?? 1;
6921
+ puff3.sg = init.sg ?? 0.1;
6922
+ puff3.gk = init.gk ?? 6;
6923
+ puff3.life = init.life ?? 3;
6924
+ puff3.t = 0;
6925
+ puff3.h0 = init.h0 ?? 0;
6926
+ puff3.hl = init.hl ?? 0.4;
6927
+ puff3.drag = init.drag ?? 3;
6928
+ puff3.buoy = init.buoy ?? 0.5;
6929
+ puff3.grav = init.grav ?? 0;
6930
+ puff3.kind = init.kind ?? 0;
6931
+ puff3.seed = random.next();
6932
+ puff3.wind = init.wind ?? 1;
6933
+ puff3.color = init.color ?? SMOKE_COLORS.grey;
6934
+ puff3.ring = init.ring ?? null;
6935
+ puff3.ph = init.ph ?? 0;
6936
+ puff3.th = init.th ?? 0;
6937
+ puff3.rng = random;
6938
+ puff3.age = 0;
6939
+ puff3.heat = 0;
6940
+ puff3.size = 0;
6941
+ return puff3;
6942
+ }
6943
+ function ageOrKeep2(puff3, dt) {
6944
+ puff3.t += dt;
6945
+ return puff3.t < puff3.life;
6946
+ }
6947
+ function moveOnRing(puff3, ring2, wind) {
6898
6948
  const ringTime = ring2.t;
6899
6949
  const centerY = ring2.y0 + ring2.rise * (1 - Math.exp(-ringTime * ring2.rk));
6900
6950
  const majorRadius = ring2.R0 + (ring2.R1 - ring2.R0) * (1 - Math.exp(-ringTime * 1.6));
6901
- const theta = puff2.th + ringTime * ring2.roll;
6951
+ const theta = puff3.th + ringTime * ring2.roll;
6902
6952
  const minorRadius = ring2.r * (0.8 + 0.4 * Math.min(1, ringTime));
6903
6953
  const radius = majorRadius + minorRadius * Math.cos(theta);
6904
- puff2.x = ring2.x + Math.cos(puff2.ph) * radius + wind.x * ringTime * 0.6;
6905
- puff2.z = ring2.z + Math.sin(puff2.ph) * radius + wind.z * ringTime * 0.6;
6906
- puff2.y = centerY + minorRadius * Math.sin(theta);
6907
- }
6908
- function moveFree(puff2, dt, age, heat, wind) {
6909
- const damping = Math.exp(-puff2.drag * dt);
6910
- puff2.vx *= damping;
6911
- puff2.vy *= damping;
6912
- puff2.vz *= damping;
6913
- puff2.vy += (puff2.buoy * (heat > 0 ? 1.6 : 1) - puff2.grav) * dt;
6914
- puff2.x += (puff2.vx + wind.x * puff2.wind * age) * dt;
6915
- puff2.y += puff2.vy * dt;
6916
- puff2.z += (puff2.vz + wind.z * puff2.wind * age) * dt;
6917
- if (puff2.y < puff2.s0 * FLOOR_FRACTION) {
6918
- puff2.y = puff2.s0 * FLOOR_FRACTION;
6919
- if (puff2.vy < 0) puff2.vy *= FLOOR_BOUNCE;
6954
+ puff3.x = ring2.x + Math.cos(puff3.ph) * radius + wind.x * ringTime * 0.6;
6955
+ puff3.z = ring2.z + Math.sin(puff3.ph) * radius + wind.z * ringTime * 0.6;
6956
+ puff3.y = centerY + minorRadius * Math.sin(theta);
6957
+ }
6958
+ function moveFree(puff3, dt, age, heat, wind) {
6959
+ const damping = Math.exp(-puff3.drag * dt);
6960
+ puff3.vx *= damping;
6961
+ puff3.vy *= damping;
6962
+ puff3.vz *= damping;
6963
+ puff3.vy += (puff3.buoy * (heat > 0 ? 1.6 : 1) - puff3.grav) * dt;
6964
+ puff3.x += (puff3.vx + wind.x * puff3.wind * age) * dt;
6965
+ puff3.y += puff3.vy * dt;
6966
+ puff3.z += (puff3.vz + wind.z * puff3.wind * age) * dt;
6967
+ if (puff3.y < puff3.s0 * FLOOR_FRACTION) {
6968
+ puff3.y = puff3.s0 * FLOOR_FRACTION;
6969
+ if (puff3.vy < 0) puff3.vy *= FLOOR_BOUNCE;
6920
6970
  }
6921
6971
  }
6922
6972
  function updatePuffs(pool, dt, wind) {
6923
6973
  pool.compact(ageOrKeep2, dt);
6924
- for (const puff2 of pool.items) {
6925
- const age = puff2.t / puff2.life;
6926
- const heat = puff2.h0 > 0 ? puff2.h0 * Math.pow(Math.max(0, 1 - puff2.t / puff2.hl), 1.25) : 0;
6927
- if (puff2.ring) moveOnRing(puff2, puff2.ring, wind);
6928
- else moveFree(puff2, dt, age, heat, wind);
6929
- const size = puff2.s0 + (puff2.s1 - puff2.s0) * (1 - Math.exp(-puff2.t * puff2.gk)) + puff2.sg * Math.min(puff2.t, GROWTH_CAP_SECONDS);
6930
- puff2.size = size * Math.min(1, puff2.t * ARRIVAL_FRAME + ARRIVAL_BASE);
6931
- puff2.age = age;
6932
- puff2.heat = heat;
6974
+ for (const puff3 of pool.items) {
6975
+ const age = puff3.t / puff3.life;
6976
+ const heat = puff3.h0 > 0 ? puff3.h0 * Math.pow(Math.max(0, 1 - puff3.t / puff3.hl), 1.25) : 0;
6977
+ if (puff3.ring) moveOnRing(puff3, puff3.ring, wind);
6978
+ else moveFree(puff3, dt, age, heat, wind);
6979
+ const size = puff3.s0 + (puff3.s1 - puff3.s0) * (1 - Math.exp(-puff3.t * puff3.gk)) + puff3.sg * Math.min(puff3.t, GROWTH_CAP_SECONDS);
6980
+ puff3.size = size * Math.min(1, puff3.t * ARRIVAL_FRAME + ARRIVAL_BASE);
6981
+ puff3.age = age;
6982
+ puff3.heat = heat;
6933
6983
  }
6934
6984
  }
6935
6985
  var GROUND_LIFT = 0.02;
@@ -6986,9 +7036,9 @@ var GameTestRoom = (() => {
6986
7036
  var HOT_GLOW_COLOR = [1, 0.45, 0.12];
6987
7037
  function addPuff(core, random, init) {
6988
7038
  core.spawned.puffs++;
6989
- const puff2 = spawnPuff(core.puffs, random, init);
6990
- core.trace?.puff?.(puff2);
6991
- return puff2;
7039
+ const puff3 = spawnPuff(core.puffs, random, init);
7040
+ core.trace?.puff?.(puff3);
7041
+ return puff3;
6992
7042
  }
6993
7043
  function addSpark(core, init) {
6994
7044
  core.spawned.sparks++;
@@ -7170,6 +7220,7 @@ var GameTestRoom = (() => {
7170
7220
  var neg = call("neg");
7171
7221
  var abs = call("abs");
7172
7222
  var sqrt = call("sqrt");
7223
+ var exp = call("exp");
7173
7224
  var sin = call("sin");
7174
7225
  var cos = call("cos");
7175
7226
  var atan2 = call("atan2");
@@ -7215,10 +7266,12 @@ var GameTestRoom = (() => {
7215
7266
  var INDEX = { $: "index" };
7216
7267
  var QUALITY = { $: "quality" };
7217
7268
  var FRESH = { $: "fresh" };
7269
+ var DIRECTION = { $: "direction" };
7218
7270
  var CAMERA_POSITION = { $: "cameraPosition" };
7219
7271
  var burst = (system, count, spawn2) => ({ op: "burst", system, count, spawn: spawn2 });
7220
7272
  var spawn = (system, options) => ({ op: "spawn", system, spawn: options });
7221
7273
  var emit = (key, system, rate, options) => ({ op: "emit", key, system, rate, spawn: options });
7274
+ var mesh = (options) => ({ op: "mesh", spawn: options });
7222
7275
  var debris = (options) => ({ op: "debris", spawn: options });
7223
7276
  var decal = (...args) => ({ op: "decal", args });
7224
7277
  var ring = (...args) => ({ op: "ring", args });
@@ -8230,6 +8283,241 @@ var GameTestRoom = (() => {
8230
8283
  )
8231
8284
  ]
8232
8285
  };
8286
+ var SPOTS_X = [-3, 1, 5];
8287
+ var SPOTS_Z = [-1, -2.4, 0.4];
8288
+ var FOG_AND_ASH = {
8289
+ id: "fog_and_ash",
8290
+ pageId: "neblina",
8291
+ team: "world",
8292
+ budget: { particles: 120, lights: 1 },
8293
+ loop: 0,
8294
+ view: { eye: [3, 1.8, 7.5], target: [-3, 1.2, -1] },
8295
+ setup: [
8296
+ cue("fog", { density: 0.075 }),
8297
+ cue("lamp_cone", { visible: true }),
8298
+ cue("zombie_visible", { index: -1, visible: false }),
8299
+ repeat(
8300
+ SPOTS_X.length,
8301
+ set("turn", rnd(-0.6, 0.6)),
8302
+ behavior("walkers", "place", { index: INDEX, x: pick(SPOTS_X, INDEX), z: pick(SPOTS_Z, INDEX), facing: variable("turn") })
8303
+ )
8304
+ ],
8305
+ timeline: [],
8306
+ tick: [
8307
+ emit("fog", "normal", 5, { p: [rnd(-14, 10), rnd(0.2, 1.1), rnd(-3, 8)], v: [rnd(0.15, 0.35), 0, rnd(-0.05, 0.05)], sp: 0, life: [9, 13], size: [3, 5], grow: 1.4, c0: 8227488, a: 0.13, fi: 0.35 }),
8308
+ emit("ash", "normal", 14, { p: [rnd(-10, 8), rnd(4, 6), rnd(-3, 7)], v: [rnd(0.1, 0.4), -0.35, rnd(-0.1, 0.1)], sp: [0.05, 0.15], life: [8, 12], size: [0.025, 0.04], c0: 10525328, a: 0.75, fi: 0.1, gnd: 1 })
8309
+ ]
8310
+ };
8311
+ var RAIN_CAPACITY = 4e3;
8312
+ var FALL_SPEED = 15;
8313
+ var WIND_PER_FALL = 0.07;
8314
+ var STREAK_SLANT = 0.04;
8315
+ var STREAK_LENGTH = 0.6;
8316
+ var BOX_X = [-15, 15];
8317
+ var BOX_Z = [-6, 16];
8318
+ var START_HEIGHT = [0, 16];
8319
+ var REENTRY_HEIGHT = [14, 17];
8320
+ var SPLASH_CHANCE = 0.35;
8321
+ var SPLASH_RANGE = 12;
8322
+ var SPRITE_CHANCE = 0.5;
8323
+ var UP9 = [0, 1, 0];
8324
+ var WORKSHOP_PUDDLES = [
8325
+ [-1.5, 2.2, 1.6, 0.9],
8326
+ [3, 3.6, 1.1, 0.7],
8327
+ [-6, 3.2, 1.3, 0.8],
8328
+ [1.4, -2.2, 0.9, 0.5]
8329
+ ];
8330
+ var inPuddle = (puddles, x, z) => puddles.some((p) => ((x - p[0]) / p[2]) ** 2 + ((z - p[1]) / p[3]) ** 2 < 1);
8331
+ var RainField = class {
8332
+ constructor(engine) {
8333
+ __publicField(this, "engine", engine);
8334
+ __publicField(this, "positions", new Float32Array(RAIN_CAPACITY * 6));
8335
+ __publicField(this, "count", 0);
8336
+ __publicField(this, "wind", 1);
8337
+ __publicField(this, "on", false);
8338
+ __publicField(this, "centerX", 0);
8339
+ __publicField(this, "centerZ", 0);
8340
+ __publicField(this, "puddles", WORKSHOP_PUDDLES);
8341
+ __publicField(this, "puddleAt");
8342
+ }
8343
+ /** `initRain` (W321): `count` streaks (thinned by the quality), each drawn at a random place in the box. */
8344
+ init(count, wind) {
8345
+ const { random } = this.engine;
8346
+ this.count = Math.min(RAIN_CAPACITY, Math.round(count * this.engine.context.quality.mult));
8347
+ this.wind = wind;
8348
+ this.on = true;
8349
+ for (let index = 0; index < this.count; index++) {
8350
+ const offset = index * 6;
8351
+ const x = random.between(BOX_X[0], BOX_X[1]);
8352
+ const y = random.between(START_HEIGHT[0], START_HEIGHT[1]);
8353
+ const z = random.between(BOX_Z[0], BOX_Z[1]);
8354
+ this.set(offset, x, y, z);
8355
+ }
8356
+ }
8357
+ /**
8358
+ * What a game does with the field every frame: the box follows the camera (`centerX`, `centerZ`), the streaks thin or thicken to `count` (a new streak
8359
+ * starts at a random place of the box, as `init` draws it) and the wind slants them. The golden never calls it.
8360
+ */
8361
+ drive(count, wind, centerX, centerZ) {
8362
+ const { random } = this.engine;
8363
+ const target = Math.max(0, Math.min(RAIN_CAPACITY, Math.round(count * this.engine.context.quality.mult)));
8364
+ this.wind = wind;
8365
+ this.centerX = centerX;
8366
+ this.centerZ = centerZ;
8367
+ this.on = target > 0;
8368
+ for (let index = this.count; index < target; index++) {
8369
+ this.set(index * 6, random.between(BOX_X[0], BOX_X[1]) + centerX, random.between(START_HEIGHT[0], START_HEIGHT[1]), random.between(BOX_Z[0], BOX_Z[1]) + centerZ);
8370
+ }
8371
+ this.count = target;
8372
+ }
8373
+ /** `updateRain` (W322-329). `camera` is where the eye is; `splash` lets the drops that land leave a splash. */
8374
+ update(dt, splash, camera2) {
8375
+ if (!this.on) return;
8376
+ const { random, context } = this.engine;
8377
+ const positions = this.positions;
8378
+ const fall = FALL_SPEED * dt;
8379
+ const slant = this.wind * fall * WIND_PER_FALL;
8380
+ const reach = SPLASH_RANGE / (1 + context.quality.level);
8381
+ for (let index = 0; index < this.count; index++) {
8382
+ const o = index * 6;
8383
+ positions[o] += slant;
8384
+ positions[o + 3] += slant;
8385
+ positions[o + 1] -= fall;
8386
+ positions[o + 4] -= fall;
8387
+ const ground = context.groundAt(positions[o], positions[o + 2]);
8388
+ if (positions[o + 4] < ground) {
8389
+ const x = positions[o];
8390
+ const z = positions[o + 2];
8391
+ if (splash && random.next() < SPLASH_CHANCE) {
8392
+ const distance4 = Math.hypot(x - camera2[0], z - camera2[2]);
8393
+ if (distance4 < reach) {
8394
+ if (random.next() < SPRITE_CHANCE) {
8395
+ this.engine.spawn("normal", { p: [x, ground + 0.01, z], dir: UP9, cone: 0.6, sp: [0.6, 1.3], life: [0.18, 0.3], size: 0.035, c0: 12110044, a: 0.6, g: 9.8, fi: 0 });
8396
+ }
8397
+ if (this.puddleAt ? this.puddleAt(x, z) : inPuddle(this.puddles, x, z)) {
8398
+ this.engine.ring([[x, ground + 0.012, z], 0.01, random.between(0.12, 0.25), random.between(0.4, 0.6), 6978192]);
8399
+ }
8400
+ }
8401
+ }
8402
+ const nx = random.between(BOX_X[0], BOX_X[1]) + this.centerX;
8403
+ const ny = random.between(REENTRY_HEIGHT[0], REENTRY_HEIGHT[1]);
8404
+ const nz = random.between(BOX_Z[0], BOX_Z[1]) + this.centerZ;
8405
+ this.set(o, nx, ny, nz);
8406
+ }
8407
+ }
8408
+ }
8409
+ /** `rain.visible = false; RN.on = false` (W440): the field stops; the streaks stay where they were. */
8410
+ clear() {
8411
+ this.on = false;
8412
+ }
8413
+ set(offset, x, y, z) {
8414
+ const positions = this.positions;
8415
+ positions[offset] = x;
8416
+ positions[offset + 1] = y;
8417
+ positions[offset + 2] = z;
8418
+ positions[offset + 3] = x + this.wind * STREAK_SLANT;
8419
+ positions[offset + 4] = y - STREAK_LENGTH;
8420
+ positions[offset + 5] = z;
8421
+ }
8422
+ };
8423
+ var DROPS = [900, 2200, 4e3];
8424
+ var SLANT = [0.3, 1.2, 4.5];
8425
+ var STORM = 2;
8426
+ var FLASH_STEPS = ite(lt(variable("flash"), 0.08), 1, ite(lt(variable("flash"), 0.16), 0.2, ite(lt(variable("flash"), 0.26), 0.8, 0)));
8427
+ var RAIN_AND_PUDDLES = {
8428
+ id: "rain_and_puddles",
8429
+ pageId: "chuva",
8430
+ team: "world",
8431
+ params: { id: "intensity", options: ["drizzle", "rain", "storm"], default: 1 },
8432
+ budget: { particles: 4e3, decals: 4 },
8433
+ loop: 0,
8434
+ view: { eye: [2, 2, 7], target: [-1, 0.6, 1] },
8435
+ setup: [
8436
+ cue("lamp_cone", { visible: true }),
8437
+ ...WORKSHOP_PUDDLES.map(([x, z, radiusX, radiusZ]) => decal(x, 0, z, radiusX * 2, 790550, { grow: 0, life: 1e6, ar: radiusZ / radiusX }))
8438
+ ],
8439
+ timeline: [
8440
+ behavior("rain", "init", { count: ite(eq(PARAM, 0), DROPS[0], ite(eq(PARAM, 1), DROPS[1], DROPS[2])), wind: ite(eq(PARAM, 0), SLANT[0], ite(eq(PARAM, 1), SLANT[1], SLANT[2])) }),
8441
+ set("nextStrike", rnd(2, 4)),
8442
+ set("flash", -1)
8443
+ ],
8444
+ tick: [
8445
+ behavior("rain", "update", { dt: DELTA, splash: gt(PARAM, 0) }),
8446
+ when(eq(PARAM, STORM), [
8447
+ when(gt(TIME, variable("nextStrike")), [set("nextStrike", add(TIME, rnd(3.5, 6))), set("flash", 0)]),
8448
+ when(ge(variable("flash"), 0), [
8449
+ set("flash", add(variable("flash"), DELTA)),
8450
+ set("light", FLASH_STEPS),
8451
+ cue("lightning", { intensity: variable("light") }),
8452
+ screen({ max: { white: mul(variable("light"), 0.12) } }),
8453
+ when(gt(variable("flash"), 0.4), [set("flash", -1), cue("lightning", { intensity: 0 })])
8454
+ ]),
8455
+ emit("sheet", "normal", 2.5, { p: [-12, 2.5, rnd(-1, 8)], box: [0, 2, 0], v: [7, -0.5, 0], sp: 0, life: [3, 4], size: [3, 5], grow: 1.3, c0: 9083568, a: 0.06, fi: 0.3 })
8456
+ ])
8457
+ ]
8458
+ };
8459
+ var CAR_ORIGIN2 = [5.6, 0, 0.8];
8460
+ var CAR_ROTATION2 = [0, -0.4, 0];
8461
+ var carPoint = (x, y, z) => localToWorld(CAR_ORIGIN2, CAR_ROTATION2, [x, y, z]);
8462
+ var carDirection = (from, to) => normalized([to[0] - from[0], to[1] - from[1], to[2] - from[2]]);
8463
+ var BEAM_FROM = carPoint(0, 0.74, 2.15);
8464
+ var BEAM_DIRECTION = carDirection(BEAM_FROM, carPoint(0, 0.74, 3.15));
8465
+ var BEAM_SIDE = [BEAM_DIRECTION[2], 0, -BEAM_DIRECTION[0]];
8466
+ var WALKERS = 6;
8467
+ var WALK_SPEED2 = 0.7;
8468
+ var DROPS2 = 1800;
8469
+ var SLANT2 = 0.8;
8470
+ var startWalking = [
8471
+ set("spot", query("car", "point", rnd(-2, 2), 0, rnd(8, 18))),
8472
+ set("goal", query("car", "point", rnd(-0.6, 0.6), 0, 2.6)),
8473
+ behavior("walkers", "place", { index: INDEX, x: pick(variable("spot"), 0), z: pick(variable("spot"), 2), facing: 0 }),
8474
+ behavior("walkers", "walk", { index: INDEX, to: variable("goal"), speed: WALK_SPEED2 })
8475
+ ];
8476
+ var HEADLIGHTS_IN_RAIN = {
8477
+ id: "headlights_in_rain",
8478
+ pageId: "farol",
8479
+ team: "vehicles",
8480
+ budget: { particles: 600, lights: 2 },
8481
+ loop: 0,
8482
+ view: { eye: [-2.5, 2, 5.5], target: [4, 1, 6] },
8483
+ setup: [
8484
+ cue("fog", { density: 0.05 }),
8485
+ cue("headlights", { on: true, intensity: 2.4, color: 16773584 }),
8486
+ behavior("rain", "init", { count: DROPS2, wind: SLANT2 }),
8487
+ repeat(WALKERS, ...startWalking)
8488
+ ],
8489
+ timeline: [],
8490
+ tick: [
8491
+ behavior("rain", "update", { dt: DELTA, splash: true }),
8492
+ emit(
8493
+ "lit",
8494
+ "streak",
8495
+ 600,
8496
+ local(
8497
+ "depth",
8498
+ rnd(1, 13),
8499
+ local(
8500
+ "spread",
8501
+ mul(variable("depth"), 0.33, sqrt(rand())),
8502
+ local(
8503
+ "turn",
8504
+ rnd(0, 6.283),
8505
+ local("base", vadd(BEAM_FROM, vscale(BEAM_DIRECTION, variable("depth")), vscale(BEAM_SIDE, mul(cos(variable("turn")), variable("spread")))), {
8506
+ p: vwith(variable("base"), 1, add(vy(variable("base")), mul(sin(variable("turn")), variable("spread"), 0.6))),
8507
+ v: [0.8, -14, 0],
8508
+ sp: 0,
8509
+ life: 0.06,
8510
+ len: 0.035,
8511
+ c0: 13622512,
8512
+ a: 0.9
8513
+ })
8514
+ )
8515
+ )
8516
+ )
8517
+ ),
8518
+ repeat(WALKERS, when(not(query("walkers", "is_walking", INDEX)), startWalking))
8519
+ ]
8520
+ };
8233
8521
  var YAW = -Math.PI / 2;
8234
8522
  var COS = Math.cos(YAW);
8235
8523
  var SIN = Math.sin(YAW);
@@ -8257,10 +8545,6 @@ var GameTestRoom = (() => {
8257
8545
  fireLight(0, [vx(variable("contact")), 0.34, vz(variable("contact"))], 1.6, mul(TIME, 3))
8258
8546
  ]
8259
8547
  };
8260
- var CAR_ORIGIN2 = [5.6, 0, 0.8];
8261
- var CAR_ROTATION2 = [0, -0.4, 0];
8262
- var carPoint = (x, y, z) => localToWorld(CAR_ORIGIN2, CAR_ROTATION2, [x, y, z]);
8263
- var carDirection = (from, to) => normalized([to[0] - from[0], to[1] - from[1], to[2] - from[2]]);
8264
8548
  var HOOD = carPoint(0, 1.02, 1.4);
8265
8549
  var EXHAUST = carPoint(0.5, 0.3, -2.2);
8266
8550
  var EXHAUST_DIRECTION = carDirection(EXHAUST, carPoint(0.5, 0.3, -3));
@@ -8294,7 +8578,7 @@ var GameTestRoom = (() => {
8294
8578
  ], [fireLightOff(0)])
8295
8579
  ]
8296
8580
  };
8297
- var UP9 = [0, 1, 0];
8581
+ var UP10 = [0, 1, 0];
8298
8582
  var doorVelocity = (side) => {
8299
8583
  const out = carPoint(side * 4, 3, 0.3);
8300
8584
  const centre = carPoint(0, 0, 0.3);
@@ -8304,7 +8588,7 @@ var GameTestRoom = (() => {
8304
8588
  call2("explode", vadd(query("car", "position"), [0, 0.6, 0]), 1.8, 2759188),
8305
8589
  cue("car_burnt"),
8306
8590
  behavior("car", "launch", { rise: 4.5, spin: 0.9 }),
8307
- repeat(scaled(30), debris({ p: vadd(query("car", "position"), [0, 1.3, 0]), dir: UP9, cone: 1.3, sp: [2, 6], size: [0.06, 6e-3, 0.06], c: 11061464, life: 20 })),
8591
+ repeat(scaled(30), debris({ p: vadd(query("car", "position"), [0, 1.3, 0]), dir: UP10, cone: 1.3, sp: [2, 6], size: [0.06, 6e-3, 0.06], c: 11061464, life: 20 })),
8308
8592
  debris({ p: carPoint(0, 1, 1.4), v: [rnd(-1, 1), 9, rnd(-1, 1)], sp: 0, size: [1.6, 0.06, 1.2], c: 3808536, spin: 6, life: 20, bounce: 0.2 }),
8309
8593
  ...[-1, 1].map((side) => debris({ p: carPoint(side * 0.95, 0.7, 0.3), v: doorVelocity(side), sp: 0, size: [0.06, 0.7, 1.1], c: 3808536, spin: 5, life: 20, bounce: 0.2 })),
8310
8594
  debris({ p: carPoint(0.95, 0.38, 1.35), v: [3, 4, 2], sp: 0, size: [0.3, 0.76, 0.76], c: 1118481, spin: 8, life: 20, bounce: 0.4 })
@@ -8474,7 +8758,7 @@ var GameTestRoom = (() => {
8474
8758
  ])
8475
8759
  ]
8476
8760
  };
8477
- var WALKERS = 6;
8761
+ var WALKERS2 = 6;
8478
8762
  var NEAR = 9;
8479
8763
  var RANGE = 7;
8480
8764
  var RESTING_BPM = 70;
@@ -8490,7 +8774,7 @@ var GameTestRoom = (() => {
8490
8774
  setup: [
8491
8775
  cue("zombie_visible", { index: -1, visible: false }),
8492
8776
  repeat(
8493
- WALKERS,
8777
+ WALKERS2,
8494
8778
  set("startX", rnd(-4, 4)),
8495
8779
  set("startZ", sub(rnd(-4, -2), mul(INDEX, 0.6))),
8496
8780
  behavior("walkers", "place", { index: INDEX, x: variable("startX"), z: variable("startZ"), facing: 0 }),
@@ -8500,7 +8784,7 @@ var GameTestRoom = (() => {
8500
8784
  timeline: [],
8501
8785
  tick: [
8502
8786
  set("nearest", 99),
8503
- repeat(WALKERS, set("nearest", min(variable("nearest"), vdist(walkerPoint, CAMERA_POSITION)))),
8787
+ repeat(WALKERS2, set("nearest", min(variable("nearest"), vdist(walkerPoint, CAMERA_POSITION)))),
8504
8788
  set("fear", clamp3(div(sub(NEAR, variable("nearest")), RANGE), 0, 1)),
8505
8789
  set("bpm", add(RESTING_BPM, mul(BPM_RISE, variable("fear")))),
8506
8790
  when(lt(mod(div(mul(TIME, variable("bpm")), 60), 1), div(mul(DELTA, variable("bpm")), 60)), [screen({ set: { beat: add(0.4, mul(0.6, variable("fear"))) } })]),
@@ -8567,7 +8851,7 @@ var GameTestRoom = (() => {
8567
8851
  ]
8568
8852
  };
8569
8853
  var FACING4 = [0, 0, 1];
8570
- var UP10 = [0, 1, 0];
8854
+ var UP11 = [0, 1, 0];
8571
8855
  var ORIGIN = [0, 0, 0];
8572
8856
  var FORWARD4 = [0, 0, -1];
8573
8857
  var IMPACT_MATERIALS = ["concrete", "metal", "wood", "glass", "flesh", "water"];
@@ -8697,12 +8981,157 @@ var GameTestRoom = (() => {
8697
8981
  team: "destruction",
8698
8982
  budget: { particles: 20, debris: 8 },
8699
8983
  loop: 2,
8700
- timeline: [call2("chips", ORIGIN, UP10, 8, 0.9), call2("rust_at", ORIGIN, 6)]
8984
+ timeline: [call2("chips", ORIGIN, UP11, 8, 0.9), call2("rust_at", ORIGIN, 6)]
8701
8985
  }
8702
8986
  ];
8703
- var ALL_EFFECTS = [BLOOD_HIT, HEADSHOT, DISMEMBER, BLOATER_CLOUD, SCREAMER_SHRIEK, CORPSE_DECAY, REANIMATION, EYES_IN_FOG, MUZZLE_FLASH, IMPACT_BY_MATERIAL, PENETRATION, CASINGS_AND_MAGAZINE, MOLOTOV, GRENADE, EXPLOSIVE_BARREL, SHOPFRONT_GLASS, DOOR_STAGES, BUILDING_COLLAPSE, BLOCK_WALL, METAL_DOOR_EFFECT, IRON_RAILING, WOODEN_FENCE, HIT_POINT_BREAKAGE, PRESSURE_CYLINDERS, BUILDING_FIRE, SCRAPING_SPARKS, ENGINE_SMOKE, FUEL_LEAK, CAR_EXPLOSION, WINDSHIELD, DAMAGE_AND_BITE, INFECTION_PHASES, PANIC, BLOATER_BLINDNESS, CAMPFIRE, COUGH_AND_BREATH];
8987
+ var PI = Math.PI;
8988
+ var SMOKE = { color: 9079430, width: 10, height: 8, gravity: -0.08, drag: 2.2, growth: 2.1 };
8989
+ var FLASH_SECONDS = 0.055;
8990
+ var FLASH_RAMP = [0.6, 0.9];
8991
+ var FLASH_OPACITY = 0.95;
8992
+ var FLAME_COLORS = [16742946, 16760896, 13121050, 16773296];
8993
+ var FLAME_ADDITIVE = 3;
8994
+ var ORIGIN2 = [0, 0, 0];
8995
+ var SIZE = variable("size");
8996
+ var wide = (width) => mul(sub(rand(), 0.5), width);
8997
+ var puff2 = (point, velocity, world, radius, life, opacity) => mesh({
8998
+ shape: "sphere",
8999
+ p: point,
9000
+ v: velocity,
9001
+ wv: world,
9002
+ r: radius,
9003
+ seg: SMOKE.width,
9004
+ seg2: SMOKE.height,
9005
+ life,
9006
+ c0: SMOKE.color,
9007
+ a: opacity,
9008
+ fade: true,
9009
+ lit: true,
9010
+ g: SMOKE.gravity,
9011
+ drag: SMOKE.drag,
9012
+ grow: SMOKE.growth
9013
+ });
9014
+ var flameBit = (point, velocity, world, radius, life) => [
9015
+ set("bitPoint", point),
9016
+ set("bitVelocity", velocity),
9017
+ set("bitWorld", world),
9018
+ set("bitRadius", radius),
9019
+ set("bitLife", life),
9020
+ set("bitColour", floor(mul(rand(), FLAME_COLORS.length))),
9021
+ mesh({
9022
+ shape: "icosahedron",
9023
+ p: variable("bitPoint"),
9024
+ v: variable("bitVelocity"),
9025
+ wv: variable("bitWorld"),
9026
+ r: variable("bitRadius"),
9027
+ seg: 1,
9028
+ rot: [mul(rand(), 3), mul(rand(), 3), 0],
9029
+ spin: [3, 4, 2],
9030
+ life: variable("bitLife"),
9031
+ c0: pick(FLAME_COLORS, variable("bitColour")),
9032
+ add: eq(variable("bitColour"), FLAME_ADDITIVE),
9033
+ a: 0.9,
9034
+ fade: true,
9035
+ g: -1.4,
9036
+ drag: 1.4,
9037
+ grow: 2.2
9038
+ })
9039
+ ];
9040
+ var flashPiece = (shape, radius, height, width, at3, rot, color) => mesh({ shape, r: radius, ...height === void 0 ? {} : { h: height }, seg: width, seg2: shape === "sphere" ? 8 : 1, p: ORIGIN2, at: at3, rot, roll: variable("roll"), ramp: FLASH_RAMP, life: FLASH_SECONDS, c0: color, a: FLASH_OPACITY, add: true });
9041
+ var MUZZLE_FLASH2 = {
9042
+ id: "viewmodel_muzzle_flash",
9043
+ pageId: "event:viewmodel_muzzle_flash",
9044
+ team: "weapons",
9045
+ budget: { particles: 12, lights: 1 },
9046
+ loop: 0.1,
9047
+ timeline: [
9048
+ set("angles", []),
9049
+ repeat(4, append("angles", add(div(mul(INDEX, PI), 2), mul(rand(), 0.6)))),
9050
+ set("roll", mul(rand(), PI)),
9051
+ flash(ORIGIN2, 16756832, mul(4, SIZE), FLASH_SECONDS, mul(2, SIZE), true, true),
9052
+ flashPiece("cone_open", mul(0.013, SIZE), mul(0.085, SIZE), 10, [mul(0.042, SIZE), 0, 0], [0, 0, -PI / 2], 16757575),
9053
+ repeat(
9054
+ 4,
9055
+ set("angle", pick(variable("angles"), INDEX)),
9056
+ flashPiece(
9057
+ "cone",
9058
+ mul(5e-3, SIZE),
9059
+ mul(0.04, SIZE),
9060
+ 6,
9061
+ [mul(0.012, SIZE), mul(cos(variable("angle")), 0.016, SIZE), mul(sin(variable("angle")), 0.016, SIZE)],
9062
+ [variable("angle"), 0, -PI / 2],
9063
+ 16757575
9064
+ )
9065
+ ),
9066
+ flashPiece("sphere", mul(0.016, SIZE), void 0, 12, [mul(8e-3, SIZE), 0, 0], [0, 0, 0], 16773568),
9067
+ repeat(
9068
+ 6,
9069
+ puff2(
9070
+ ORIGIN2,
9071
+ [add(0.15, mul(rand(), 0.35)), add(0.03, mul(rand(), 0.12)), mul(sub(rand(), 0.5), 0.14)],
9072
+ [0, 0, 0],
9073
+ mul(0.013, SIZE),
9074
+ add(1.3, mul(rand(), 0.6)),
9075
+ 0.45
9076
+ )
9077
+ )
9078
+ ]
9079
+ };
9080
+ var SMOKE_PUFF = {
9081
+ id: "weapon_smoke_puff",
9082
+ pageId: "event:weapon_smoke_puff",
9083
+ team: "weapons",
9084
+ budget: { particles: 1 },
9085
+ loop: 0.1,
9086
+ timeline: [puff2(ORIGIN2, void 0, [variable("vx"), variable("vy"), variable("vz")], variable("radius"), variable("life"), variable("opacity"))]
9087
+ };
9088
+ var BACKBLAST = {
9089
+ id: "launcher_backblast",
9090
+ pageId: "event:launcher_backblast",
9091
+ team: "weapons",
9092
+ budget: { particles: 40 },
9093
+ loop: 0.1,
9094
+ timeline: [
9095
+ repeat(26, puff2(ORIGIN2, [neg(add(1.5, mul(rand(), 3))), 0, 0], [wide(1.4), wide(0.8), wide(1.4)], add(0.03, mul(rand(), 0.03)), add(1.8, mul(rand(), 1.4)), 0.5)),
9096
+ repeat(14, ...flameBit(ORIGIN2, [neg(add(2, mul(rand(), 2))), 0, 0], [wide(1), wide(0.6), wide(1)], 0.02, 0.25))
9097
+ ]
9098
+ };
9099
+ var FLAME_JET = {
9100
+ id: "flame_jet",
9101
+ pageId: "event:flame_jet",
9102
+ team: "weapons",
9103
+ budget: { particles: 30 },
9104
+ loop: 0.1,
9105
+ timeline: [
9106
+ repeat(
9107
+ floor(mul(420, variable("dt"), variable("scale"))),
9108
+ set("speed", mul(add(2.2, mul(rand(), 1.1)), variable("scale"))),
9109
+ ...flameBit(
9110
+ [mul(rand(), variable("speed"), min(variable("dt"), 0.3)), 0, 0],
9111
+ [variable("speed"), 0, 0],
9112
+ [wide(0.35), mul(sub(rand(), 0.3), 0.3), wide(0.35)],
9113
+ add(0.02, mul(rand(), 0.022)),
9114
+ add(0.4, mul(rand(), 0.35))
9115
+ )
9116
+ ),
9117
+ when(lt(rand(), 0.5), [
9118
+ puff2([add(mul(1.1, variable("scale")), mul(rand(), 0.4)), 0, 0], void 0, [mul(vx(DIRECTION), 0.6), 0.35, mul(vz(DIRECTION), 0.6)], 0.03, 1.6, 0.4)
9119
+ ])
9120
+ ]
9121
+ };
9122
+ var VIEWMODEL_EFFECTS = [MUZZLE_FLASH2, SMOKE_PUFF, BACKBLAST, FLAME_JET];
9123
+ var aliasOf = (base, id, changes) => ({ ...base, id, pageId: `vehicle:${id}`, anchor: CAR_ORIGIN2, ...changes });
9124
+ var VEHICLE_EFFECTS = [
9125
+ /** The engine on fire: `engine_smoke` at its worst condition (black smoke, flames in the hood, a light). */
9126
+ aliasOf(ENGINE_SMOKE, "vehicle_fire", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 3 }, loop: 12 }),
9127
+ /** The exhaust of a car under way: `engine_smoke` in the best condition, for half a second so a moving car asks again. */
9128
+ aliasOf(ENGINE_SMOKE, "vehicle_exhaust", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 0 }, loop: 0.5 }),
9129
+ /** A hole in the tank: the page's own petrol leak (drips, pool, sparks, fire running to the tank). */
9130
+ aliasOf(FUEL_LEAK, "vehicle_leak", {})
9131
+ ];
9132
+ var ALL_EFFECTS = [BLOOD_HIT, HEADSHOT, DISMEMBER, BLOATER_CLOUD, SCREAMER_SHRIEK, CORPSE_DECAY, REANIMATION, EYES_IN_FOG, MUZZLE_FLASH, IMPACT_BY_MATERIAL, PENETRATION, CASINGS_AND_MAGAZINE, MOLOTOV, GRENADE, EXPLOSIVE_BARREL, SHOPFRONT_GLASS, DOOR_STAGES, BUILDING_COLLAPSE, BLOCK_WALL, METAL_DOOR_EFFECT, IRON_RAILING, WOODEN_FENCE, HIT_POINT_BREAKAGE, PRESSURE_CYLINDERS, BUILDING_FIRE, RAIN_AND_PUDDLES, FOG_AND_ASH, SCRAPING_SPARKS, ENGINE_SMOKE, FUEL_LEAK, CAR_EXPLOSION, WINDSHIELD, HEADLIGHTS_IN_RAIN, DAMAGE_AND_BITE, INFECTION_PHASES, PANIC, BLOATER_BLINDNESS, CAMPFIRE, COUGH_AND_BREATH];
8704
9133
  var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
8705
- var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries(EVENT_EFFECTS.map((effect) => [effect.id, effect])) };
9134
+ var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS, ...VEHICLE_EFFECTS].map((effect) => [effect.id, effect])) };
8706
9135
  var LEHMER_MULTIPLIER = 16807;
8707
9136
  var LEHMER_MODULUS = 2147483647;
8708
9137
  var HASH_MIX = 2654435761;
@@ -8798,7 +9227,7 @@ var GameTestRoom = (() => {
8798
9227
  }
8799
9228
  var COLUMNS = 6;
8800
9229
  var ROWS = 5;
8801
- var ORIGIN2 = [-2.2, 0, 2.6];
9230
+ var ORIGIN3 = [-2.2, 0, 2.6];
8802
9231
  var BLOCK_SIZE = [0.5, 0.44, 0.25];
8803
9232
  var OBJECT_ID_DRAWS2 = 4;
8804
9233
  var FLOOR2 = 0.13;
@@ -8841,7 +9270,7 @@ var GameTestRoom = (() => {
8841
9270
  const chipScale = random.between(0.8, 1.3) * scale2;
8842
9271
  for (let draw2 = 0; draw2 < OBJECT_ID_DRAWS2; draw2++) random.next();
8843
9272
  best.holes.push({ x: offsetX, y: offsetY, scale: scale2, chipTurn, chipScale });
8844
- impact(host.engine, "concrete", [ORIGIN2[0] + x, y, ORIGIN2[2] + 0.13], [0, 0, 1], 0.8);
9273
+ impact(host.engine, "concrete", [ORIGIN3[0] + x, y, ORIGIN3[2] + 0.13], [0, 0, 1], 0.8);
8845
9274
  } else if (action === "collapse") {
8846
9275
  const { x, y } = args;
8847
9276
  for (const block of blocks) {
@@ -8891,7 +9320,7 @@ var GameTestRoom = (() => {
8891
9320
  }
8892
9321
  };
8893
9322
  }
8894
- var ORIGIN3 = [5.6, 0, 0.8];
9323
+ var ORIGIN4 = [5.6, 0, 0.8];
8895
9324
  var MAX_TILT = 0.25;
8896
9325
  function createCarBehavior() {
8897
9326
  let y = 0;
@@ -8923,8 +9352,13 @@ var GameTestRoom = (() => {
8923
9352
  }
8924
9353
  }
8925
9354
  },
8926
- query(_host, name) {
8927
- if (name === "position") return [ORIGIN3[0], y, ORIGIN3[2]];
9355
+ query(_host, name, args) {
9356
+ if (name === "position") return [ORIGIN4[0], y, ORIGIN4[2]];
9357
+ if (name === "point") {
9358
+ const [x, py, z] = args;
9359
+ const point = carPoint(x, py, z);
9360
+ return [point[0], point[1] + y, point[2]];
9361
+ }
8928
9362
  if (name === "roll") return roll;
8929
9363
  throw new Error(`car: unknown query ${name}`);
8930
9364
  },
@@ -9124,6 +9558,29 @@ var GameTestRoom = (() => {
9124
9558
  }
9125
9559
  };
9126
9560
  }
9561
+ function createRainBehavior() {
9562
+ return {
9563
+ reset() {
9564
+ },
9565
+ act(host, action, args) {
9566
+ const { engine, camera: camera2 } = host;
9567
+ if (action === "init") {
9568
+ const { count, wind } = args;
9569
+ engine.rain.init(count, wind);
9570
+ } else if (action === "update") {
9571
+ const { dt, splash } = args;
9572
+ engine.rain.update(dt, splash, camera2.position);
9573
+ }
9574
+ },
9575
+ query(host, name) {
9576
+ if (name === "count") return host.engine.rain.count;
9577
+ if (name === "on") return host.engine.rain.on;
9578
+ throw new Error(`rain: unknown query ${name}`);
9579
+ },
9580
+ update() {
9581
+ }
9582
+ };
9583
+ }
9127
9584
  var WALKER_COUNT = 10;
9128
9585
  var ARRIVAL_DISTANCE = 0.05;
9129
9586
  var SWAY_AMPLITUDE = 0.04;
@@ -9189,7 +9646,8 @@ var GameTestRoom = (() => {
9189
9646
  blocks: () => createBlocksBehavior(),
9190
9647
  plate: () => createPlateBehavior(),
9191
9648
  cylinder: () => createCylinderBehavior(),
9192
- car: () => createCarBehavior()
9649
+ car: () => createCarBehavior(),
9650
+ rain: () => createRainBehavior()
9193
9651
  };
9194
9652
  var NOOP = () => {
9195
9653
  };
@@ -9260,6 +9718,8 @@ var GameTestRoom = (() => {
9260
9718
  return Math.abs(asNumber(first));
9261
9719
  case "sqrt":
9262
9720
  return Math.sqrt(asNumber(first));
9721
+ case "exp":
9722
+ return Math.exp(asNumber(first));
9263
9723
  case "sin":
9264
9724
  return Math.sin(asNumber(first));
9265
9725
  case "cos":
@@ -9378,7 +9838,7 @@ var GameTestRoom = (() => {
9378
9838
  return evaluateCall(value.$, args.map((entry) => resolve(entry, context)));
9379
9839
  }
9380
9840
  }
9381
- var UP11 = [0, 1, 0];
9841
+ var UP12 = [0, 1, 0];
9382
9842
  var ZERO = [0, 0, 0];
9383
9843
  var FULL_TURN_DRAWN = 6.283;
9384
9844
  function scaledSizes(range2, factor) {
@@ -9391,7 +9851,7 @@ var GameTestRoom = (() => {
9391
9851
  p: centre,
9392
9852
  r: radius,
9393
9853
  flat: true,
9394
- dir: options.dir ?? UP11,
9854
+ dir: options.dir ?? UP12,
9395
9855
  cone: 0.35,
9396
9856
  sp: [0.4, 1.4],
9397
9857
  v: wind,
@@ -9408,7 +9868,7 @@ var GameTestRoom = (() => {
9408
9868
  p: centre,
9409
9869
  r: radius * 0.6,
9410
9870
  flat: true,
9411
- dir: UP11,
9871
+ dir: UP12,
9412
9872
  cone: 0.3,
9413
9873
  sp: [0.3, 0.8],
9414
9874
  life: [0.2, 0.4],
@@ -9423,14 +9883,14 @@ var GameTestRoom = (() => {
9423
9883
  if (engine.quality < 2) {
9424
9884
  engine.emit(`${key}e`, "streak", 10 * factor, dt, () => {
9425
9885
  const jitter = plus(wind, engine.random.between(-0.4, 0.4), 0, engine.random.between(-0.4, 0.4));
9426
- return { p: centre, box: [radius, 0, radius], dir: UP11, cone: 0.5, sp: [1.5, 3.5], v: jitter, life: [0.8, 1.8], len: 0.03, c0: 16756816, c1: 6295552, g: -0.6, drag: 0.4 };
9886
+ return { p: centre, box: [radius, 0, radius], dir: UP12, cone: 0.5, sp: [1.5, 3.5], v: jitter, life: [0.8, 1.8], len: 0.03, c0: 16756816, c1: 6295552, g: -0.6, drag: 0.4 };
9427
9887
  });
9428
9888
  }
9429
9889
  if (options.noSmoke) return;
9430
9890
  engine.emit(`${key}s`, "normal", (options.smoke || 14) * factor, dt, () => ({
9431
9891
  p: plus(centre, 0, 0.8 * sqrtFactor, 0),
9432
9892
  r: radius * 0.7,
9433
- dir: UP11,
9893
+ dir: UP12,
9434
9894
  cone: 0.35,
9435
9895
  sp: [0.6, 1.4],
9436
9896
  v: wind,
@@ -9470,15 +9930,15 @@ var GameTestRoom = (() => {
9470
9930
  engine.screen.shake += 0.7 * scale2;
9471
9931
  engine.screen.white += 0.18 * scale2;
9472
9932
  engine.burst("add", 55 * scale2, { p: point, r: 0.3 * scale2, sp: [2, 7 * scale2], drag: 4, life: [0.25, 0.6], size: [0.7 * scale2, 1.4 * scale2], grow: 1.7, c0: 16773296, c1: 10103304, fi: 0.04 });
9473
- engine.burst("add", 28 * scale2, { p: plus(point, 0, 0.3, 0), r: 0.3, dir: UP11, cone: 0.7, sp: [2, 5 * scale2], drag: 1.5, life: [0.6, 1.1], size: [0.6 * scale2, 1 * scale2], grow: 1.3, c0: 16760928, c1: 5902853, g: -2, fi: 0.05 });
9933
+ engine.burst("add", 28 * scale2, { p: plus(point, 0, 0.3, 0), r: 0.3, dir: UP12, cone: 0.7, sp: [2, 5 * scale2], drag: 1.5, life: [0.6, 1.1], size: [0.6 * scale2, 1 * scale2], grow: 1.3, c0: 16760928, c1: 5902853, g: -2, fi: 0.05 });
9474
9934
  engine.ring([plus(point, 0, 0.05, 0), 0.3, 7 * scale2, 0.55, 16767136]);
9475
9935
  const toCamera = normalized([cameraPosition[0] - point[0], cameraPosition[1] - point[1], cameraPosition[2] - point[2]]);
9476
9936
  engine.ring([plus(point, 0, 1, 0), 0.3, 5 * scale2, 0.35, 16773328, toCamera]);
9477
- engine.burst("normal", 32 * scale2, { p: plus(point, 0, 0.6, 0), r: 0.8 * scale2, dir: UP11, cone: 1.1, sp: [1, 3], drag: 1.2, life: [3, 6], size: [1 * scale2, 1.8 * scale2], grow: 2.6, c0: 2828068, c1: 987155, a: 0.6, fi: 0.18, g: -0.35 });
9937
+ engine.burst("normal", 32 * scale2, { p: plus(point, 0, 0.6, 0), r: 0.8 * scale2, dir: UP12, cone: 1.1, sp: [1, 3], drag: 1.2, life: [3, 6], size: [1 * scale2, 1.8 * scale2], grow: 2.6, c0: 2828068, c1: 987155, a: 0.6, fi: 0.18, g: -0.35 });
9478
9938
  ringOfDust(engine, point, scale2);
9479
- if (engine.quality < 2) engine.burst("streak", 44 * scale2, { p: point, dir: UP11, cone: 1.45, sp: [10, 26], life: [0.3, 0.7], len: 0.03, c0: 16760944, c1: 8396800, g: 9.8, gnd: 2 });
9939
+ if (engine.quality < 2) engine.burst("streak", 44 * scale2, { p: point, dir: UP12, cone: 1.45, sp: [10, 26], life: [0.3, 0.7], len: 0.03, c0: 16760944, c1: 8396800, g: 9.8, gnd: 2 });
9480
9940
  for (let index = 0; index < engine.scaled(16 * scale2); index++) {
9481
- engine.debrisPiece({ p: point, dir: UP11, cone: 1.2, sp: [3, 9], size: [0.12, 0.06, 0.1], k: [0.6, 1.6], c: chipColor || 2762274, life: 9 });
9941
+ engine.debrisPiece({ p: point, dir: UP12, cone: 1.2, sp: [3, 9], size: [0.12, 0.06, 0.1], k: [0.6, 1.6], c: chipColor || 2762274, life: 9 });
9482
9942
  }
9483
9943
  engine.decal([point[0], engine.context.groundAt(point[0], point[2]), point[2], 3 * scale2, 657930, { grow: 0.15, life: 60 }]);
9484
9944
  }
@@ -9621,6 +10081,7 @@ var GameTestRoom = (() => {
9621
10081
  if (!def) return;
9622
10082
  this.schedule.clear();
9623
10083
  this.variables.clear();
10084
+ for (const [name, value] of Object.entries(this.setup.values ?? {})) this.variables.set(name, value);
9624
10085
  for (const behavior2 of this.deps.behaviors()) behavior2.reset(this.host);
9625
10086
  this.time = 0;
9626
10087
  this.within(() => {
@@ -9679,6 +10140,10 @@ var GameTestRoom = (() => {
9679
10140
  return engine.spawn(step.system, this.value(step.spawn));
9680
10141
  case "emit":
9681
10142
  return engine.emit(step.key, step.system, this.value(step.rate), this.dt, () => this.value(step.spawn));
10143
+ case "mesh": {
10144
+ engine.mesh(this.value(step.spawn));
10145
+ return;
10146
+ }
9682
10147
  case "debris":
9683
10148
  return engine.debrisPiece(this.value(step.spawn));
9684
10149
  case "decal":
@@ -9760,7 +10225,7 @@ var GameTestRoom = (() => {
9760
10225
  this.index = previous;
9761
10226
  }
9762
10227
  };
9763
- var ORIGIN4 = [0, 0, 0];
10228
+ var ORIGIN5 = [0, 0, 0];
9764
10229
  var DEFAULT_HEADING = [0, 0, -1];
9765
10230
  var DEFAULT_MAX_INSTANCES = 48;
9766
10231
  var ENDLESS_SECONDS = 60;
@@ -9805,7 +10270,7 @@ var GameTestRoom = (() => {
9805
10270
  }
9806
10271
  function placementOf(entry) {
9807
10272
  const origin = entry.follow ? [entry.origin[0] + positionOf2(entry.follow)[0], entry.origin[1] + positionOf2(entry.follow)[1], entry.origin[2] + positionOf2(entry.follow)[2]] : entry.origin;
9808
- return { origin, anchor: entry.anchor, yaw: entry.yaw };
10273
+ return { origin, anchor: entry.anchor, yaw: entry.yaw, ...entry.orientation ? { orientation: entry.orientation } : {} };
9809
10274
  }
9810
10275
  function drawing(entry, action) {
9811
10276
  const context = engine.context;
@@ -9829,8 +10294,9 @@ var GameTestRoom = (() => {
9829
10294
  random: playOptions.seed !== void 0 ? streamOf(playOptions.seed) : void 0,
9830
10295
  follow: playOptions.follow,
9831
10296
  origin: point,
9832
- anchor: def.anchor ?? ORIGIN4,
10297
+ anchor: def.anchor ?? ORIGIN5,
9833
10298
  yaw: yawOf(heading, playOptions.direction),
10299
+ orientation: playOptions.orientation,
9834
10300
  seconds: playOptions.seconds ?? (def.loop && def.loop > 0 ? def.loop : ENDLESS_SECONDS)
9835
10301
  };
9836
10302
  playing.push(entry);
@@ -9839,7 +10305,8 @@ var GameTestRoom = (() => {
9839
10305
  () => instance.begin(def, paramIndex(def, playOptions.param), {
9840
10306
  placement: placementOf(entry),
9841
10307
  ...playOptions.direction ? { direction: playOptions.direction } : {},
9842
- ...playOptions.normal ? { normal: playOptions.normal } : {}
10308
+ ...playOptions.normal ? { normal: playOptions.normal } : {},
10309
+ ...playOptions.values ? { values: playOptions.values } : {}
9843
10310
  })
9844
10311
  );
9845
10312
  return entry.handle;
@@ -10099,15 +10566,15 @@ var GameTestRoom = (() => {
10099
10566
  return shock;
10100
10567
  }
10101
10568
  function pushPuffs(puffs, shock, reach, dt, age) {
10102
- for (const puff2 of puffs) {
10103
- if (puff2.ring || puff2.t > PUSH_AGE_LIMIT) continue;
10104
- const dx = puff2.x - shock.x;
10105
- const dz = puff2.z - shock.z;
10569
+ for (const puff3 of puffs) {
10570
+ if (puff3.ring || puff3.t > PUSH_AGE_LIMIT) continue;
10571
+ const dx = puff3.x - shock.x;
10572
+ const dz = puff3.z - shock.z;
10106
10573
  const distance4 = Math.hypot(dx, dz) + 1e-3;
10107
- if (Math.abs(distance4 - reach) < PUSH_BAND && puff2.y < PUSH_CEILING) {
10574
+ if (Math.abs(distance4 - reach) < PUSH_BAND && puff3.y < PUSH_CEILING) {
10108
10575
  const strength = 4 * dt * (1 - age);
10109
- puff2.vx += dx / distance4 * strength * 6;
10110
- puff2.vz += dz / distance4 * strength * 6;
10576
+ puff3.vx += dx / distance4 * strength * 6;
10577
+ puff3.vz += dz / distance4 * strength * 6;
10111
10578
  }
10112
10579
  }
10113
10580
  }
@@ -10407,10 +10874,10 @@ var GameTestRoom = (() => {
10407
10874
  const life = random.between(ring2.life * 0.75, ring2.life);
10408
10875
  const h0 = random.between(1, 1.25);
10409
10876
  const hl = random.between(0.9, 1.6);
10410
- const puff2 = addPuff(core, random, { x, y, z, s0: spec.radius * 0.2, s1, sg: spec.radius * 0.06, gk: 3, life, h0, hl, color, kind: 0 });
10411
- puff2.ring = ring2;
10412
- puff2.ph = random.next() * Math.PI * 2;
10413
- puff2.th = random.next() * Math.PI * 2;
10877
+ const puff3 = addPuff(core, random, { x, y, z, s0: spec.radius * 0.2, s1, sg: spec.radius * 0.06, gk: 3, life, h0, hl, color, kind: 0 });
10878
+ puff3.ring = ring2;
10879
+ puff3.ph = random.next() * Math.PI * 2;
10880
+ puff3.th = random.next() * Math.PI * 2;
10414
10881
  }
10415
10882
  const stemCount = Math.round((spec.stem || 18) * Math.max(lod, 0.4));
10416
10883
  for (let index = 0; index < stemCount; index++) {
@@ -11211,7 +11678,7 @@ var GameTestRoom = (() => {
11211
11678
  };
11212
11679
  var TWO_PI_ROUNDED = 6.2832;
11213
11680
  var WIDE_CONE = 3.1;
11214
- var UP12 = [0, 1, 0];
11681
+ var UP13 = [0, 1, 0];
11215
11682
  function drawRange(random, range2) {
11216
11683
  if (Array.isArray(range2)) return random.between(range2[0], range2[1]);
11217
11684
  return range2 || 0;
@@ -11239,7 +11706,7 @@ var GameTestRoom = (() => {
11239
11706
  }
11240
11707
  function coneDirection(random, direction, cone, out) {
11241
11708
  if (cone >= WIDE_CONE) return randomUnit(random, out);
11242
- const [dx, dy, dz] = direction ?? UP12;
11709
+ const [dx, dy, dz] = direction ?? UP13;
11243
11710
  const cosTilt = 1 - random.next() * (1 - Math.cos(cone));
11244
11711
  const sinTilt = Math.sqrt(1 - cosTilt * cosTilt);
11245
11712
  const phase = random.next() * TWO_PI_ROUNDED;
@@ -11359,12 +11826,12 @@ var GameTestRoom = (() => {
11359
11826
  }
11360
11827
  };
11361
11828
  var CAPACITY2 = 400;
11362
- var UP13 = [0, 1, 0];
11829
+ var UP14 = [0, 1, 0];
11363
11830
  var DEFAULT_LIFE2 = 60;
11364
11831
  var DEFAULT_GROW = 1.2;
11365
11832
  var EVAPORATION = { fuel: [6, 5], water: [40, 20] };
11366
11833
  function createDecal() {
11367
- return { on: false, x: 0, y: 0, z: 0, sx: 0, sy: 0, t: 0, life: 0, grow: 0, color: 0, normal: UP13, fluid: void 0, dryColor: -1, dryAt: 0, dryTime: 0 };
11834
+ return { on: false, x: 0, y: 0, z: 0, sx: 0, sy: 0, t: 0, life: 0, grow: 0, color: 0, normal: UP14, fluid: void 0, dryColor: -1, dryAt: 0, dryTime: 0 };
11368
11835
  }
11369
11836
  var DecalPool = class {
11370
11837
  constructor(context) {
@@ -11377,7 +11844,7 @@ var GameTestRoom = (() => {
11377
11844
  const random = this.context.random;
11378
11845
  const index = this.next++ % CAPACITY2;
11379
11846
  const decal2 = this.decals[index];
11380
- const normal = options.n ?? UP13;
11847
+ const normal = options.n ?? UP14;
11381
11848
  random.between(0, 6.28);
11382
11849
  const offset = 4e-3 + index % 40 * 4e-4;
11383
11850
  decal2.on = true;
@@ -11418,6 +11885,94 @@ var GameTestRoom = (() => {
11418
11885
  }
11419
11886
  }
11420
11887
  };
11888
+ var CAPACITY3 = 400;
11889
+ var IDENTITY2 = [0, 0, 0, 1];
11890
+ function multiply(a, b) {
11891
+ const [ax, ay, az, aw] = a;
11892
+ const [bx, by, bz, bw] = b;
11893
+ return [aw * bx + ax * bw + ay * bz - az * by, aw * by - ax * bz + ay * bw + az * bx, aw * bz + ax * by - ay * bx + az * bw, aw * bw - ax * bx - ay * by - az * bz];
11894
+ }
11895
+ var aboutX = (angle) => [Math.sin(angle / 2), 0, 0, Math.cos(angle / 2)];
11896
+ var MeshPool = class {
11897
+ constructor(capacity = CAPACITY3) {
11898
+ __publicField(this, "capacity", capacity);
11899
+ __publicField(this, "particles", []);
11900
+ __publicField(this, "serial", 0);
11901
+ }
11902
+ get count() {
11903
+ return this.particles.length;
11904
+ }
11905
+ spawn(options, frame3 = IDENTITY2) {
11906
+ const velocity = options.v ?? [0, 0, 0];
11907
+ const world = options.wv ?? [0, 0, 0];
11908
+ const particle = {
11909
+ id: ++this.serial,
11910
+ shape: options.shape,
11911
+ radius: options.r,
11912
+ height: options.h ?? 0,
11913
+ segments: options.seg ?? 0,
11914
+ segments2: options.seg2 ?? 0,
11915
+ color: options.c0,
11916
+ additive: options.add === true,
11917
+ lit: options.lit === true,
11918
+ x: options.p[0],
11919
+ y: options.p[1],
11920
+ z: options.p[2],
11921
+ frame: options.roll ? multiply(frame3, aboutX(options.roll)) : frame3,
11922
+ at: options.at ?? [0, 0, 0],
11923
+ rx: options.rot?.[0] ?? 0,
11924
+ ry: options.rot?.[1] ?? 0,
11925
+ rz: options.rot?.[2] ?? 0,
11926
+ scale: 1,
11927
+ opacity: options.a,
11928
+ age: 0,
11929
+ life: options.life,
11930
+ vx: velocity[0] + world[0],
11931
+ vy: velocity[1] + world[1],
11932
+ vz: velocity[2] + world[2],
11933
+ spin: options.spin ?? [0, 0, 0],
11934
+ gravity: options.g ?? 0,
11935
+ drag: options.drag ?? 0,
11936
+ grow: options.grow ?? 0,
11937
+ ramp: options.ramp,
11938
+ fade: options.fade === true,
11939
+ startOpacity: options.a
11940
+ };
11941
+ this.particles.push(particle);
11942
+ if (this.particles.length > this.capacity) this.particles.shift();
11943
+ return particle;
11944
+ }
11945
+ update(dt) {
11946
+ const live = this.particles;
11947
+ for (let index = 0; index < live.length; index++) {
11948
+ const particle = live[index];
11949
+ particle.age += dt;
11950
+ if (particle.ramp ? particle.age >= particle.life : particle.age > particle.life) {
11951
+ live.splice(index--, 1);
11952
+ continue;
11953
+ }
11954
+ if (particle.fade) particle.opacity = Math.max(0, particle.startOpacity * (1 - particle.age / particle.life));
11955
+ particle.vy -= particle.gravity * dt;
11956
+ if (particle.drag) {
11957
+ const keep = Math.max(0, 1 - particle.drag * dt);
11958
+ particle.vx *= keep;
11959
+ particle.vy *= keep;
11960
+ particle.vz *= keep;
11961
+ }
11962
+ particle.x += particle.vx * dt;
11963
+ particle.y += particle.vy * dt;
11964
+ particle.z += particle.vz * dt;
11965
+ particle.rx += particle.spin[0] * dt;
11966
+ particle.ry += particle.spin[1] * dt;
11967
+ particle.rz += particle.spin[2] * dt;
11968
+ if (particle.grow) particle.scale *= 1 + particle.grow * dt;
11969
+ if (particle.ramp) particle.scale = particle.ramp[0] + Math.min(1, particle.age / particle.life) * particle.ramp[1];
11970
+ }
11971
+ }
11972
+ clear() {
11973
+ this.particles.length = 0;
11974
+ }
11975
+ };
11421
11976
  var DEFAULT_FADE_IN = 0.1;
11422
11977
  var FLOATS_PER_VECTOR = 3;
11423
11978
  var BYTE_MAX2 = 255;
@@ -11707,11 +12262,11 @@ var GameTestRoom = (() => {
11707
12262
  };
11708
12263
  var RING_CAPACITY = 120;
11709
12264
  var LIGHT_CAPACITY = 4;
11710
- var UP14 = [0, 1, 0];
12265
+ var UP15 = [0, 1, 0];
11711
12266
  var DEFAULT_LIGHT_DISTANCE = 14;
11712
12267
  var WaveRingPool = class {
11713
12268
  constructor() {
11714
- __publicField(this, "rings", Array.from({ length: RING_CAPACITY }, () => ({ on: false, point: UP14, from: 0, to: 0, t: 0, duration: 1, color: 0, normal: UP14, hold: 0 })));
12269
+ __publicField(this, "rings", Array.from({ length: RING_CAPACITY }, () => ({ on: false, point: UP15, from: 0, to: 0, t: 0, duration: 1, color: 0, normal: UP15, hold: 0 })));
11715
12270
  __publicField(this, "count", 0);
11716
12271
  __publicField(this, "next", 0);
11717
12272
  }
@@ -11724,7 +12279,7 @@ var GameTestRoom = (() => {
11724
12279
  ring2.t = 0;
11725
12280
  ring2.duration = duration;
11726
12281
  ring2.color = color;
11727
- ring2.normal = normal ?? UP14;
12282
+ ring2.normal = normal ?? UP15;
11728
12283
  ring2.hold = hold || 0;
11729
12284
  }
11730
12285
  clear() {
@@ -11744,7 +12299,7 @@ var GameTestRoom = (() => {
11744
12299
  var FlashLightPool = class {
11745
12300
  constructor(context) {
11746
12301
  __publicField(this, "context", context);
11747
- __publicField(this, "lights", Array.from({ length: LIGHT_CAPACITY }, () => ({ x: 0, y: 0, z: 0, color: 16760944, distance: DEFAULT_LIGHT_DISTANCE, peak: 0, duration: 1, t: 0, intensity: 0 })));
12302
+ __publicField(this, "lights", Array.from({ length: LIGHT_CAPACITY }, () => ({ x: 0, y: 0, z: 0, color: 16760944, distance: DEFAULT_LIGHT_DISTANCE, peak: 0, duration: 1, t: 0, intensity: 0, steady: false })));
11748
12303
  __publicField(this, "fire", [
11749
12304
  { x: 0, y: 0, z: 0, intensity: 0 },
11750
12305
  { x: 0, y: 0, z: 0, intensity: 0 }
@@ -11754,7 +12309,7 @@ var GameTestRoom = (() => {
11754
12309
  get lit() {
11755
12310
  return this.lights.filter((light) => light.intensity > 0).length;
11756
12311
  }
11757
- flash(point, color, intensity, duration, distance4, force) {
12312
+ flash(point, color, intensity, duration, distance4, force, steady) {
11758
12313
  const { level } = this.context.quality;
11759
12314
  if (level >= 1 && !force) return;
11760
12315
  if (level >= 2 && force !== "boom") return;
@@ -11767,6 +12322,7 @@ var GameTestRoom = (() => {
11767
12322
  light.peak = intensity;
11768
12323
  light.duration = duration;
11769
12324
  light.t = 0;
12325
+ light.steady = steady === true;
11770
12326
  light.intensity = intensity;
11771
12327
  }
11772
12328
  clear() {
@@ -11778,7 +12334,7 @@ var GameTestRoom = (() => {
11778
12334
  if (light.intensity <= 0) continue;
11779
12335
  light.t += dt;
11780
12336
  const age = light.t / light.duration;
11781
- light.intensity = age >= 1 ? 0 : light.peak * (1 - age) * (1 - age);
12337
+ light.intensity = age >= 1 ? 0 : light.steady ? light.peak : light.peak * (1 - age) * (1 - age);
11782
12338
  }
11783
12339
  }
11784
12340
  };
@@ -11786,6 +12342,14 @@ var GameTestRoom = (() => {
11786
12342
  var FIRE_FLICKER_SWAY = 0.25;
11787
12343
  var FIRE_FLICKER_JITTER = 0.08;
11788
12344
  var LENS_FADE = 0.16;
12345
+ function rotate(q, v) {
12346
+ const [qx, qy, qz, qw] = q;
12347
+ const ix = qw * v[0] + qy * v[2] - qz * v[1];
12348
+ const iy = qw * v[1] + qz * v[0] - qx * v[2];
12349
+ const iz = qw * v[2] + qx * v[1] - qy * v[0];
12350
+ const iw = -qx * v[0] - qy * v[1] - qz * v[2];
12351
+ return [ix * qw + iw * -qx + iy * -qz - iz * -qy, iy * qw + iw * -qy + iz * -qx - ix * -qz, iz * qw + iw * -qz + ix * -qy - iy * -qx];
12352
+ }
11789
12353
  function workshopGround(x, z) {
11790
12354
  return z < -2.9 && z > -4.5 && Math.abs(x) < 13 ? 0.12 : 0;
11791
12355
  }
@@ -11799,6 +12363,8 @@ var GameTestRoom = (() => {
11799
12363
  __publicField(this, "decals");
11800
12364
  __publicField(this, "rings", new WaveRingPool());
11801
12365
  __publicField(this, "lights");
12366
+ __publicField(this, "rain", new RainField(this));
12367
+ __publicField(this, "meshes", new MeshPool());
11802
12368
  __publicField(this, "screen", createScreenState());
11803
12369
  __publicField(this, "lens", []);
11804
12370
  __publicField(this, "post", {});
@@ -11831,7 +12397,11 @@ var GameTestRoom = (() => {
11831
12397
  place(point) {
11832
12398
  const placement = this.placement;
11833
12399
  if (!placement) return point;
11834
- const { anchor, origin, yaw } = placement;
12400
+ const { anchor, origin, yaw, orientation } = placement;
12401
+ if (orientation) {
12402
+ const turned = rotate(orientation, [point[0] - anchor[0], point[1] - anchor[1], point[2] - anchor[2]]);
12403
+ return [origin[0] + turned[0], origin[1] + turned[1], origin[2] + turned[2]];
12404
+ }
11835
12405
  const x = point[0] - anchor[0];
11836
12406
  const z = point[2] - anchor[2];
11837
12407
  const cos2 = Math.cos(yaw);
@@ -11842,6 +12412,7 @@ var GameTestRoom = (() => {
11842
12412
  turn(vector) {
11843
12413
  const placement = this.placement;
11844
12414
  if (!placement) return vector;
12415
+ if (placement.orientation) return rotate(placement.orientation, vector);
11845
12416
  const cos2 = Math.cos(placement.yaw);
11846
12417
  const sin2 = Math.sin(placement.yaw);
11847
12418
  return [cos2 * vector[0] + sin2 * vector[2], vector[1], -sin2 * vector[0] + cos2 * vector[2]];
@@ -11870,6 +12441,14 @@ var GameTestRoom = (() => {
11870
12441
  }
11871
12442
  this.accumulators.set(key, carried);
11872
12443
  }
12444
+ /** A solid particle (a cone, a sphere): the group sits at `p` turned by the placement, `v` is turned with it and `wv` is not. */
12445
+ mesh(recipe) {
12446
+ const placement = this.placement;
12447
+ const options = placement ? { ...recipe, p: this.place(recipe.p), ...recipe.v ? { v: this.turn(recipe.v) } : {} } : recipe;
12448
+ this.context.trace?.("mesh", [options]);
12449
+ const grouped = recipe.at !== void 0 || recipe.roll !== void 0;
12450
+ return this.meshes.spawn(options, placement && grouped ? placement.orientation ?? [0, Math.sin(placement.yaw / 2), 0, Math.cos(placement.yaw / 2)] : void 0);
12451
+ }
11873
12452
  debrisPiece(recipe) {
11874
12453
  const options = this.placed(recipe);
11875
12454
  this.context.trace?.("debris", [options]);
@@ -11890,10 +12469,11 @@ var GameTestRoom = (() => {
11890
12469
  this.context.trace?.("ring", args);
11891
12470
  this.rings.add(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
11892
12471
  }
12472
+ /** `steady` (a seventh number): the light holds its intensity for its duration and goes out, where the Workshop's fades with the square of its age. */
11893
12473
  flash(recipe) {
11894
- const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5]] : recipe;
11895
- this.context.trace?.("flash", args);
11896
- this.lights.flash(args[0], args[1], args[2], args[3], args[4], args[5]);
12474
+ const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5], recipe[6]] : recipe;
12475
+ this.context.trace?.("flash", args.slice(0, 6));
12476
+ this.lights.flash(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
11897
12477
  }
11898
12478
  /** `fireLight` (W400): sways with the clock and flickers with one draw. */
11899
12479
  fireLight(slot, recipe, intensity, time) {
@@ -11918,6 +12498,7 @@ var GameTestRoom = (() => {
11918
12498
  this.debris.update(dt);
11919
12499
  this.decals.update(dt);
11920
12500
  this.rings.update(dt);
12501
+ this.meshes.update(dt);
11921
12502
  this.lights.update(dt);
11922
12503
  updateScreen(this.screen, dt);
11923
12504
  for (let index = this.lens.length - 1; index >= 0; index--) {
@@ -11934,7 +12515,9 @@ var GameTestRoom = (() => {
11934
12515
  this.debris.clear();
11935
12516
  this.decals.clear();
11936
12517
  this.rings.clear();
12518
+ this.meshes.clear();
11937
12519
  this.lights.clear();
12520
+ this.rain.clear();
11938
12521
  this.accumulators.clear();
11939
12522
  resetScreenState(this.screen);
11940
12523
  this.lens.length = 0;
@@ -12080,16 +12663,16 @@ var GameTestRoom = (() => {
12080
12663
  },
12081
12664
  push(point, radius, speed) {
12082
12665
  spriteSystem.push(point, radius, speed);
12083
- for (const puff2 of core.puffs.items) {
12084
- const dx = puff2.x - point[0];
12085
- const dy = puff2.y - point[1];
12086
- const dz = puff2.z - point[2];
12666
+ for (const puff3 of core.puffs.items) {
12667
+ const dx = puff3.x - point[0];
12668
+ const dy = puff3.y - point[1];
12669
+ const dz = puff3.z - point[2];
12087
12670
  const distance4 = Math.sqrt(dx * dx + dy * dy + dz * dz);
12088
12671
  if (distance4 >= radius) continue;
12089
12672
  const strength = speed * (1 - distance4 / radius) / (distance4 + 1e-3);
12090
- puff2.vx += dx * strength;
12091
- puff2.vy += dy * strength;
12092
- puff2.vz += dz * strength;
12673
+ puff3.vx += dx * strength;
12674
+ puff3.vy += dy * strength;
12675
+ puff3.vz += dz * strength;
12093
12676
  }
12094
12677
  },
12095
12678
  stop(handle) {
@@ -12106,6 +12689,12 @@ var GameTestRoom = (() => {
12106
12689
  camera2.y = position[1];
12107
12690
  camera2.z = position[2];
12108
12691
  },
12692
+ setWind(next) {
12693
+ wind.x = next[0];
12694
+ wind.y = next[1];
12695
+ wind.z = next[2];
12696
+ },
12697
+ wind: () => [wind.x, wind.y, wind.z],
12109
12698
  forceLod(scale2) {
12110
12699
  core.clock.forcedLod = scale2;
12111
12700
  },
@@ -12374,11 +12963,11 @@ void main(){
12374
12963
  geometry.setAttribute("aC", attributes[2]);
12375
12964
  geometry.instanceCount = 0;
12376
12965
  const material = new THREE.ShaderMaterial({ uniforms, vertexShader: PUFF_VERTEX, fragmentShader: PUFF_FRAGMENT });
12377
- const mesh = new THREE.Mesh(geometry, material);
12378
- mesh.name = "vfx-puffs";
12379
- mesh.frustumCulled = false;
12966
+ const mesh2 = new THREE.Mesh(geometry, material);
12967
+ mesh2.name = "vfx-puffs";
12968
+ mesh2.frustumCulled = false;
12380
12969
  return {
12381
- mesh,
12970
+ mesh: mesh2,
12382
12971
  sync() {
12383
12972
  let n = 0;
12384
12973
  for (const p of pool.items) {
@@ -12419,12 +13008,12 @@ void main(){
12419
13008
  geometry.setAttribute("aE", attributes[2]);
12420
13009
  geometry.instanceCount = 0;
12421
13010
  const material = new THREE.ShaderMaterial({ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, vertexShader: SPARK_VERTEX, fragmentShader: SPARK_FRAGMENT });
12422
- const mesh = new THREE.Mesh(geometry, material);
12423
- mesh.name = "vfx-sparks";
12424
- mesh.frustumCulled = false;
12425
- mesh.renderOrder = 5;
13011
+ const mesh2 = new THREE.Mesh(geometry, material);
13012
+ mesh2.name = "vfx-sparks";
13013
+ mesh2.frustumCulled = false;
13014
+ mesh2.renderOrder = 5;
12426
13015
  return {
12427
- mesh,
13016
+ mesh: mesh2,
12428
13017
  sync() {
12429
13018
  let n = 0;
12430
13019
  for (const s of pool.items) {
@@ -12462,12 +13051,12 @@ void main(){
12462
13051
  geometry.setAttribute("aC", attributes[1]);
12463
13052
  geometry.instanceCount = 0;
12464
13053
  const material = new THREE.ShaderMaterial({ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, vertexShader: GLOW_VERTEX, fragmentShader: GLOW_FRAGMENT });
12465
- const mesh = new THREE.Mesh(geometry, material);
12466
- mesh.name = "vfx-glows";
12467
- mesh.frustumCulled = false;
12468
- mesh.renderOrder = 6;
13054
+ const mesh2 = new THREE.Mesh(geometry, material);
13055
+ mesh2.name = "vfx-glows";
13056
+ mesh2.frustumCulled = false;
13057
+ mesh2.renderOrder = 6;
12469
13058
  return {
12470
- mesh,
13059
+ mesh: mesh2,
12471
13060
  sync() {
12472
13061
  let n = 0;
12473
13062
  for (const g of pool.items) {
@@ -12496,14 +13085,14 @@ void main(){
12496
13085
  const rockMaterial = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.85, flatShading: true });
12497
13086
  const plateMaterial = rockMaterial.clone();
12498
13087
  const make = (geometry, material) => {
12499
- const mesh = new THREE.InstancedMesh(geometry, material, capacity);
12500
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
12501
- mesh.castShadow = true;
12502
- mesh.receiveShadow = true;
12503
- mesh.frustumCulled = false;
12504
- mesh.count = 0;
12505
- mesh.setColorAt(0, new THREE.Color(1, 1, 1));
12506
- return mesh;
13088
+ const mesh2 = new THREE.InstancedMesh(geometry, material, capacity);
13089
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13090
+ mesh2.castShadow = true;
13091
+ mesh2.receiveShadow = true;
13092
+ mesh2.frustumCulled = false;
13093
+ mesh2.count = 0;
13094
+ mesh2.setColorAt(0, new THREE.Color(1, 1, 1));
13095
+ return mesh2;
12507
13096
  };
12508
13097
  const rocks = make(shapes.rock, rockMaterial);
12509
13098
  const plates = make(shapes.plate, plateMaterial);
@@ -12522,20 +13111,20 @@ void main(){
12522
13111
  euler.set(chunk.rx, chunk.ry, chunk.rz);
12523
13112
  quaternion.setFromEuler(euler);
12524
13113
  matrix.compose(position.set(chunk.x, chunk.y, chunk.z), quaternion, scale2.set(chunk.sx, chunk.sy, chunk.sz));
12525
- const mesh = chunk.plate ? plates : rocks;
13114
+ const mesh2 = chunk.plate ? plates : rocks;
12526
13115
  const index = chunk.plate ? plateCount++ : rockCount++;
12527
- mesh.setMatrixAt(index, matrix);
13116
+ mesh2.setMatrixAt(index, matrix);
12528
13117
  const color = colorOf2(chunk);
12529
- const colors = mesh.instanceColor.array;
13118
+ const colors = mesh2.instanceColor.array;
12530
13119
  colors[index * 3] = color[0];
12531
13120
  colors[index * 3 + 1] = color[1];
12532
13121
  colors[index * 3 + 2] = color[2];
12533
13122
  }
12534
13123
  rocks.count = rockCount;
12535
13124
  plates.count = plateCount;
12536
- for (const mesh of [rocks, plates]) {
12537
- mesh.instanceMatrix.needsUpdate = true;
12538
- mesh.instanceColor.needsUpdate = true;
13125
+ for (const mesh2 of [rocks, plates]) {
13126
+ mesh2.instanceMatrix.needsUpdate = true;
13127
+ mesh2.instanceColor.needsUpdate = true;
12539
13128
  }
12540
13129
  return rockCount + plateCount;
12541
13130
  },
@@ -12719,28 +13308,28 @@ void main(){
12719
13308
  position.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
12720
13309
  }
12721
13310
  geometry.computeVertexNormals();
12722
- const mesh = new THREE.Mesh(geometry, material);
12723
- mesh.receiveShadow = true;
12724
- return mesh;
13311
+ const mesh2 = new THREE.Mesh(geometry, material);
13312
+ mesh2.receiveShadow = true;
13313
+ return mesh2;
12725
13314
  }
12726
13315
  function addRock(mark) {
12727
- const mesh = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
12728
- mesh.position.set(mark.x, mark.y, mark.z);
12729
- mesh.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
12730
- mesh.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
12731
- mesh.castShadow = true;
12732
- mesh.receiveShadow = true;
12733
- return mesh;
13316
+ const mesh2 = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
13317
+ mesh2.position.set(mark.x, mark.y, mark.z);
13318
+ mesh2.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
13319
+ mesh2.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
13320
+ mesh2.castShadow = true;
13321
+ mesh2.receiveShadow = true;
13322
+ return mesh2;
12734
13323
  }
12735
13324
  function addBlood(mark) {
12736
- const mesh = new THREE.Mesh(bloodGeometry, bloodMaterial);
12737
- mesh.position.set(mark.x, mark.y, mark.z);
12738
- mesh.scale.set(mark.scaleX, 1, mark.scaleZ);
12739
- mesh.rotation.y = mark.rotation;
12740
- mesh.receiveShadow = true;
12741
- mesh.scale.multiplyScalar(0.05);
12742
- blood.push({ mesh, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
12743
- return mesh;
13325
+ const mesh2 = new THREE.Mesh(bloodGeometry, bloodMaterial);
13326
+ mesh2.position.set(mark.x, mark.y, mark.z);
13327
+ mesh2.scale.set(mark.scaleX, 1, mark.scaleZ);
13328
+ mesh2.rotation.y = mark.rotation;
13329
+ mesh2.receiveShadow = true;
13330
+ mesh2.scale.multiplyScalar(0.05);
13331
+ blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
13332
+ return mesh2;
12744
13333
  }
12745
13334
  function build(mark) {
12746
13335
  if (mark.kind === "scorch") return conform(mark, scorchMaterials[mark.variant]);
@@ -12951,6 +13540,73 @@ void main() {
12951
13540
  }
12952
13541
  };
12953
13542
  }
13543
+ var SPHERE_DEFAULT_WIDTH = 8;
13544
+ var SPHERE_DEFAULT_HEIGHT = 6;
13545
+ function geometryOf(THREE, particle) {
13546
+ switch (particle.shape) {
13547
+ case "cone":
13548
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
13549
+ case "cone_open":
13550
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
13551
+ case "sphere":
13552
+ return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
13553
+ case "icosahedron":
13554
+ return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
13555
+ }
13556
+ }
13557
+ function materialOf(THREE, particle) {
13558
+ if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
13559
+ return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
13560
+ }
13561
+ function createMeshLayer(THREE, pool) {
13562
+ const group = new THREE.Group();
13563
+ group.name = "workshop-meshes";
13564
+ const drawn = /* @__PURE__ */ new Map();
13565
+ const frame3 = new THREE.Quaternion();
13566
+ const euler = new THREE.Euler();
13567
+ const turn = new THREE.Quaternion();
13568
+ const offset = new THREE.Vector3();
13569
+ function release(mesh2) {
13570
+ mesh2.removeFromParent();
13571
+ mesh2.geometry.dispose();
13572
+ mesh2.material.dispose();
13573
+ }
13574
+ return {
13575
+ group,
13576
+ sync() {
13577
+ const live = /* @__PURE__ */ new Set();
13578
+ for (const particle of pool.particles) {
13579
+ live.add(particle.id);
13580
+ let mesh2 = drawn.get(particle.id);
13581
+ if (!mesh2) {
13582
+ mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
13583
+ mesh2.frustumCulled = false;
13584
+ mesh2.renderOrder = particle.additive ? 5 : 3;
13585
+ drawn.set(particle.id, mesh2);
13586
+ group.add(mesh2);
13587
+ }
13588
+ frame3.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
13589
+ offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame3);
13590
+ mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
13591
+ turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
13592
+ mesh2.quaternion.copy(frame3).multiply(turn);
13593
+ mesh2.scale.setScalar(particle.scale);
13594
+ mesh2.material.opacity = particle.opacity;
13595
+ }
13596
+ for (const [id, mesh2] of drawn) {
13597
+ if (live.has(id)) continue;
13598
+ release(mesh2);
13599
+ drawn.delete(id);
13600
+ }
13601
+ return pool.count;
13602
+ },
13603
+ dispose() {
13604
+ for (const mesh2 of drawn.values()) release(mesh2);
13605
+ drawn.clear();
13606
+ group.removeFromParent();
13607
+ }
13608
+ };
13609
+ }
12954
13610
  var WORKSHOP_SPRITE_VERTEX = `attribute float size;attribute float alpha;attribute vec3 pcol;varying vec3 vC;varying float vA;uniform float uScale;
12955
13611
  void main(){vC=pcol;vA=alpha;vec4 mv=modelViewMatrix*vec4(position,1.);gl_PointSize=clamp(size*uScale/-mv.z,1.,512.);gl_Position=projectionMatrix*mv;}`;
12956
13612
  var WORKSHOP_SPRITE_FRAGMENT = `uniform float uSoft;varying vec3 vC;varying float vA;
@@ -13026,13 +13682,37 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13026
13682
  }
13027
13683
  };
13028
13684
  }
13685
+ function rainLayer(THREE, rain) {
13686
+ const geometry = new THREE.BufferGeometry();
13687
+ const position = new THREE.BufferAttribute(rain.positions, 3).setUsage(THREE.DynamicDrawUsage);
13688
+ geometry.setAttribute("position", position);
13689
+ geometry.setDrawRange(0, 0);
13690
+ const material = new THREE.LineBasicMaterial({ color: 10466255, transparent: true, opacity: 0.35, depthWrite: false });
13691
+ const lines = new THREE.LineSegments(geometry, material);
13692
+ lines.frustumCulled = false;
13693
+ lines.name = "workshop-rain";
13694
+ return {
13695
+ object: lines,
13696
+ sync() {
13697
+ const drawn = rain.on && rain.count > 0;
13698
+ lines.visible = drawn;
13699
+ geometry.setDrawRange(0, drawn ? rain.count * 2 : 0);
13700
+ position.needsUpdate = true;
13701
+ return drawn;
13702
+ },
13703
+ dispose() {
13704
+ geometry.dispose();
13705
+ material.dispose();
13706
+ }
13707
+ };
13708
+ }
13029
13709
  function debrisLayer(THREE, pool) {
13030
13710
  const geometry = new THREE.BoxGeometry(1, 1, 1);
13031
13711
  const material = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.45, metalness: 0.25, flatShading: true });
13032
- const mesh = new THREE.InstancedMesh(geometry, material, DEBRIS_CAPACITY);
13033
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13034
- mesh.frustumCulled = false;
13035
- mesh.name = "workshop-debris";
13712
+ const mesh2 = new THREE.InstancedMesh(geometry, material, DEBRIS_CAPACITY);
13713
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13714
+ mesh2.frustumCulled = false;
13715
+ mesh2.name = "workshop-debris";
13036
13716
  const euler = new THREE.Euler();
13037
13717
  const quaternion = new THREE.Quaternion();
13038
13718
  const position = new THREE.Vector3();
@@ -13040,16 +13720,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13040
13720
  const matrix = new THREE.Matrix4();
13041
13721
  const colour = new THREE.Color();
13042
13722
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
13043
- for (let index = 0; index < DEBRIS_CAPACITY; index++) mesh.setMatrixAt(index, hidden);
13044
- mesh.setColorAt(0, colour.setHex(16777215));
13723
+ for (let index = 0; index < DEBRIS_CAPACITY; index++) mesh2.setMatrixAt(index, hidden);
13724
+ mesh2.setColorAt(0, colour.setHex(16777215));
13045
13725
  return {
13046
- object: mesh,
13726
+ object: mesh2,
13047
13727
  sync() {
13048
13728
  let drawn = false;
13049
13729
  for (let index = 0; index < DEBRIS_CAPACITY; index++) {
13050
13730
  const piece = pool.pieces[index];
13051
13731
  if (!piece.on) {
13052
- mesh.setMatrixAt(index, hidden);
13732
+ mesh2.setMatrixAt(index, hidden);
13053
13733
  continue;
13054
13734
  }
13055
13735
  drawn = true;
@@ -13057,11 +13737,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13057
13737
  euler.set(piece.rx, piece.ry, piece.rz);
13058
13738
  quaternion.setFromEuler(euler);
13059
13739
  matrix.compose(position.set(piece.x, piece.y, piece.z), quaternion, scale2.set(piece.sx * shrink, piece.sy * shrink, piece.sz * shrink));
13060
- mesh.setMatrixAt(index, matrix);
13061
- mesh.setColorAt(index, colour.setHex(piece.color));
13740
+ mesh2.setMatrixAt(index, matrix);
13741
+ mesh2.setColorAt(index, colour.setHex(piece.color));
13062
13742
  }
13063
- mesh.instanceMatrix.needsUpdate = true;
13064
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
13743
+ mesh2.instanceMatrix.needsUpdate = true;
13744
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
13065
13745
  return drawn;
13066
13746
  },
13067
13747
  dispose() {
@@ -13118,11 +13798,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13118
13798
  const dryMesh = new THREE.InstancedMesh(geometry, dry, DECAL_CAPACITY);
13119
13799
  const group = new THREE.Group();
13120
13800
  group.name = "workshop-decals";
13121
- for (const mesh of [wetMesh, dryMesh]) {
13122
- mesh.frustumCulled = false;
13123
- mesh.renderOrder = 1;
13124
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13125
- group.add(mesh);
13801
+ for (const mesh2 of [wetMesh, dryMesh]) {
13802
+ mesh2.frustumCulled = false;
13803
+ mesh2.renderOrder = 1;
13804
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13805
+ group.add(mesh2);
13126
13806
  }
13127
13807
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
13128
13808
  const colour = new THREE.Color();
@@ -13159,13 +13839,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13159
13839
  quaternion.multiply(turn.setFromAxisAngle(forward, index * 2.399963));
13160
13840
  const offset = 4e-3 + index % 40 * 4e-4;
13161
13841
  matrix.compose(position.set(decal2.x + normal.x * offset, decal2.y + normal.y * offset, decal2.z + normal.z * offset), quaternion, scale2.set(look.sx, look.sy, 1));
13162
- const mesh = look.dried ? dryMesh : wetMesh;
13163
- mesh.setMatrixAt(index, matrix);
13164
- mesh.setColorAt(index, colour);
13842
+ const mesh2 = look.dried ? dryMesh : wetMesh;
13843
+ mesh2.setMatrixAt(index, matrix);
13844
+ mesh2.setColorAt(index, colour);
13165
13845
  }
13166
- for (const mesh of [wetMesh, dryMesh]) {
13167
- mesh.instanceMatrix.needsUpdate = true;
13168
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
13846
+ for (const mesh2 of [wetMesh, dryMesh]) {
13847
+ mesh2.instanceMatrix.needsUpdate = true;
13848
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
13169
13849
  }
13170
13850
  return drawn;
13171
13851
  },
@@ -13179,10 +13859,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13179
13859
  function ringLayer(THREE, pool) {
13180
13860
  const geometry = new THREE.RingGeometry(0.86, 1, 40);
13181
13861
  const material = new THREE.MeshBasicMaterial({ color: 16777215, transparent: true, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide });
13182
- const mesh = new THREE.InstancedMesh(geometry, material, RING_CAPACITY3);
13183
- mesh.frustumCulled = false;
13184
- mesh.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13185
- mesh.name = "workshop-rings";
13862
+ const mesh2 = new THREE.InstancedMesh(geometry, material, RING_CAPACITY3);
13863
+ mesh2.frustumCulled = false;
13864
+ mesh2.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
13865
+ mesh2.name = "workshop-rings";
13186
13866
  const hidden = new THREE.Matrix4().makeScale(0, 0, 0);
13187
13867
  const colour = new THREE.Color();
13188
13868
  const normal = new THREE.Vector3();
@@ -13191,16 +13871,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13191
13871
  const position = new THREE.Vector3();
13192
13872
  const scale2 = new THREE.Vector3();
13193
13873
  const matrix = new THREE.Matrix4();
13194
- mesh.setColorAt(0, colour.setHex(0));
13195
- for (let index = 0; index < RING_CAPACITY3; index++) mesh.setMatrixAt(index, hidden);
13874
+ mesh2.setColorAt(0, colour.setHex(0));
13875
+ for (let index = 0; index < RING_CAPACITY3; index++) mesh2.setMatrixAt(index, hidden);
13196
13876
  return {
13197
- object: mesh,
13877
+ object: mesh2,
13198
13878
  sync() {
13199
13879
  let drawn = false;
13200
13880
  for (let index = 0; index < RING_CAPACITY3; index++) {
13201
13881
  const ring2 = pool.rings[index];
13202
13882
  if (!ring2.on) {
13203
- mesh.setMatrixAt(index, hidden);
13883
+ mesh2.setMatrixAt(index, hidden);
13204
13884
  continue;
13205
13885
  }
13206
13886
  drawn = true;
@@ -13208,11 +13888,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13208
13888
  const radius = ring2.from + (ring2.to - ring2.from) * (1 - Math.pow(1 - k, 2));
13209
13889
  quaternion.setFromUnitVectors(forward, normal.set(ring2.normal[0], ring2.normal[1], ring2.normal[2]));
13210
13890
  matrix.compose(position.set(ring2.point[0], ring2.point[1], ring2.point[2]), quaternion, scale2.set(radius, radius, 1));
13211
- mesh.setMatrixAt(index, matrix);
13212
- mesh.setColorAt(index, colour.setHex(ring2.color).multiplyScalar(ring2.hold ? ring2.hold : 1 - k));
13891
+ mesh2.setMatrixAt(index, matrix);
13892
+ mesh2.setColorAt(index, colour.setHex(ring2.color).multiplyScalar(ring2.hold ? ring2.hold : 1 - k));
13213
13893
  }
13214
- mesh.instanceMatrix.needsUpdate = true;
13215
- if (mesh.instanceColor) mesh.instanceColor.needsUpdate = true;
13894
+ mesh2.instanceMatrix.needsUpdate = true;
13895
+ if (mesh2.instanceColor) mesh2.instanceColor.needsUpdate = true;
13216
13896
  return drawn;
13217
13897
  },
13218
13898
  dispose() {
@@ -13228,11 +13908,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13228
13908
  spriteLayer(THREE, engine.add, true, 1, pointScale),
13229
13909
  spriteLayer(THREE, engine.normal, false, 0.55, pointScale),
13230
13910
  streakLayer(THREE, engine.streaks),
13911
+ rainLayer(THREE, engine.rain),
13231
13912
  debrisLayer(THREE, engine.debris),
13232
13913
  decalLayer(THREE, engine.decals),
13233
13914
  ringLayer(THREE, engine.rings)
13234
13915
  ];
13235
13916
  for (const layer2 of layers) group.add(layer2.object);
13917
+ const meshes = createMeshLayer(THREE, engine.meshes);
13918
+ group.add(meshes.group);
13236
13919
  const flashLights = engine.lights.lights.map(() => new THREE.PointLight(16760944, 0, 14));
13237
13920
  const fireLights = engine.lights.fire.map(() => new THREE.PointLight(FIRE_LIGHT_COLOR, 0, FIRE_LIGHT_DISTANCE));
13238
13921
  for (const light of [...flashLights, ...fireLights]) group.add(light);
@@ -13241,6 +13924,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13241
13924
  sync() {
13242
13925
  let drawCalls = 0;
13243
13926
  for (const layer2 of layers) if (layer2.sync()) drawCalls++;
13927
+ drawCalls += meshes.sync();
13244
13928
  engine.lights.lights.forEach((source, index) => {
13245
13929
  const light = flashLights[index];
13246
13930
  light.position.set(source.x, source.y, source.z);
@@ -13257,6 +13941,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13257
13941
  },
13258
13942
  dispose() {
13259
13943
  for (const layer2 of layers) layer2.dispose();
13944
+ meshes.dispose();
13260
13945
  group.clear();
13261
13946
  }
13262
13947
  };
@@ -13372,8 +14057,10 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13372
14057
  return world.play(request.effect, request.point, {
13373
14058
  ...request.direction ? { direction: request.direction } : {},
13374
14059
  ...request.normal ? { normal: request.normal } : {},
14060
+ ...request.orientation ? { orientation: request.orientation } : {},
13375
14061
  ...request.seed !== void 0 ? { seed: request.seed } : {},
13376
- ...request.param !== void 0 ? { param: request.param } : {}
14062
+ ...request.param !== void 0 ? { param: request.param } : {},
14063
+ ...request.values ? { values: request.values } : {}
13377
14064
  });
13378
14065
  },
13379
14066
  stop: (handle) => world.stop(handle),
@@ -13438,8 +14125,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13438
14125
  const uv = geometry.attributes.uv;
13439
14126
  for (let index = 0; index < position.count; index++) uv.setXY(index, position.getX(index) / metres, position.getZ(index) / metres);
13440
14127
  const material = options.material ?? new THREE.MeshStandardMaterial({ color: 2829359, roughness: 0.93, flatShading: true });
13441
- const mesh = new THREE.Mesh(geometry, material);
13442
- mesh.receiveShadow = true;
14128
+ const mesh2 = new THREE.Mesh(geometry, material);
14129
+ mesh2.receiveShadow = true;
13443
14130
  let version = -1;
13444
14131
  function sync() {
13445
14132
  if (field.version === version) return false;
@@ -13450,7 +14137,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13450
14137
  return true;
13451
14138
  }
13452
14139
  return {
13453
- mesh,
14140
+ mesh: mesh2,
13454
14141
  sync,
13455
14142
  dispose() {
13456
14143
  geometry.dispose();
@@ -13564,11 +14251,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13564
14251
  * 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
13565
14252
  * a unit box scaled to the fragment, so each disc is scaled back; exact for a mark on a face, which is where a bullet hole is.
13566
14253
  */
13567
- addMarks(mesh, f) {
14254
+ addMarks(mesh2, f) {
13568
14255
  if (!f.marks?.length) return;
13569
14256
  this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
13570
14257
  this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
13571
- const sc = mesh.scale;
14258
+ const sc = mesh2.scale;
13572
14259
  for (const mark of f.marks) {
13573
14260
  const disc = new this.THREE.Mesh(this.markGeometry, this.markMaterial);
13574
14261
  const radius = Math.max(4e-3, mark.diameter) * 0.6;
@@ -13576,22 +14263,22 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13576
14263
  disc.position.set(mark.point[0] / sc.x + n.x * 2e-3 / sc.x, mark.point[1] / sc.y + n.y * 2e-3 / sc.y, mark.point[2] / sc.z + n.z * 2e-3 / sc.z);
13577
14264
  disc.quaternion.setFromUnitVectors(this.w.set(0, 0, 1), n);
13578
14265
  disc.scale.set(radius / sc.x, radius / sc.y, radius / sc.z);
13579
- mesh.add(disc);
14266
+ mesh2.add(disc);
13580
14267
  }
13581
14268
  }
13582
14269
  /** Create the mesh of a fragment and start its motion. `maxAllowed` is the live budget. */
13583
14270
  spawn(f, material, home, maxAllowed) {
13584
14271
  const geo = f.shape === "shard" ? this.shardGeometry(hashString(f.id)) : this.box;
13585
- const mesh = new this.THREE.Mesh(geo, material);
13586
- mesh.position.set(f.center[0], f.center[1], f.center[2]);
13587
- mesh.quaternion.set(f.rotation[0], f.rotation[1], f.rotation[2], f.rotation[3]).normalize();
14272
+ const mesh2 = new this.THREE.Mesh(geo, material);
14273
+ mesh2.position.set(f.center[0], f.center[1], f.center[2]);
14274
+ mesh2.quaternion.set(f.rotation[0], f.rotation[1], f.rotation[2], f.rotation[3]).normalize();
13588
14275
  const sz = f.size;
13589
14276
  const sc = new this.THREE.Vector3(Math.max(5e-3, sz[0]), Math.max(5e-3, sz[1]), Math.max(3e-3, sz[2]));
13590
- mesh.scale.copy(sc);
13591
- mesh.castShadow = true;
13592
- this.o.scene.add(mesh);
14277
+ mesh2.scale.copy(sc);
14278
+ mesh2.castShadow = true;
14279
+ this.o.scene.add(mesh2);
13593
14280
  const live = {
13594
- mesh,
14281
+ mesh: mesh2,
13595
14282
  home,
13596
14283
  vel: new this.THREE.Vector3(f.velocity[0], f.velocity[1], f.velocity[2]),
13597
14284
  ang: new this.THREE.Vector3(f.angularVelocity[0], f.angularVelocity[1], f.angularVelocity[2]),
@@ -13612,8 +14299,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13612
14299
  alive--;
13613
14300
  }
13614
14301
  }
13615
- this.addMarks(mesh, f);
13616
- return mesh;
14302
+ this.addMarks(mesh2, f);
14303
+ return mesh2;
13617
14304
  }
13618
14305
  /** Add velocity to every fragment within `radius` of a point (blasts). */
13619
14306
  blast(point, radius, speed) {
@@ -13652,24 +14339,24 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13652
14339
  }
13653
14340
  }
13654
14341
  step(f, dt, g) {
13655
- const mesh = f.mesh;
14342
+ const mesh2 = f.mesh;
13656
14343
  f.vel.y -= g * dt;
13657
- mesh.position.addScaledVector(f.vel, dt);
14344
+ mesh2.position.addScaledVector(f.vel, dt);
13658
14345
  const w = f.ang.length();
13659
14346
  if (w > 1e-5) {
13660
14347
  this.dq.setFromAxisAngle(this.v.copy(f.ang).multiplyScalar(1 / w), w * dt);
13661
- mesh.quaternion.premultiply(this.dq).normalize();
14348
+ mesh2.quaternion.premultiply(this.dq).normalize();
13662
14349
  }
13663
- this.m.makeRotationFromQuaternion(mesh.quaternion);
14350
+ this.m.makeRotationFromQuaternion(mesh2.quaternion);
13664
14351
  const e = this.m.elements;
13665
14352
  const h = f.half;
13666
14353
  const sc = f.dying ? f.fade : 1;
13667
14354
  const ext = (Math.abs(e[1]) * h.x + Math.abs(e[5]) * h.y + Math.abs(e[9]) * h.z) * sc;
13668
- const gy = this.o.groundAt(mesh.position.x, mesh.position.z);
14355
+ const gy = this.o.groundAt(mesh2.position.x, mesh2.position.z);
13669
14356
  let contact = false;
13670
- if (mesh.position.y - ext < gy) {
14357
+ if (mesh2.position.y - ext < gy) {
13671
14358
  contact = true;
13672
- mesh.position.y = gy + ext;
14359
+ mesh2.position.y = gy + ext;
13673
14360
  if (f.vel.y < -0.6) {
13674
14361
  f.vel.y = -f.vel.y * 0.25;
13675
14362
  f.vel.x *= 0.7;
@@ -13702,8 +14389,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13702
14389
  }
13703
14390
  /** Tip the fragment towards lying on its flattest face. */
13704
14391
  settle(f, dt) {
13705
- const mesh = f.mesh;
13706
- this.m.makeRotationFromQuaternion(mesh.quaternion);
14392
+ const mesh2 = f.mesh;
14393
+ this.m.makeRotationFromQuaternion(mesh2.quaternion);
13707
14394
  const e = this.m.elements;
13708
14395
  const up = [e[1], e[5], e[9]];
13709
14396
  const half = [f.half.x, f.half.y, f.half.z];
@@ -13718,8 +14405,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13718
14405
  this.v.set(e[axis2 * 4], e[axis2 * 4 + 1], e[axis2 * 4 + 2]).multiplyScalar(sign);
13719
14406
  this.w.set(0, 1, 0);
13720
14407
  this.dq.setFromUnitVectors(this.v, this.w);
13721
- this.q.copy(this.dq).multiply(mesh.quaternion);
13722
- mesh.quaternion.slerp(this.q, clamp6(dt * 7, 0, 1));
14408
+ this.q.copy(this.dq).multiply(mesh2.quaternion);
14409
+ mesh2.quaternion.slerp(this.q, clamp6(dt * 7, 0, 1));
13723
14410
  }
13724
14411
  /** Remove the fragments of one object (after `restore`). */
13725
14412
  removeHome(home) {
@@ -13789,13 +14476,13 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13789
14476
  }
13790
14477
  }
13791
14478
  while (this.spots.length >= this.cap) this.remove(this.spots[0]);
13792
- const mesh = new this.THREE.Mesh(this.geometry, this.material(kind, color));
14479
+ const mesh2 = new this.THREE.Mesh(this.geometry, this.material(kind, color));
13793
14480
  const y = this.groundAt(x, z) + 4e-3 + this.spots.length % 8 * 4e-4;
13794
- mesh.position.set(x, y, z);
13795
- mesh.scale.setScalar(1e-3);
13796
- mesh.renderOrder = 1;
13797
- this.scene.add(mesh);
13798
- this.spots.push({ mesh, kind, home, x, z, radius: 1e-3, target: Math.min(MAX_RADIUS, radius) });
14481
+ mesh2.position.set(x, y, z);
14482
+ mesh2.scale.setScalar(1e-3);
14483
+ mesh2.renderOrder = 1;
14484
+ this.scene.add(mesh2);
14485
+ this.spots.push({ mesh: mesh2, kind, home, x, z, radius: 1e-3, target: Math.min(MAX_RADIUS, radius) });
13799
14486
  }
13800
14487
  remove(s) {
13801
14488
  const i = this.spots.indexOf(s);
@@ -14171,7 +14858,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14171
14858
  /** Bars drawn now, over every part. */
14172
14859
  get count() {
14173
14860
  let n = 0;
14174
- for (const mesh of this.cages.values()) n += mesh.count;
14861
+ for (const mesh2 of this.cages.values()) n += mesh2.count;
14175
14862
  return n;
14176
14863
  }
14177
14864
  /** Meshes of the live cages (tests and picking). */
@@ -14182,25 +14869,25 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14182
14869
  set(key, bars) {
14183
14870
  this.remove(key);
14184
14871
  if (bars.length === 0) return;
14185
- const mesh = new this.THREE.InstancedMesh(this.geometry, this.material, bars.length);
14872
+ const mesh2 = new this.THREE.InstancedMesh(this.geometry, this.material, bars.length);
14186
14873
  bars.forEach(([a, b], i) => {
14187
14874
  this.dir.set(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
14188
14875
  const length3 = this.dir.length();
14189
14876
  this.mid.set((a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2);
14190
14877
  this.q.setFromUnitVectors(this.up, length3 > 1e-9 ? this.dir.divideScalar(length3) : this.up);
14191
14878
  this.m.compose(this.mid, this.q, this.scale.set(1, Math.max(length3, 1e-4), 1));
14192
- mesh.setMatrixAt(i, this.m);
14879
+ mesh2.setMatrixAt(i, this.m);
14193
14880
  });
14194
- mesh.instanceMatrix.needsUpdate = true;
14195
- mesh.castShadow = true;
14196
- this.scene.add(mesh);
14197
- this.cages.set(key, mesh);
14881
+ mesh2.instanceMatrix.needsUpdate = true;
14882
+ mesh2.castShadow = true;
14883
+ this.scene.add(mesh2);
14884
+ this.cages.set(key, mesh2);
14198
14885
  }
14199
14886
  remove(key) {
14200
- const mesh = this.cages.get(key);
14201
- if (!mesh) return;
14202
- this.scene.remove(mesh);
14203
- mesh.dispose();
14887
+ const mesh2 = this.cages.get(key);
14888
+ if (!mesh2) return;
14889
+ this.scene.remove(mesh2);
14890
+ mesh2.dispose();
14204
14891
  this.cages.delete(key);
14205
14892
  }
14206
14893
  /** Remove the cages of every part of `object` (keys are `object|part`). */
@@ -14484,25 +15171,25 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14484
15171
  /**
14485
15172
  * @param range vertex range `[v0, v1)` of a merged mesh this part is drawn by (see `MeshRange`); `hosts` caches the shared state per mesh for one `bind`.
14486
15173
  */
14487
- makeMeshVis(mesh, home, range2, hosts) {
15174
+ makeMeshVis(mesh2, home, range2, hosts) {
14488
15175
  let host = null;
14489
15176
  if (range2) {
14490
- host = hosts?.get(mesh) ?? null;
15177
+ host = hosts?.get(mesh2) ?? null;
14491
15178
  if (!host) {
14492
- host = { mesh, origGeometry: mesh.geometry, origMaterial: mesh.material, ownGeo: false, work: null, ownMat: false, sharers: [] };
14493
- hosts?.set(mesh, host);
15179
+ host = { mesh: mesh2, origGeometry: mesh2.geometry, origMaterial: mesh2.material, ownGeo: false, work: null, ownMat: false, sharers: [] };
15180
+ hosts?.set(mesh2, host);
14494
15181
  }
14495
15182
  }
14496
15183
  const mv = {
14497
- mesh,
15184
+ mesh: mesh2,
14498
15185
  home,
14499
- origGeometry: mesh.geometry,
14500
- origMaterial: mesh.material,
14501
- origVisible: mesh.visible,
14502
- origParent: mesh.parent,
14503
- origPosition: mesh.position.clone(),
14504
- origQuaternion: mesh.quaternion.clone(),
14505
- origScale: mesh.scale.clone(),
15186
+ origGeometry: mesh2.geometry,
15187
+ origMaterial: mesh2.material,
15188
+ origVisible: mesh2.visible,
15189
+ origParent: mesh2.parent,
15190
+ origPosition: mesh2.position.clone(),
15191
+ origQuaternion: mesh2.quaternion.clone(),
15192
+ origScale: mesh2.scale.clone(),
14506
15193
  ownMat: false,
14507
15194
  ownGeo: false,
14508
15195
  work: null,
@@ -14627,15 +15314,15 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14627
15314
  * Returns false when nothing was changed (too far, low quality on a heavy mesh, or no editable positions).
14628
15315
  */
14629
15316
  applyDent(mv, worldPoint, worldNormal, dent) {
14630
- const mesh = mv.mesh;
15317
+ const mesh2 = mv.mesh;
14631
15318
  if (this.lowQuality && this.vertexCount(mv) > 2e4) return false;
14632
- mesh.updateWorldMatrix(true, false);
14633
- const s = mesh.matrixWorld.getMaxScaleOnAxis() || 1;
14634
- const lp = mesh.worldToLocal(this.a.copy(worldPoint));
14635
- const ln = this.b.copy(worldNormal).transformDirection(this.m4.copy(mesh.matrixWorld).invert()).normalize();
15319
+ mesh2.updateWorldMatrix(true, false);
15320
+ const s = mesh2.matrixWorld.getMaxScaleOnAxis() || 1;
15321
+ const lp = mesh2.worldToLocal(this.a.copy(worldPoint));
15322
+ const ln = this.b.copy(worldNormal).transformDirection(this.m4.copy(mesh2.matrixWorld).invert()).normalize();
14636
15323
  const r = dent.radius / s;
14637
15324
  const depth = dent.depth / s;
14638
- const geo = mesh.geometry;
15325
+ const geo = mesh2.geometry;
14639
15326
  let bb;
14640
15327
  if (mv.host) bb = this.localBox(mv, this.lbox);
14641
15328
  else {
@@ -14645,7 +15332,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14645
15332
  if (bb && !bb.isEmpty() && bb.distanceToPoint(lp) > r) return false;
14646
15333
  if (!this.ownGeometry(mv)) return false;
14647
15334
  const work = mv.host ? mv.host.work : mv.work;
14648
- const pos = positionAttr(mesh.geometry);
15335
+ const pos = positionAttr(mesh2.geometry);
14649
15336
  const r2 = r * r;
14650
15337
  let touched = false;
14651
15338
  const px = lp.x;
@@ -14711,11 +15398,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14711
15398
  // Decals
14712
15399
  // ------------------------------------------------------------------------------------------------------------------------------------
14713
15400
  /** Decal pose in mesh space: position and a rotation taking +Z to the surface normal. */
14714
- decalPose(mesh, worldPoint, worldNormal, lift) {
14715
- mesh.updateWorldMatrix(true, false);
14716
- const s = mesh.matrixWorld.getMaxScaleOnAxis() || 1;
14717
- const ln = worldNormal.clone().transformDirection(this.m4.copy(mesh.matrixWorld).invert()).normalize();
14718
- const pos = mesh.worldToLocal(worldPoint.clone()).addScaledVector(ln, lift / s);
15401
+ decalPose(mesh2, worldPoint, worldNormal, lift) {
15402
+ mesh2.updateWorldMatrix(true, false);
15403
+ const s = mesh2.matrixWorld.getMaxScaleOnAxis() || 1;
15404
+ const ln = worldNormal.clone().transformDirection(this.m4.copy(mesh2.matrixWorld).invert()).normalize();
15405
+ const pos = mesh2.worldToLocal(worldPoint.clone()).addScaledVector(ln, lift / s);
14719
15406
  const quat2 = new this.THREE.Quaternion().setFromUnitVectors(new this.THREE.Vector3(0, 0, 1), ln);
14720
15407
  return { s, pos, quat: quat2 };
14721
15408
  }
@@ -14851,7 +15538,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14851
15538
  if (Math.abs(m - pv.meltApplied) < 0.01) return;
14852
15539
  pv.meltApplied = m;
14853
15540
  for (const mv of pv.meshes) {
14854
- const mesh = mv.mesh;
15541
+ const mesh2 = mv.mesh;
14855
15542
  if (mv.host) {
14856
15543
  mv.meltApplied = m;
14857
15544
  mv.touched = true;
@@ -14859,27 +15546,27 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
14859
15546
  continue;
14860
15547
  }
14861
15548
  if (mv.meltApplied === 0 && m > 0) {
14862
- mesh.updateWorldMatrix(true, false);
14863
- mv.bottomY = this.box.setFromObject(mesh).min.y;
15549
+ mesh2.updateWorldMatrix(true, false);
15550
+ mv.bottomY = this.box.setFromObject(mesh2).min.y;
14864
15551
  }
14865
15552
  mv.meltApplied = m;
14866
15553
  mv.moved = true;
14867
15554
  const sy = lerp5(1, 0.25, m);
14868
15555
  const sxz = 1 + 0.18 * m;
14869
- mesh.scale.set(mv.origScale.x * sxz, mv.origScale.y * sy, mv.origScale.z * sxz);
14870
- mesh.position.copy(mv.origPosition);
14871
- mesh.updateWorldMatrix(true, false);
14872
- const bottom = this.box.setFromObject(mesh).min.y;
15556
+ mesh2.scale.set(mv.origScale.x * sxz, mv.origScale.y * sy, mv.origScale.z * sxz);
15557
+ mesh2.position.copy(mv.origPosition);
15558
+ mesh2.updateWorldMatrix(true, false);
15559
+ const bottom = this.box.setFromObject(mesh2).min.y;
14873
15560
  const dy = mv.bottomY - bottom;
14874
- if (Math.abs(dy) > 1e-6 && mesh.parent) {
14875
- mesh.parent.updateWorldMatrix(true, false);
14876
- const inv = this.m4.copy(mesh.parent.matrixWorld).invert();
15561
+ if (Math.abs(dy) > 1e-6 && mesh2.parent) {
15562
+ mesh2.parent.updateWorldMatrix(true, false);
15563
+ const inv = this.m4.copy(mesh2.parent.matrixWorld).invert();
14877
15564
  this.a.set(0, dy, 0).applyMatrix4(inv);
14878
15565
  this.b.set(0, 0, 0).applyMatrix4(inv);
14879
- mesh.position.copy(mv.origPosition).add(this.a.sub(this.b));
15566
+ mesh2.position.copy(mv.origPosition).add(this.a.sub(this.b));
14880
15567
  }
14881
15568
  if (!(this.lowQuality && this.vertexCount(mv) > 2e4) && this.ownGeometry(mv)) {
14882
- this.writePositions(mv, positionAttr(mesh.geometry));
15569
+ this.writePositions(mv, positionAttr(mesh2.geometry));
14883
15570
  }
14884
15571
  }
14885
15572
  }
@@ -15152,30 +15839,30 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
15152
15839
  this.restoreRanged(mv);
15153
15840
  return;
15154
15841
  }
15155
- const mesh = mv.mesh;
15842
+ const mesh2 = mv.mesh;
15156
15843
  for (const d of mv.decals) this.disposeDecal(d);
15157
15844
  mv.decals.length = 0;
15158
15845
  if (mv.ownGeo) {
15159
- mesh.geometry.dispose();
15160
- mesh.geometry = mv.origGeometry;
15846
+ mesh2.geometry.dispose();
15847
+ mesh2.geometry = mv.origGeometry;
15161
15848
  mv.ownGeo = false;
15162
15849
  mv.work = null;
15163
15850
  }
15164
15851
  if (mv.ownMat) {
15165
- for (const m of materialsOf(mesh.material)) m.dispose();
15166
- mesh.material = mv.origMaterial;
15852
+ for (const m of materialsOf(mesh2.material)) m.dispose();
15853
+ mesh2.material = mv.origMaterial;
15167
15854
  mv.ownMat = false;
15168
15855
  mv.baseColors = null;
15169
15856
  }
15170
- mesh.visible = mv.origVisible;
15171
- if (mv.origParent && mesh.parent !== mv.origParent) {
15172
- mv.origParent.add(mesh);
15857
+ mesh2.visible = mv.origVisible;
15858
+ if (mv.origParent && mesh2.parent !== mv.origParent) {
15859
+ mv.origParent.add(mesh2);
15173
15860
  mv.moved = true;
15174
15861
  }
15175
15862
  if (mv.moved) {
15176
- mesh.position.copy(mv.origPosition);
15177
- mesh.quaternion.copy(mv.origQuaternion);
15178
- mesh.scale.copy(mv.origScale);
15863
+ mesh2.position.copy(mv.origPosition);
15864
+ mesh2.quaternion.copy(mv.origQuaternion);
15865
+ mesh2.scale.copy(mv.origScale);
15179
15866
  mv.moved = false;
15180
15867
  }
15181
15868
  mv.meltApplied = 0;
@@ -16223,88 +16910,42 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
16223
16910
  this.disposed = true;
16224
16911
  }
16225
16912
  };
16226
- var ATTACK = 2e-3;
16227
- var lengthOf = (d, attack) => {
16228
- if (typeof d === "number") return attack + d;
16229
- if (Array.isArray(d)) return [attack + d[0], attack + d[1]];
16230
- return { ...d, add: (d.add ?? 0) + attack };
16231
- };
16232
- var envelope = (attack) => [
16233
- [0, 1e-4, "set", "fixed"],
16234
- [attack, 1, "exp"],
16235
- ["1d", 1e-4, "exp", "fixed"]
16236
- ];
16237
- function noise(o) {
16238
- const a = o.a ?? ATTACK;
16239
- const over = typeof o.d === "number" ? o.d : 0;
16240
- return {
16241
- filters: {
16242
- mode: "serial",
16243
- bank: [{ type: o.type ?? "lowpass", frequency: o.f2 === void 0 ? { set: o.f } : { set: o.f, to: o.f2, curve: "exp", over }, q: o.q ?? 0.7 }]
16244
- },
16245
- duration: lengthOf(o.d, a),
16246
- gain: o.vol,
16247
- ...o.delay === void 0 ? {} : { at: o.delay },
16248
- ...o.wet === void 0 ? {} : { wet: o.wet },
16249
- source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
16250
- envelope: envelope(a)
16251
- };
16252
- }
16253
- function tone(o) {
16254
- return {
16255
- source: { kind: "osc", wave: o.wave ?? "sine", frequency: { from: o.f, to: o.f2 ?? "from", curve: "exp", over: "duration" } },
16256
- duration: lengthOf(o.d, ATTACK),
16257
- gain: o.vol,
16258
- ...o.delay === void 0 ? {} : { at: o.delay },
16259
- ...o.wet === void 0 ? {} : { wet: o.wet },
16260
- envelope: envelope(ATTACK)
16261
- };
16262
- }
16263
- var many = (count, make) => Array.from({ length: count }, make);
16264
- var scene = (id, layers, line, vars) => ({
16265
- id,
16266
- bus: "world",
16267
- pan: true,
16268
- layers,
16269
- falloff: DISTANCE_CURVES.art_scene,
16270
- ...vars ? { vars } : {},
16271
- sources: [`cena:${line}`]
16272
- });
16913
+ var scene = (id, layers, line, vars) => sceneRecipe(id, layers, line, vars ? { vars } : {});
16273
16914
  var boomLayers = (big) => [
16274
- noise({ f: 1200, f2: 120, d: 1.8, vol: 2, wet: 1.2 }),
16275
- tone({ f: 65, f2: 24, d: 1, vol: 1.8 }),
16276
- noise({ type: "highpass", f: 1500, d: 0.2, vol: 0.8 }),
16277
- ...big ? [noise({ f: 500, f2: 90, a: 0.2, d: 2.4, vol: 1.2, wet: 1, delay: 0.15 })] : [],
16278
- ...many(8, () => tone({ f: [2600, 5600], d: 0.05, vol: 0.08, delay: [0.4, 1.6] }))
16915
+ sceneNoise({ f: 1200, f2: 120, d: 1.8, vol: 2, wet: 1.2 }),
16916
+ sceneTone({ f: 65, f2: 24, d: 1, vol: 1.8 }),
16917
+ sceneNoise({ type: "highpass", f: 1500, d: 0.2, vol: 0.8 }),
16918
+ ...big ? [sceneNoise({ f: 500, f2: 90, a: 0.2, d: 2.4, vol: 1.2, wet: 1, delay: 0.15 })] : [],
16919
+ ...sceneMany(8, () => sceneTone({ f: [2600, 5600], d: 0.05, vol: 0.08, delay: [0.4, 1.6] }))
16279
16920
  ];
16280
16921
  var SCENE_SOUNDS = [
16281
16922
  scene(
16282
16923
  "glass_shatter",
16283
- [noise({ type: "highpass", f: 2800, d: 0.45, vol: 0.9, wet: 0.5 }), ...many(12, () => tone({ f: [3e3, 7500], d: [0.06, 0.14], vol: 0.18, delay: [0, 0.55] }))],
16924
+ [sceneNoise({ type: "highpass", f: 2800, d: 0.45, vol: 0.9, wet: 0.5 }), ...sceneMany(12, () => sceneTone({ f: [3e3, 7500], d: [0.06, 0.14], vol: 0.18, delay: [0, 0.55] }))],
16284
16925
  "L1359 glass"
16285
16926
  ),
16286
16927
  scene(
16287
16928
  "wood_hit",
16288
- [noise({ type: "bandpass", f: 850, q: 1, d: 0.25, vol: 0.9, wet: 0.3 }), tone({ f: 210, f2: 90, d: 0.1, vol: 0.6 }), noise({ type: "highpass", f: 2e3, d: 0.04, vol: 0.5 })],
16929
+ [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 })],
16289
16930
  "L1360 wood"
16290
16931
  ),
16291
- scene("thunk", [noise({ type: "bandpass", f: 700, q: 2, d: 0.07, vol: 0.4 })], "L1361 thunk"),
16292
- scene("ping", [tone({ f: [2600, 3500], f2: 1400, d: 0.14, vol: 0.18, wet: 0.3 })], "L1362 ping"),
16293
- scene("clang", [tone({ f: [520, 720], f2: 480, d: 0.5, vol: 0.35, wet: 0.4 }), tone({ f: 1370, f2: 1300, d: 0.3, vol: 0.15 }), noise({ type: "highpass", f: 2e3, d: 0.05, vol: 0.5 })], "L1363 clang"),
16294
- scene("chip", [noise({ type: "bandpass", f: 1600, q: 1.2, d: 0.06, vol: 0.5 })], "L1364 chip"),
16932
+ scene("thunk", [sceneNoise({ type: "bandpass", f: 700, q: 2, d: 0.07, vol: 0.4 })], "L1361 thunk"),
16933
+ scene("ping", [sceneTone({ f: [2600, 3500], f2: 1400, d: 0.14, vol: 0.18, wet: 0.3 })], "L1362 ping"),
16934
+ 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"),
16935
+ scene("chip", [sceneNoise({ type: "bandpass", f: 1600, q: 1.2, d: 0.06, vol: 0.5 })], "L1364 chip"),
16295
16936
  scene(
16296
16937
  "crumble",
16297
- [noise({ f: 900, f2: 200, d: 0.6, vol: 0.9, wet: 0.3 }), ...many(5, () => noise({ type: "bandpass", f: [500, 1400], q: 2, d: 0.05, vol: 0.4, delay: [0.1, 0.5] }))],
16938
+ [sceneNoise({ f: 900, f2: 200, d: 0.6, vol: 0.9, wet: 0.3 }), ...sceneMany(5, () => sceneNoise({ type: "bandpass", f: [500, 1400], q: 2, d: 0.05, vol: 0.4, delay: [0.1, 0.5] }))],
16298
16939
  "L1365 crumble"
16299
16940
  ),
16300
16941
  // `thud(x, z, v)`: `v` is the `scale` param (default 1)
16301
- scene("thud", [tone({ f: 75, f2: 38, d: 0.22, vol: { param: "scale", scale: 0.6 } }), noise({ f: 420, d: 0.16, vol: { param: "scale", scale: 0.45 } })], "L1358 thud", { scale: 1 }),
16942
+ 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 }),
16302
16943
  // `hiss(x, z, d)`: `seconds` is the param `d` (default 1.2)
16303
- scene("hiss", [noise({ type: "highpass", f: 3500, a: 0.05, d: { param: "seconds" }, vol: 0.35 })], "L1366 hiss", { seconds: 1.2 }),
16944
+ scene("hiss", [sceneNoise({ type: "highpass", f: 3500, a: 0.05, d: { param: "seconds" }, vol: 0.35 })], "L1366 hiss", { seconds: 1.2 }),
16304
16945
  scene("boom", boomLayers(false), "L1367 boom"),
16305
16946
  scene("boom_big", boomLayers(true), "L1367 boom (big)"),
16306
- scene("burst", [noise({ f: 700, f2: 120, d: 0.7, vol: 1.4, wet: 0.5 }), noise({ type: "highpass", f: 3e3, a: 0.05, d: 1.2, vol: 0.4, delay: 0.1 })], "L1357 burst"),
16307
- scene("crack", [noise({ type: "highpass", f: 1800, d: 0.05, vol: 0.8 }), tone({ f: 420, f2: 180, d: 0.06, vol: 0.4 })], "L1354 crack")
16947
+ 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"),
16948
+ scene("crack", [sceneNoise({ type: "highpass", f: 1800, d: 0.05, vol: 0.8 }), sceneTone({ f: 420, f2: 180, d: 0.06, vol: 0.4 })], "L1354 crack")
16308
16949
  ];
16309
16950
  var EXPLOSION_BOOM = {
16310
16951
  id: "explosion_boom",
@@ -32046,7 +32687,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32046
32687
  if (lastRow < 0) return;
32047
32688
  const profile = profileOf2();
32048
32689
  const rainScale = 1 - options.weather().rain;
32049
- const seconds = dryingSeconds * rainScale;
32690
+ const seconds = dryingSeconds * rainScale * (options.weather().drying ?? 1);
32050
32691
  for (let row = firstRow; row <= lastRow; row++) {
32051
32692
  const offset = row * width;
32052
32693
  for (let column = firstColumn; column <= lastColumn; column++) {
@@ -32170,6 +32811,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32170
32811
  }
32171
32812
  var DEFAULT_STEP_RATE = 180;
32172
32813
  var DEFAULT_MAX_STEPS = 6;
32814
+ var WASH_BELOW_DRYNESS = 0.5;
32173
32815
  function createFilm(options) {
32174
32816
  const { layout, random } = options;
32175
32817
  const cellCount = layout.width * layout.height;
@@ -32180,7 +32822,7 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32180
32822
  dryness: new Float32Array(cellCount),
32181
32823
  dampness: new Float32Array(cellCount)
32182
32824
  };
32183
- let weatherState = { rain: 0 };
32825
+ let weatherState = { rain: 0, drying: 1 };
32184
32826
  let currentProfile = options.profile;
32185
32827
  const context = { field, random, profile: () => currentProfile };
32186
32828
  const drops = createDropPool(layout, context);
@@ -32218,9 +32860,29 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32218
32860
  simulator.advance({ dt, dryingSeconds });
32219
32861
  },
32220
32862
  setWeather(weather) {
32221
- weatherState = { rain: Math.min(1, Math.max(0, weather.rain ?? weatherState.rain)) };
32863
+ weatherState = {
32864
+ rain: Math.min(1, Math.max(0, weather.rain ?? weatherState.rain)),
32865
+ drying: Math.max(0, weather.drying ?? weatherState.drying ?? 1)
32866
+ };
32222
32867
  },
32223
32868
  weather: () => ({ ...weatherState }),
32869
+ wash(maxDryness = WASH_BELOW_DRYNESS) {
32870
+ simulator.refreshBounds();
32871
+ const { firstRow, lastRow, firstColumn, lastColumn } = simulator.bounds;
32872
+ let cleared = 0;
32873
+ for (let row = firstRow; row <= lastRow; row++) {
32874
+ for (let column = firstColumn; column <= lastColumn; column++) {
32875
+ const index = row * layout.width + column;
32876
+ if (field.thickness[index] > 0 && field.dryness[index] < maxDryness) {
32877
+ field.thickness[index] = 0;
32878
+ field.dryness[index] = 0;
32879
+ field.dampness[index] = 0;
32880
+ cleared++;
32881
+ }
32882
+ }
32883
+ }
32884
+ return cleared;
32885
+ },
32224
32886
  clear() {
32225
32887
  field.thickness.fill(0);
32226
32888
  field.dryness.fill(0);
@@ -32324,8 +32986,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32324
32986
  bedHeight = roadY * 1e3 + z * 14 - (x - params.roadSlopeAnchorX) * 5;
32325
32987
  roughness = 0.55;
32326
32988
  }
32327
- const noise2 = valueNoise(column * 0.35, row * 0.35) * 0.6 + valueNoise(column * 1.3, row * 1.3) * 0.4;
32328
- bed[index] = bedHeight + (noise2 - 0.5) * roughness * 2;
32989
+ const noise = valueNoise(column * 0.35, row * 0.35) * 0.6 + valueNoise(column * 1.3, row * 1.3) * 0.4;
32990
+ bed[index] = bedHeight + (noise - 0.5) * roughness * 2;
32329
32991
  vertical[index] = region === 0 || region === 2 ? 0.95 : 1;
32330
32992
  pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, row * 0.21) * 0.5 + hashCell(column, row) * 0.5) * (region === 3 ? 1.25 : 1);
32331
32993
  }
@@ -32482,8 +33144,8 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
32482
33144
  for (let column = 0; column < columns; column++) {
32483
33145
  const index = (region.firstRow + row) * columns + column;
32484
33146
  const centre = add2(add2(region.origin, scale(columnAxis, (column + 0.5) * cell)), scale(region.rowAxis, (row + 0.5) * cell));
32485
- const noise2 = valueNoise(column * 0.35, (region.firstRow + row) * 0.35) * 0.6 + valueNoise(column * 1.3, (region.firstRow + row) * 1.3) * 0.4;
32486
- bed[index] = centre[1] * 1e3 + (noise2 - 0.5) * strip.roughness * 2;
33147
+ const noise = valueNoise(column * 0.35, (region.firstRow + row) * 0.35) * 0.6 + valueNoise(column * 1.3, (region.firstRow + row) * 1.3) * 0.4;
33148
+ bed[index] = centre[1] * 1e3 + (noise - 0.5) * strip.roughness * 2;
32487
33149
  vertical[index] = region.isVertical ? 0.95 : 1;
32488
33150
  pin[index] = 0.62 + 0.76 * (valueNoise(column * 0.21 + 40, (region.firstRow + row) * 0.21) * 0.5 + valueNoise(column * 0.9, row * 0.9) * 0.5);
32489
33151
  }
@@ -33150,11 +33812,11 @@ ${problems.map((problem) => ` - ${problem}`).join("\n")}`);
33150
33812
  const index = [];
33151
33813
  for (let i = 0; i < this.ring; i++) index.push(0, 1 + i, 1 + (i + 1) % this.ring);
33152
33814
  geometry.setIndex(index);
33153
- const mesh = new T.Mesh(geometry);
33154
- mesh.name = name;
33155
- mesh.frustumCulled = false;
33156
- mesh.renderOrder = order;
33157
- return { mesh, position, color };
33815
+ const mesh2 = new T.Mesh(geometry);
33816
+ mesh2.name = name;
33817
+ mesh2.frustumCulled = false;
33818
+ mesh2.renderOrder = order;
33819
+ return { mesh: mesh2, position, color };
33158
33820
  }
33159
33821
  assign(def, materials) {
33160
33822
  this.outer.mesh.material = materials.stainFilm(def);
@@ -33877,12 +34539,12 @@ void main(){
33877
34539
  this.group = new THREE.Group();
33878
34540
  this.group.name = "film_grid";
33879
34541
  for (const region of layout.regions) {
33880
- const mesh = new THREE.Mesh(createStripGeometry(THREE, layout, region), this.material.material);
33881
- mesh.frustumCulled = false;
33882
- mesh.renderOrder = 3;
33883
- mesh.name = `film_${region.id}`;
33884
- this.strips.push(mesh);
33885
- this.group.add(mesh);
34542
+ const mesh2 = new THREE.Mesh(createStripGeometry(THREE, layout, region), this.material.material);
34543
+ mesh2.frustumCulled = false;
34544
+ mesh2.renderOrder = 3;
34545
+ mesh2.name = `film_${region.id}`;
34546
+ this.strips.push(mesh2);
34547
+ this.group.add(mesh2);
33886
34548
  }
33887
34549
  this.dropsView = createFilmDrops(THREE, layout.rules.dropCapacity, profile);
33888
34550
  this.group.add(this.dropsView.drops, this.dropsView.bead);
@@ -33927,6 +34589,19 @@ void main(){
33927
34589
  this.pending = this.union(this.pending, rect);
33928
34590
  return this.takePending();
33929
34591
  }
34592
+ /** Clears the cells less than `maxDryness` dry and sends the rectangle that held them up with the next update. Returns how many it cleared. */
34593
+ wash(maxDryness) {
34594
+ const cleared = this.film.wash(maxDryness);
34595
+ if (cleared > 0) {
34596
+ const bounds = this.film.simulator.bounds;
34597
+ if (bounds.lastRow >= 0) {
34598
+ const rect = { firstRow: bounds.firstRow, lastRow: bounds.lastRow, firstColumn: bounds.firstColumn, lastColumn: bounds.lastColumn };
34599
+ this.encode(rect);
34600
+ this.pending = this.union(this.pending, rect);
34601
+ }
34602
+ }
34603
+ return cleared;
34604
+ }
33930
34605
  takePending() {
33931
34606
  const rect = this.pending;
33932
34607
  this.pending = null;
@@ -33982,7 +34657,7 @@ void main(){
33982
34657
  return { firstRow: Math.min(a.firstRow, b.firstRow), lastRow: Math.max(a.lastRow, b.lastRow), firstColumn: Math.min(a.firstColumn, b.firstColumn), lastColumn: Math.max(a.lastColumn, b.lastColumn) };
33983
34658
  }
33984
34659
  dispose() {
33985
- for (const mesh of this.strips) mesh.geometry.dispose();
34660
+ for (const mesh2 of this.strips) mesh2.geometry.dispose();
33986
34661
  this.material.dispose();
33987
34662
  this.dropsView.dispose();
33988
34663
  this.texture.dispose();
@@ -33996,11 +34671,12 @@ void main(){
33996
34671
  var DEFAULT_GRID_METRES = 2;
33997
34672
  var DEFAULT_CELL2 = 0.0125;
33998
34673
  var BAKE_RANGE_FACTOR = 1.25;
34674
+ var DEFAULT_WASHABLE = ["blood", "old_blood"];
33999
34675
  var EDGE_MARGIN = 0.35;
34000
34676
  var FLOOR_REACH = 0.3;
34001
34677
  var DROP_HEIGHT = 2.5;
34002
34678
  var DEFAULT_DROP_RADIUS = 3;
34003
- var FALL_SPEED = 2;
34679
+ var FALL_SPEED2 = 2;
34004
34680
  var VISIBLE_THICKNESS22 = 0.035;
34005
34681
  var DEFAULT_HALF = 50;
34006
34682
  var FREE_AFTER_EMPTY = 2;
@@ -34021,6 +34697,7 @@ void main(){
34021
34697
  __publicField5(this, "staging", /* @__PURE__ */ new Map());
34022
34698
  __publicField5(this, "now");
34023
34699
  __publicField5(this, "rain", 0);
34700
+ __publicField5(this, "drying", 1);
34024
34701
  __publicField5(this, "time", 0);
34025
34702
  __publicField5(this, "bakedCount", 0);
34026
34703
  __publicField5(this, "last", { grids: 0, drops: 0, wetCells: 0, simMs: 0, starved: 0, baked: 0, uploadedTexels: 0 });
@@ -34040,13 +34717,24 @@ void main(){
34040
34717
  }
34041
34718
  setWeather(weather) {
34042
34719
  this.rain = Math.min(1, Math.max(0, weather.rain));
34043
- for (const grid of this.grids) grid.film.setWeather({ rain: this.rain });
34720
+ if (weather.drying !== void 0) this.drying = Math.max(0, weather.drying);
34721
+ for (const grid of this.grids) grid.film.setWeather({ rain: this.rain, drying: this.drying });
34722
+ }
34723
+ /**
34724
+ * Rain washes the fresh stain away (the visor's `ZG.wipe` with mode "chuva"): in the grids of the washable liquids (default blood and old blood), the
34725
+ * cells that are less than `maxDryness` dry (default 0.5) are cleared. Returns how many cells it cleared.
34726
+ */
34727
+ wash(maxDryness) {
34728
+ const washable = this.options.washable ?? DEFAULT_WASHABLE;
34729
+ let cleared = 0;
34730
+ for (const grid of this.grids) if (washable.includes(grid.profile.id)) cleared += grid.wash(maxDryness);
34731
+ return cleared;
34044
34732
  }
34045
34733
  /** Throws a drop of a liquid: it lands in the grid under it (made on the spot) and leaves the impact splash. Null when it is out of range or has no surface. */
34046
34734
  launch(request) {
34047
34735
  const grid = this.gridFor(request.fluid, request.point, request.surfaces, request.tint);
34048
34736
  if (!grid) return null;
34049
- const velocity = request.velocity ?? [0, -FALL_SPEED, 0];
34737
+ const velocity = request.velocity ?? [0, -FALL_SPEED2, 0];
34050
34738
  grid.film.launch(request.point, velocity, request.radius ?? DEFAULT_DROP_RADIUS);
34051
34739
  return grid;
34052
34740
  }
@@ -34071,7 +34759,7 @@ void main(){
34071
34759
  const layout = layoutFromSurfaces({ ...found, center: point, columns: this.columns(), rows: this.columns(), cellSize: this.options.cellSize ?? DEFAULT_CELL2 });
34072
34760
  if (this.grids.length >= (this.options.maxGrids ?? DEFAULT_MAX_GRIDS)) this.evict(point);
34073
34761
  const grid = new FilmGrid(this.THREE, { layout, profile, random: this.options.random, center: point, ...tint ? { tint } : {}, ...this.options.base === void 0 ? {} : { base: this.options.base } });
34074
- grid.film.setWeather({ rain: this.rain });
34762
+ grid.film.setWeather({ rain: this.rain, drying: this.drying });
34075
34763
  this.grids.push(grid);
34076
34764
  this.group.add(grid.group);
34077
34765
  return grid;
@@ -34148,7 +34836,7 @@ void main(){
34148
34836
  /** Whether `point` is over the grid, away from its edge, so a splash there does not fall off the strip. */
34149
34837
  covers(grid, point) {
34150
34838
  const layout = grid.film.layout;
34151
- const wide = layout.width * layout.cellSize;
34839
+ const wide2 = layout.width * layout.cellSize;
34152
34840
  for (const region of layout.regions) {
34153
34841
  const local2 = [point[0] - region.origin[0], point[1] - region.origin[1], point[2] - region.origin[2]];
34154
34842
  const above2 = local2[0] * region.normal[0] + local2[1] * region.normal[1] + local2[2] * region.normal[2];
@@ -34156,7 +34844,7 @@ void main(){
34156
34844
  const sideways = local2[0] * region.columnAxis[0] + local2[1] * region.columnAxis[1] + local2[2] * region.columnAxis[2];
34157
34845
  const down = local2[0] * region.rowAxis[0] + local2[1] * region.rowAxis[1] + local2[2] * region.rowAxis[2];
34158
34846
  const length3 = region.rowCount * layout.cellSize;
34159
- if (sideways > EDGE_MARGIN && sideways < wide - EDGE_MARGIN && down > EDGE_MARGIN && down < length3 - EDGE_MARGIN) return true;
34847
+ if (sideways > EDGE_MARGIN && sideways < wide2 - EDGE_MARGIN && down > EDGE_MARGIN && down < length3 - EDGE_MARGIN) return true;
34160
34848
  }
34161
34849
  return false;
34162
34850
  }
@@ -34307,6 +34995,7 @@ void main(){
34307
34995
  return landed;
34308
34996
  },
34309
34997
  setWeather: (weather) => manager.setWeather(weather),
34998
+ wash: (maxDryness) => manager.wash(maxDryness),
34310
34999
  update(dt) {
34311
35000
  if (leaks.size === 0) return;
34312
35001
  const streams = new Map(fluids.streams().map((stream) => [stream.id, stream]));
@@ -34399,15 +35088,15 @@ void main(){
34399
35088
  if (s.kind === "box") geo = new THREE.BoxGeometry(s.size[0], s.size[1], s.size[2]);
34400
35089
  else if (s.kind === "cylinder") geo = new THREE.CylinderGeometry(s.radius, s.radius, s.height, seg);
34401
35090
  else geo = new THREE.SphereGeometry(s.radius, seg, Math.max(6, seg - 4));
34402
- const mesh = new THREE.Mesh(geo, materialFor(p));
34403
- mesh.name = p.id;
34404
- mesh.userData.destructionPart = p.id;
34405
- mesh.userData.destructionMaterial = p.material;
34406
- mesh.position.set(p.position[0], p.position[1], p.position[2]);
34407
- if (p.rotation) mesh.rotation.set(p.rotation[0], p.rotation[1], p.rotation[2], "XYZ");
34408
- mesh.castShadow = shadows && !TRANSPARENT[p.material];
34409
- mesh.receiveShadow = shadows;
34410
- root.add(mesh);
35091
+ const mesh2 = new THREE.Mesh(geo, materialFor(p));
35092
+ mesh2.name = p.id;
35093
+ mesh2.userData.destructionPart = p.id;
35094
+ mesh2.userData.destructionMaterial = p.material;
35095
+ mesh2.position.set(p.position[0], p.position[1], p.position[2]);
35096
+ if (p.rotation) mesh2.rotation.set(p.rotation[0], p.rotation[1], p.rotation[2], "XYZ");
35097
+ mesh2.castShadow = shadows && !TRANSPARENT[p.material];
35098
+ mesh2.receiveShadow = shadows;
35099
+ root.add(mesh2);
34411
35100
  }
34412
35101
  return root;
34413
35102
  }
@@ -35732,16 +36421,16 @@ void main(){
35732
36421
  const up = new THREE.Vector3(0, 1, 0);
35733
36422
  const clear = () => {
35734
36423
  projectiles.length = 0;
35735
- for (const mesh of meshes) mesh.removeFromParent();
36424
+ for (const mesh2 of meshes) mesh2.removeFromParent();
35736
36425
  meshes.length = 0;
35737
36426
  };
35738
36427
  const addMesh = (geometry, color, metalness, at3) => {
35739
- const mesh = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color, roughness: 0.6, metalness }));
35740
- mesh.castShadow = true;
35741
- mesh.position.set(at3[0], at3[1], at3[2]);
35742
- scene2.add(mesh);
35743
- meshes.push(mesh);
35744
- return mesh;
36428
+ const mesh2 = new THREE.Mesh(geometry, new THREE.MeshStandardMaterial({ color, roughness: 0.6, metalness }));
36429
+ mesh2.castShadow = true;
36430
+ mesh2.position.set(at3[0], at3[1], at3[2]);
36431
+ scene2.add(mesh2);
36432
+ meshes.push(mesh2);
36433
+ return mesh2;
35745
36434
  };
35746
36435
  const remove = (projectile) => {
35747
36436
  for (const extra of projectile.attached ?? []) {
@@ -35759,9 +36448,9 @@ void main(){
35759
36448
  const isMolotov = !!use.fire;
35760
36449
  const isRocket = use.trigger === "impact" && (use.armDistance ?? 0) > 0;
35761
36450
  const geometry = isRocket ? new THREE.CylinderGeometry(0.04, 0.04, 0.5, 8) : isMolotov ? new THREE.CylinderGeometry(0.035, 0.04, 0.2, 8) : new THREE.SphereGeometry(0.045, 8, 6);
35762
- const mesh = addMesh(geometry, isMolotov ? 3897915 : isRocket ? 7041116 : 4936480, isMolotov ? 0 : 0.4, use.origin);
36451
+ const mesh2 = addMesh(geometry, isMolotov ? 3897915 : isRocket ? 7041116 : 4936480, isMolotov ? 0 : 0.4, use.origin);
35763
36452
  projectiles.push({
35764
- mesh,
36453
+ mesh: mesh2,
35765
36454
  pos: new THREE.Vector3(use.origin[0], use.origin[1], use.origin[2]),
35766
36455
  vel: new THREE.Vector3(use.velocity[0], use.velocity[1], use.velocity[2]),
35767
36456
  weapon: context.toolId(),
@@ -35812,11 +36501,11 @@ void main(){
35812
36501
  const placed = { ...use, origin: at3 };
35813
36502
  const body = createExplosiveBody(placed, arsenal.explosives, arsenal.rules.explosives);
35814
36503
  if (!body) return;
35815
- const mesh = addMesh(new THREE.BoxGeometry(0.18, 0.06, 0.1), use.explosive === "claymore" ? 5597999 : 12757112, 0.1, at3);
35816
- mesh.rotation.y = Math.atan2(use.direction[0], use.direction[2]);
36504
+ const mesh2 = addMesh(new THREE.BoxGeometry(0.18, 0.06, 0.1), use.explosive === "claymore" ? 5597999 : 12757112, 0.1, at3);
36505
+ mesh2.rotation.y = Math.atan2(use.direction[0], use.direction[2]);
35817
36506
  const attached = [markerAt(at3)];
35818
36507
  const segment = tripwireSegment(placed);
35819
- const projectile = { mesh, pos: new THREE.Vector3(at3[0], at3[1], at3[2]), vel: new THREE.Vector3(), weapon: context.toolId(), fuse: 0, burstOnImpact: false, energy: use.energy, radius: use.radius, age: 0, body, placed: true, attached };
36508
+ const projectile = { mesh: mesh2, pos: new THREE.Vector3(at3[0], at3[1], at3[2]), vel: new THREE.Vector3(), weapon: context.toolId(), fuse: 0, burstOnImpact: false, energy: use.energy, radius: use.radius, age: 0, body, placed: true, attached };
35820
36509
  if (segment) {
35821
36510
  const wire = { from: segment.from, to: segment.to, letThrough: /* @__PURE__ */ new Set() };
35822
36511
  for (const id of touching(wire)) wire.letThrough.add(id);
@@ -36493,13 +37182,13 @@ void main(){
36493
37182
  if (!g || g.done) break;
36494
37183
  g.done = true;
36495
37184
  const [a, b] = ev.parts;
36496
- const many2 = g.list.length > 1;
37185
+ const many = g.list.length > 1;
36497
37186
  c.log.add({
36498
37187
  channel: "event",
36499
37188
  type: "detach",
36500
37189
  objectId: ev.object,
36501
37190
  itemId: c.itemIdOf(ev.object),
36502
- text: many2 ? t(`ev.detach.many.${ev.by === "tool" ? "tool" : "break"}`, { obj: c.nameOf(ev.object), joints: joints(g.list.length), pieces: pieces(g.groups) }) : t("ev.detach", { obj: c.nameOf(ev.object), a, b, by: t(`by.${ev.by}`), pieces: pieces(g.groups) }),
37191
+ text: many ? t(`ev.detach.many.${ev.by === "tool" ? "tool" : "break"}`, { obj: c.nameOf(ev.object), joints: joints(g.list.length), pieces: pieces(g.groups) }) : t("ev.detach", { obj: c.nameOf(ev.object), a, b, by: t(`by.${ev.by}`), pieces: pieces(g.groups) }),
36503
37192
  data: { joint: ev.joint, joints: g.list.map((j) => j.joint), parts: ev.parts, by: ev.by, groups: g.groups }
36504
37193
  });
36505
37194
  break;
@@ -40036,6 +40725,8 @@ void main(){
40036
40725
  camera: camera2,
40037
40726
  arsenal,
40038
40727
  fluids,
40728
+ film: filmBridge,
40729
+ vfx: vfx.world,
40039
40730
  fluidRenderer: fluidView,
40040
40731
  audio,
40041
40732
  environment,