@adatechnology/game-fluids-three 0.3.1 → 0.4.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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as THREE_NS from 'three';
2
+ import { WarmUpProbes } from '@adatechnology/game-contracts';
2
3
  import { VfxWorld } from '@adatechnology/game-vfx';
3
4
  import { ThreeLib as ThreeLib$1 } from '@adatechnology/game-vfx-three';
4
5
  import { Vec3, FluidWorld, Film, StainTint, FilmLayout, FilmProfile, FilmRandom, LiquidId, SurfaceSpec, FilmLook, FilmRegion } from '@adatechnology/game-fluids';
@@ -56,6 +57,8 @@ interface FluidRenderer {
56
57
  /** Draw the liquid of a world container inside `object` (a volume clipped by the liquid surface, plus steam). */
57
58
  bindContainer(containerId: string, object: THREE_NS.Object3D, options?: BindOptions): void;
58
59
  unbindContainer(containerId: string): void;
60
+ /** Meshes that use the materials of the liquid bodies, streams and stains, to render once so that the first leak does not build their shader programs in the middle of a frame. */
61
+ warmUpProbes(): WarmUpProbes;
59
62
  /** Advance the visuals (call it after `fluids.update(dt)`). */
60
63
  update(dt: number): void;
61
64
  /** Remove every stain, stream and particle (the bindings stay). */
@@ -3088,6 +3088,17 @@ var GameFluidsThree = (() => {
3088
3088
  ])
3089
3089
  ]
3090
3090
  };
3091
+ var FUEL_LEAK_ONLY = {
3092
+ id: "fuel_leak_only",
3093
+ pageId: "vazamento",
3094
+ team: "vehicles",
3095
+ budget: { particles: 200, lights: 0 },
3096
+ loop: 11,
3097
+ view: FUEL_LEAK.view,
3098
+ setup: FUEL_LEAK.setup,
3099
+ timeline: [decal(POOL[0], 0, POOL[2], 2.2, 2040368, { grow: 3, life: 60, ar: 0.6, fluid: "oil" })],
3100
+ tick: [emit("drip", "normal", 12, { p: TANK, box: [0.05, 0, 0.05], sp: 0, life: 1, size: 0.03, c0: 5921402, a: 0.9, g: 9.8, fi: 0, gnd: 1 })]
3101
+ };
3091
3102
  var CAR_EXPLOSION = {
3092
3103
  id: "car_explosion",
3093
3104
  pageId: "explosao",
@@ -3441,6 +3452,8 @@ var GameFluidsThree = (() => {
3441
3452
  ];
3442
3453
  var PI = Math.PI;
3443
3454
  var SMOKE = { color: 9079430, width: 10, height: 8, gravity: -0.08, drag: 2.2, growth: 2.1 };
3455
+ var DEFAULT_SMOKE = { puffs: 2, radius: 0.01, opacity: 0.14, life: 0.7, drift: 0.2, rise: 0.06, growth: 2.1, flash: 1 };
3456
+ var DEFAULT_SPREAD = { drift: 0.1, rise: 0.04, side: 0.06 };
3444
3457
  var FLASH_SECONDS = 0.055;
3445
3458
  var FLASH_RAMP = [0.6, 0.9];
3446
3459
  var FLASH_OPACITY = 0.95;
@@ -3448,8 +3461,10 @@ var GameFluidsThree = (() => {
3448
3461
  var FLAME_ADDITIVE = 3;
3449
3462
  var ORIGIN2 = [0, 0, 0];
3450
3463
  var SIZE = variable("size");
3464
+ var FLASH_SCALE = variable("flash");
3465
+ var fallback = (name, defaultValue) => ite(ne(variable(name), void 0), variable(name), defaultValue);
3451
3466
  var wide = (width) => mul(sub(rand(), 0.5), width);
3452
- var puff2 = (point, velocity, world, radius, life, opacity) => mesh({
3467
+ var puff2 = (point, velocity, world, radius, life, opacity, growth = SMOKE.growth) => mesh({
3453
3468
  shape: "sphere",
3454
3469
  p: point,
3455
3470
  v: velocity,
@@ -3464,7 +3479,7 @@ var GameFluidsThree = (() => {
3464
3479
  lit: true,
3465
3480
  g: SMOKE.gravity,
3466
3481
  drag: SMOKE.drag,
3467
- grow: SMOKE.growth
3482
+ grow: growth
3468
3483
  });
3469
3484
  var flameBit = (point, velocity, world, radius, life) => [
3470
3485
  set("bitPoint", point),
@@ -3497,37 +3512,55 @@ var GameFluidsThree = (() => {
3497
3512
  id: "viewmodel_muzzle_flash",
3498
3513
  pageId: "event:viewmodel_muzzle_flash",
3499
3514
  team: "weapons",
3500
- budget: { particles: 12, lights: 1 },
3515
+ budget: { particles: 14, lights: 1 },
3501
3516
  loop: 0.1,
3517
+ setup: [
3518
+ set("size", fallback("size", 1)),
3519
+ set("puffs", fallback("puffs", DEFAULT_SMOKE.puffs)),
3520
+ set("radius", fallback("radius", DEFAULT_SMOKE.radius)),
3521
+ set("opacity", fallback("opacity", DEFAULT_SMOKE.opacity)),
3522
+ set("life", fallback("life", DEFAULT_SMOKE.life)),
3523
+ set("drift", fallback("drift", DEFAULT_SMOKE.drift)),
3524
+ set("rise", fallback("rise", DEFAULT_SMOKE.rise)),
3525
+ set("growth", fallback("growth", DEFAULT_SMOKE.growth)),
3526
+ set("flash", fallback("flash", DEFAULT_SMOKE.flash)),
3527
+ set("driftSpread", fallback("driftSpread", DEFAULT_SPREAD.drift)),
3528
+ set("riseSpread", fallback("riseSpread", DEFAULT_SPREAD.rise)),
3529
+ set("sideSpread", fallback("sideSpread", DEFAULT_SPREAD.side)),
3530
+ set("lifeSpread", ite(ne(variable("lifeSpread"), void 0), variable("lifeSpread"), mul(variable("life"), 0.3)))
3531
+ ],
3502
3532
  timeline: [
3503
3533
  set("angles", []),
3504
3534
  repeat(4, append("angles", add(div(mul(INDEX, PI), 2), mul(rand(), 0.6)))),
3505
3535
  set("roll", mul(rand(), PI)),
3506
- flash(ORIGIN2, 16756832, mul(4, SIZE), FLASH_SECONDS, mul(2, SIZE), true, true),
3507
- flashPiece("cone_open", mul(0.013, SIZE), mul(0.085, SIZE), 10, [mul(0.042, SIZE), 0, 0], [0, 0, -PI / 2], 16757575),
3508
- repeat(
3509
- 4,
3510
- set("angle", pick(variable("angles"), INDEX)),
3511
- flashPiece(
3512
- "cone",
3513
- mul(5e-3, SIZE),
3514
- mul(0.04, SIZE),
3515
- 6,
3516
- [mul(0.012, SIZE), mul(cos(variable("angle")), 0.016, SIZE), mul(sin(variable("angle")), 0.016, SIZE)],
3517
- [variable("angle"), 0, -PI / 2],
3518
- 16757575
3519
- )
3520
- ),
3521
- flashPiece("sphere", mul(0.016, SIZE), void 0, 12, [mul(8e-3, SIZE), 0, 0], [0, 0, 0], 16773568),
3536
+ when(gt(FLASH_SCALE, 0), [
3537
+ flash(ORIGIN2, 16756832, mul(4, SIZE, FLASH_SCALE), FLASH_SECONDS, mul(2, SIZE, FLASH_SCALE), true, true),
3538
+ flashPiece("cone_open", mul(0.013, SIZE, FLASH_SCALE), mul(0.085, SIZE, FLASH_SCALE), 10, [mul(0.042, SIZE, FLASH_SCALE), 0, 0], [0, 0, -PI / 2], 16757575),
3539
+ repeat(
3540
+ 4,
3541
+ set("angle", pick(variable("angles"), INDEX)),
3542
+ flashPiece(
3543
+ "cone",
3544
+ mul(5e-3, SIZE, FLASH_SCALE),
3545
+ mul(0.04, SIZE, FLASH_SCALE),
3546
+ 6,
3547
+ [mul(0.012, SIZE, FLASH_SCALE), mul(cos(variable("angle")), 0.016, SIZE, FLASH_SCALE), mul(sin(variable("angle")), 0.016, SIZE, FLASH_SCALE)],
3548
+ [variable("angle"), 0, -PI / 2],
3549
+ 16757575
3550
+ )
3551
+ ),
3552
+ flashPiece("sphere", mul(0.016, SIZE, FLASH_SCALE), void 0, 12, [mul(8e-3, SIZE, FLASH_SCALE), 0, 0], [0, 0, 0], 16773568)
3553
+ ]),
3522
3554
  repeat(
3523
- 6,
3555
+ variable("puffs"),
3524
3556
  puff2(
3525
3557
  ORIGIN2,
3526
- [add(0.15, mul(rand(), 0.35)), add(0.03, mul(rand(), 0.12)), mul(sub(rand(), 0.5), 0.14)],
3558
+ [add(variable("drift"), mul(rand(), variable("driftSpread"))), add(variable("rise"), mul(rand(), variable("riseSpread"))), mul(sub(rand(), 0.5), variable("sideSpread"))],
3527
3559
  [0, 0, 0],
3528
- mul(0.013, SIZE),
3529
- add(1.3, mul(rand(), 0.6)),
3530
- 0.45
3560
+ mul(variable("radius"), SIZE),
3561
+ add(variable("life"), mul(rand(), variable("lifeSpread"))),
3562
+ variable("opacity"),
3563
+ variable("growth")
3531
3564
  )
3532
3565
  )
3533
3566
  ]
@@ -3581,8 +3614,10 @@ var GameFluidsThree = (() => {
3581
3614
  aliasOf(ENGINE_SMOKE, "vehicle_fire", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 3 }, loop: 12 }),
3582
3615
  /** The exhaust of a car under way: `engine_smoke` in the best condition, for half a second so a moving car asks again. */
3583
3616
  aliasOf(ENGINE_SMOKE, "vehicle_exhaust", { params: { id: "condition", options: ["100", "60", "30", "10"], default: 0 }, loop: 0.5 }),
3584
- /** A hole in the tank: the page's own petrol leak (drips, pool, sparks, fire running to the tank). */
3585
- aliasOf(FUEL_LEAK, "vehicle_leak", {})
3617
+ /** A hole in the tank: the page's petrol leak without the fire and the blast (the pool and the drips). A normal bullet does only this. */
3618
+ aliasOf(FUEL_LEAK_ONLY, "vehicle_leak", {}),
3619
+ /** The leak caught fire (an incendiary or tracer round, a spark, a fire near): the page's whole sequence, the fire running along the pool to the tank and the blast. */
3620
+ aliasOf(FUEL_LEAK, "vehicle_leak_ignited", {})
3586
3621
  ];
3587
3622
  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];
3588
3623
  var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
@@ -7225,6 +7260,15 @@ var GameFluidsThree = (() => {
7225
7260
  };
7226
7261
  }
7227
7262
 
7263
+ // ../game-contracts/dist/index.js
7264
+ var KIT_ORIGIN_KEY = "kitOrigin";
7265
+ function markKitOrigin(object, origin) {
7266
+ const node = object;
7267
+ node.userData = node.userData ?? {};
7268
+ node.userData[KIT_ORIGIN_KEY] = { ...origin };
7269
+ return object;
7270
+ }
7271
+
7228
7272
  // ../game-vfx-three/dist/index.js
7229
7273
  var GLOW_VERTEX = `attribute vec4 aP;attribute vec4 aC;varying vec2 vU;varying vec4 vC;
7230
7274
  void main(){vec3 R=vec3(viewMatrix[0][0],viewMatrix[1][0],viewMatrix[2][0]);vec3 Up=vec3(viewMatrix[0][1],viewMatrix[1][1],viewMatrix[2][1]);
@@ -7739,6 +7783,79 @@ void main(){
7739
7783
  }
7740
7784
  };
7741
7785
  }
7786
+ var SPHERE_DEFAULT_WIDTH = 8;
7787
+ var SPHERE_DEFAULT_HEIGHT = 6;
7788
+ function geometryOf(THREE, particle) {
7789
+ switch (particle.shape) {
7790
+ case "cone":
7791
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
7792
+ case "cone_open":
7793
+ return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
7794
+ case "sphere":
7795
+ return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
7796
+ case "icosahedron":
7797
+ return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
7798
+ }
7799
+ }
7800
+ function materialOf(THREE, particle) {
7801
+ if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
7802
+ return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
7803
+ }
7804
+ function createMeshLayerProbes(THREE) {
7805
+ const geometry = new THREE.BoxGeometry(1e-3, 1e-3, 1e-3);
7806
+ return [{ lit: true, additive: false }, { lit: false, additive: false }, { lit: false, additive: true }].map(
7807
+ (kind) => new THREE.Mesh(geometry, materialOf(THREE, { ...kind, color: 8421504, opacity: 0.5 }))
7808
+ );
7809
+ }
7810
+ function createMeshLayer(THREE, pool) {
7811
+ const group = new THREE.Group();
7812
+ group.name = "workshop-meshes";
7813
+ const drawn = /* @__PURE__ */ new Map();
7814
+ const frame = new THREE.Quaternion();
7815
+ const euler = new THREE.Euler();
7816
+ const turn = new THREE.Quaternion();
7817
+ const offset = new THREE.Vector3();
7818
+ function release(mesh2) {
7819
+ mesh2.removeFromParent();
7820
+ mesh2.geometry.dispose();
7821
+ mesh2.material.dispose();
7822
+ }
7823
+ return {
7824
+ group,
7825
+ sync() {
7826
+ const live = /* @__PURE__ */ new Set();
7827
+ for (const particle of pool.particles) {
7828
+ live.add(particle.id);
7829
+ let mesh2 = drawn.get(particle.id);
7830
+ if (!mesh2) {
7831
+ mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
7832
+ mesh2.frustumCulled = false;
7833
+ mesh2.renderOrder = particle.additive ? 5 : 3;
7834
+ drawn.set(particle.id, mesh2);
7835
+ group.add(mesh2);
7836
+ }
7837
+ frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
7838
+ offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
7839
+ mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
7840
+ turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
7841
+ mesh2.quaternion.copy(frame).multiply(turn);
7842
+ mesh2.scale.setScalar(particle.scale);
7843
+ mesh2.material.opacity = particle.opacity;
7844
+ }
7845
+ for (const [id, mesh2] of drawn) {
7846
+ if (live.has(id)) continue;
7847
+ release(mesh2);
7848
+ drawn.delete(id);
7849
+ }
7850
+ return pool.count;
7851
+ },
7852
+ dispose() {
7853
+ for (const mesh2 of drawn.values()) release(mesh2);
7854
+ drawn.clear();
7855
+ group.removeFromParent();
7856
+ }
7857
+ };
7858
+ }
7742
7859
  var clamp5 = (value, min2, max2) => value < min2 ? min2 : value > max2 ? max2 : value;
7743
7860
  function layer(container, name, blend) {
7744
7861
  const element = container.ownerDocument.createElement("div");
@@ -7908,73 +8025,6 @@ void main() {
7908
8025
  }
7909
8026
  };
7910
8027
  }
7911
- var SPHERE_DEFAULT_WIDTH = 8;
7912
- var SPHERE_DEFAULT_HEIGHT = 6;
7913
- function geometryOf(THREE, particle) {
7914
- switch (particle.shape) {
7915
- case "cone":
7916
- return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
7917
- case "cone_open":
7918
- return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
7919
- case "sphere":
7920
- return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
7921
- case "icosahedron":
7922
- return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
7923
- }
7924
- }
7925
- function materialOf(THREE, particle) {
7926
- if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
7927
- return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
7928
- }
7929
- function createMeshLayer(THREE, pool) {
7930
- const group = new THREE.Group();
7931
- group.name = "workshop-meshes";
7932
- const drawn = /* @__PURE__ */ new Map();
7933
- const frame = new THREE.Quaternion();
7934
- const euler = new THREE.Euler();
7935
- const turn = new THREE.Quaternion();
7936
- const offset = new THREE.Vector3();
7937
- function release(mesh2) {
7938
- mesh2.removeFromParent();
7939
- mesh2.geometry.dispose();
7940
- mesh2.material.dispose();
7941
- }
7942
- return {
7943
- group,
7944
- sync() {
7945
- const live = /* @__PURE__ */ new Set();
7946
- for (const particle of pool.particles) {
7947
- live.add(particle.id);
7948
- let mesh2 = drawn.get(particle.id);
7949
- if (!mesh2) {
7950
- mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
7951
- mesh2.frustumCulled = false;
7952
- mesh2.renderOrder = particle.additive ? 5 : 3;
7953
- drawn.set(particle.id, mesh2);
7954
- group.add(mesh2);
7955
- }
7956
- frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
7957
- offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
7958
- mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
7959
- turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
7960
- mesh2.quaternion.copy(frame).multiply(turn);
7961
- mesh2.scale.setScalar(particle.scale);
7962
- mesh2.material.opacity = particle.opacity;
7963
- }
7964
- for (const [id, mesh2] of drawn) {
7965
- if (live.has(id)) continue;
7966
- release(mesh2);
7967
- drawn.delete(id);
7968
- }
7969
- return pool.count;
7970
- },
7971
- dispose() {
7972
- for (const mesh2 of drawn.values()) release(mesh2);
7973
- drawn.clear();
7974
- group.removeFromParent();
7975
- }
7976
- };
7977
- }
7978
8028
  var WORKSHOP_SPRITE_VERTEX = `attribute float size;attribute float alpha;attribute vec3 pcol;varying vec3 vC;varying float vA;uniform float uScale;
7979
8029
  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;}`;
7980
8030
  var WORKSHOP_SPRITE_FRAGMENT = `uniform float uSoft;varying vec3 vC;varying float vA;
@@ -8339,7 +8389,14 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8339
8389
  const requested = options.vfx;
8340
8390
  const world = isWorld(requested) ? requested : createVfx({ profile: options.quality ?? "medium", reducedMotion, ...requested });
8341
8391
  const pools = world.pools();
8342
- const root = new THREE.Group();
8392
+ let anchors;
8393
+ const releaseAnchors = () => {
8394
+ if (!anchors) return;
8395
+ anchors.meshes[0]?.geometry.dispose();
8396
+ for (const mesh2 of anchors.meshes) mesh2.material.dispose();
8397
+ anchors = void 0;
8398
+ };
8399
+ const root = markKitOrigin(new THREE.Group(), { owner: "game-vfx-three", kind: "effect" });
8343
8400
  options.scene.add(root);
8344
8401
  const direction = new THREE.Vector3(...options.lightDirection ?? LAB_LIGHT_DIRECTION).normalize();
8345
8402
  const sky = hexToLinearRgb(SKY_COLOR);
@@ -8455,6 +8512,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8455
8512
  syncAll();
8456
8513
  overlay?.update(screenNow());
8457
8514
  },
8515
+ warmUpProbes() {
8516
+ releaseAnchors();
8517
+ const meshes = createMeshLayerProbes(THREE);
8518
+ for (const mesh2 of meshes) mesh2.frustumCulled = false;
8519
+ const group = new THREE.Group();
8520
+ group.add(...meshes);
8521
+ anchors = { meshes, group };
8522
+ return { group, dispose: () => {
8523
+ } };
8524
+ },
8458
8525
  applyCamera() {
8459
8526
  const shake = screenNow().shake;
8460
8527
  if (reducedMotion || !(shake > SHAKE_FLOOR)) return () => {
@@ -8473,6 +8540,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8473
8540
  },
8474
8541
  screen: screenNow,
8475
8542
  dispose() {
8543
+ releaseAnchors();
8476
8544
  stopMarks();
8477
8545
  overlay?.dispose();
8478
8546
  workshopLayers?.dispose();
@@ -8685,6 +8753,11 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8685
8753
  this.vertices = 0;
8686
8754
  return;
8687
8755
  }
8756
+ if (state.closed && this.isShellOpaque()) {
8757
+ if (this.mesh) this.mesh.visible = false;
8758
+ this.vertices = 0;
8759
+ return;
8760
+ }
8688
8761
  this.build(state, quality);
8689
8762
  if (!this.mesh || !this.solid) return;
8690
8763
  this.mesh.material = materials.liquidBody(def);
@@ -8710,6 +8783,17 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8710
8783
  }
8711
8784
  } else this.steamAcc = 0;
8712
8785
  }
8786
+ /** Every mesh of the bound object (the liquid itself aside) is drawn with an opaque material. */
8787
+ isShellOpaque() {
8788
+ let opaque = true;
8789
+ this.object.traverse((child) => {
8790
+ const mesh2 = child;
8791
+ if (!opaque || !mesh2.isMesh || child === this.mesh) return;
8792
+ const materials = Array.isArray(mesh2.material) ? mesh2.material : [mesh2.material];
8793
+ if (materials.some((material) => material.transparent || material.opacity < 1)) opaque = false;
8794
+ });
8795
+ return opaque;
8796
+ }
8713
8797
  get triangles() {
8714
8798
  return this.visible ? Math.floor(this.vertices / 3) : 0;
8715
8799
  }
@@ -8835,6 +8919,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8835
8919
  }
8836
8920
  return m;
8837
8921
  }
8922
+ /** One material per role, in a clear and in an opaque liquid, which are the shader programs a leak will need (the colour is not part of a program). */
8923
+ probes() {
8924
+ const base = { id: "warm_up", color: 8421504, viscosity: 1, density: 1e3, dryTime: 60, gloss: 0.5 };
8925
+ const defs = [{ ...base, opacity: 0.5 }, { ...base, opacity: 1 }];
8926
+ return {
8927
+ plain: defs.flatMap((def) => [this.liquidBody(def), this.liquidStream(def)]),
8928
+ instanced: defs.map((def) => this.liquidStream(def)),
8929
+ coloured: defs.flatMap((def) => [this.stainFilm(def), this.stainWet(def)])
8930
+ };
8931
+ }
8838
8932
  dispose() {
8839
8933
  for (const map of [this.body, this.stream, this.film, this.wet]) {
8840
8934
  for (const m of map.values()) m.dispose();
@@ -8910,9 +9004,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8910
9004
  var dot2 = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
8911
9005
  var cross3 = (a, b) => [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
8912
9006
  var length = (a) => Math.hypot(a[0], a[1], a[2]);
8913
- var normalize2 = (a, fallback = [0, 1, 0]) => {
9007
+ var normalize2 = (a, fallback2 = [0, 1, 0]) => {
8914
9008
  const l = length(a);
8915
- return l < 1e-9 ? [fallback[0], fallback[1], fallback[2]] : [a[0] / l, a[1] / l, a[2] / l];
9009
+ return l < 1e-9 ? [fallback2[0], fallback2[1], fallback2[2]] : [a[0] / l, a[1] / l, a[2] / l];
8916
9010
  };
8917
9011
  var ballistic = (origin, velocity, gravity, t) => [
8918
9012
  origin[0] + velocity[0] * t,
@@ -10348,6 +10442,29 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
10348
10442
  body.dispose();
10349
10443
  bodies.delete(containerId);
10350
10444
  },
10445
+ warmUpProbes() {
10446
+ const plainGeometry = new THREE.BufferGeometry();
10447
+ plainGeometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(9), 3));
10448
+ plainGeometry.setAttribute("normal", new THREE.BufferAttribute(new Float32Array(9), 3));
10449
+ const stainGeometry = plainGeometry.clone();
10450
+ stainGeometry.setAttribute("color", new THREE.BufferAttribute(new Float32Array(12), 4));
10451
+ const { plain, instanced, coloured } = materials.probes();
10452
+ const meshes = [
10453
+ ...plain.map((material) => new THREE.Mesh(plainGeometry, material)),
10454
+ ...instanced.map((material) => new THREE.InstancedMesh(dropGeometry, material, 1)),
10455
+ ...coloured.map((material) => new THREE.Mesh(stainGeometry, material))
10456
+ ];
10457
+ const group = new THREE.Group();
10458
+ for (const mesh2 of meshes) mesh2.frustumCulled = false;
10459
+ group.add(...meshes);
10460
+ return {
10461
+ group,
10462
+ dispose() {
10463
+ plainGeometry.dispose();
10464
+ stainGeometry.dispose();
10465
+ }
10466
+ };
10467
+ },
10351
10468
  update(dt) {
10352
10469
  if (disposed) return;
10353
10470
  time += dt;