@adatechnology/game-fluids-three 0.4.5 → 0.4.6

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.
@@ -7688,6 +7688,11 @@ void main(){
7688
7688
  var MAX_CONFORM_STEPS = 48;
7689
7689
  var MIN_CONFORM_STEPS = 16;
7690
7690
  var BLOOD_GROW_RATE = 1.2;
7691
+ var BLOOD_START_GROWTH = 0.05;
7692
+ var DEFAULT_MAX_GROUND_MARKS = 64;
7693
+ var DEFAULT_MAX_ROCKS = 512;
7694
+ var DEFAULT_MAX_BLOOD = 256;
7695
+ var MAX_SPARE_GROUND_GEOMETRIES = 16;
7691
7696
  function createMarkLayer(THREE, options) {
7692
7697
  const group = new THREE.Group();
7693
7698
  const textures = createMarkTextures(THREE, options.createCanvas);
@@ -7711,110 +7716,168 @@ void main(){
7711
7716
  const bloodMaterial = new THREE.MeshStandardMaterial({ color: 6031882, roughness: 0.15, metalness: 0.1, polygonOffset: true, polygonOffsetFactor: -3, polygonOffsetUnits: -3 });
7712
7717
  const bloodGeometry = new THREE.CircleGeometry(0.5, 9).rotateX(-Math.PI / 2);
7713
7718
  owned.push(bloodMaterial, bloodGeometry);
7714
- const rockMaterials = /* @__PURE__ */ new Map();
7715
- const blood = [];
7716
- let meshCount = 0;
7717
- function rockMaterial(color) {
7718
- let material = rockMaterials.get(color);
7719
- if (!material) {
7720
- material = new THREE.MeshStandardMaterial({ color, roughness: 0.9, metalness: 0, flatShading: true });
7721
- rockMaterials.set(color, material);
7722
- owned.push(material);
7719
+ const maxGroundMarks = options.maxGroundMarks ?? DEFAULT_MAX_GROUND_MARKS;
7720
+ const instanced = {};
7721
+ function rockMaterial() {
7722
+ if (!instanced.rockMaterial) {
7723
+ instanced.rockMaterial = new THREE.MeshStandardMaterial({ color: 16777215, roughness: 0.9, metalness: 0, flatShading: true });
7724
+ owned.push(instanced.rockMaterial);
7723
7725
  }
7724
- return material;
7726
+ return instanced.rockMaterial;
7727
+ }
7728
+ function bloodMesh() {
7729
+ return instanced.blood ?? (instanced.blood = attach(createInstanced(bloodGeometry, bloodMaterial, options.maxBlood ?? DEFAULT_MAX_BLOOD)));
7730
+ }
7731
+ function rockMesh() {
7732
+ return instanced.rock ?? (instanced.rock = attach(createInstanced(options.rockGeometry, rockMaterial(), options.maxRocks ?? DEFAULT_MAX_ROCKS), true));
7733
+ }
7734
+ function plateMesh() {
7735
+ return instanced.plate ?? (instanced.plate = attach(createInstanced(options.plateGeometry, rockMaterial(), options.maxRocks ?? DEFAULT_MAX_ROCKS), true));
7736
+ }
7737
+ function attach(mesh2, castShadow = false) {
7738
+ mesh2.castShadow = castShadow;
7739
+ group.add(mesh2);
7740
+ return mesh2;
7741
+ }
7742
+ const groundMarks = [];
7743
+ const spareGeometries = [];
7744
+ const blood = [];
7745
+ const placed = { blood: 0, rock: 0, plate: 0 };
7746
+ const matrix = new THREE.Matrix4();
7747
+ const position = new THREE.Vector3();
7748
+ const quaternion = new THREE.Quaternion();
7749
+ const euler = new THREE.Euler();
7750
+ const scale2 = new THREE.Vector3();
7751
+ const color = new THREE.Color();
7752
+ const up = new THREE.Vector3(0, 1, 0);
7753
+ function createInstanced(geometry, material, capacity) {
7754
+ const mesh2 = new THREE.InstancedMesh(geometry, material, capacity);
7755
+ mesh2.count = 0;
7756
+ mesh2.frustumCulled = false;
7757
+ mesh2.receiveShadow = true;
7758
+ return mesh2;
7759
+ }
7760
+ function takeSpare(vertexCount) {
7761
+ const found = spareGeometries.findIndex((geometry) => geometry.getAttribute("position").count === vertexCount);
7762
+ return found < 0 ? void 0 : spareGeometries.splice(found, 1)[0];
7763
+ }
7764
+ function evictOldestGroundMark() {
7765
+ const oldest = groundMarks.shift();
7766
+ group.remove(oldest.mesh);
7767
+ spareGeometries.push(oldest.mesh.geometry);
7768
+ if (spareGeometries.length > MAX_SPARE_GROUND_GEOMETRIES) spareGeometries.shift().dispose();
7725
7769
  }
7726
7770
  function conform(mark, material) {
7727
7771
  const radius = mark.radius;
7728
7772
  const steps = Math.max(MIN_CONFORM_STEPS, Math.min(MAX_CONFORM_STEPS, Math.round(radius * 8)));
7729
- const geometry = new THREE.PlaneGeometry(2, 2, steps, steps).rotateX(-Math.PI / 2);
7730
- const position = geometry.attributes.position;
7773
+ if (groundMarks.length >= maxGroundMarks) evictOldestGroundMark();
7774
+ const geometry = takeSpare((steps + 1) * (steps + 1)) ?? new THREE.PlaneGeometry(2, 2, steps, steps).rotateX(-Math.PI / 2);
7775
+ const grid = geometry.attributes.position;
7776
+ const cell = 2 / steps;
7731
7777
  const cos2 = Math.cos(mark.rotation);
7732
7778
  const sin2 = Math.sin(mark.rotation);
7733
- for (let index = 0; index < position.count; index++) {
7734
- const localX = position.getX(index) * radius;
7735
- const localZ = position.getZ(index) * radius;
7779
+ for (let index = 0; index < grid.count; index++) {
7780
+ const localX = Math.fround(index % (steps + 1) * cell - 1) * radius;
7781
+ const localZ = Math.fround(Math.floor(index / (steps + 1)) * cell - 1) * radius;
7736
7782
  const worldX = mark.x + localX * cos2 - localZ * sin2;
7737
7783
  const worldZ = mark.z + localX * sin2 + localZ * cos2;
7738
- position.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
7784
+ grid.setXYZ(index, worldX, (options.ground?.heightAt(worldX, worldZ) ?? 0) + mark.lift, worldZ);
7739
7785
  }
7786
+ grid.needsUpdate = true;
7740
7787
  geometry.computeVertexNormals();
7788
+ geometry.attributes.normal.needsUpdate = true;
7789
+ geometry.boundingSphere = null;
7790
+ geometry.boundingBox = null;
7741
7791
  const mesh2 = new THREE.Mesh(geometry, material);
7742
7792
  mesh2.receiveShadow = true;
7743
- return mesh2;
7793
+ group.add(mesh2);
7794
+ groundMarks.push({ mesh: mesh2, steps });
7744
7795
  }
7745
7796
  function addRock(mark) {
7746
- const mesh2 = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
7747
- mesh2.position.set(mark.x, mark.y, mark.z);
7748
- mesh2.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
7749
- mesh2.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
7750
- mesh2.castShadow = true;
7751
- mesh2.receiveShadow = true;
7752
- return mesh2;
7797
+ const mesh2 = mark.plate ? plateMesh() : rockMesh();
7798
+ const slot = mark.plate ? "plate" : "rock";
7799
+ const index = placed[slot]++ % mesh2.instanceMatrix.count;
7800
+ quaternion.setFromEuler(euler.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]));
7801
+ mesh2.setMatrixAt(index, matrix.compose(position.set(mark.x, mark.y, mark.z), quaternion, scale2.set(mark.scale[0], mark.scale[1], mark.scale[2])));
7802
+ mesh2.setColorAt(index, color.set(mark.color));
7803
+ mesh2.count = Math.min(placed[slot], mesh2.instanceMatrix.count);
7804
+ mesh2.instanceMatrix.needsUpdate = true;
7805
+ mesh2.instanceColor.needsUpdate = true;
7806
+ }
7807
+ function writeBlood(index, drawn, growth) {
7808
+ quaternion.setFromAxisAngle(up, drawn.rotation);
7809
+ const mesh2 = bloodMesh();
7810
+ mesh2.setMatrixAt(index, matrix.compose(position.set(drawn.position[0], drawn.position[1], drawn.position[2]), quaternion, scale2.set(drawn.scaleX * growth, 1, drawn.scaleZ * growth)));
7811
+ mesh2.instanceMatrix.needsUpdate = true;
7753
7812
  }
7754
7813
  function addBlood(mark) {
7755
- const mesh2 = new THREE.Mesh(bloodGeometry, bloodMaterial);
7756
- mesh2.position.set(mark.x, mark.y, mark.z);
7757
- mesh2.scale.set(mark.scaleX, 1, mark.scaleZ);
7758
- mesh2.rotation.y = mark.rotation;
7759
- mesh2.receiveShadow = true;
7760
- mesh2.scale.multiplyScalar(0.05);
7761
- blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
7762
- return mesh2;
7814
+ const index = placed.blood++ % bloodMesh().instanceMatrix.count;
7815
+ const drawn = { position: [mark.x, mark.y, mark.z], rotation: mark.rotation, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 };
7816
+ blood[index] = drawn;
7817
+ writeBlood(index, drawn, BLOOD_START_GROWTH);
7818
+ bloodMesh().count = Math.min(placed.blood, bloodMesh().instanceMatrix.count);
7763
7819
  }
7764
7820
  function build(mark) {
7765
- if (mark.kind === "scorch") return conform(mark, scorchMaterials[mark.variant]);
7766
- if (mark.kind === "dirt") return conform(mark, dirtMaterial);
7767
- if (mark.kind === "rock") return addRock(mark);
7768
- return addBlood(mark);
7769
- }
7770
- function clearMeshes() {
7771
- for (const child of [...group.children]) {
7772
- group.remove(child);
7773
- const geometry = child.geometry;
7774
- if (geometry !== options.rockGeometry && geometry !== options.plateGeometry && geometry !== bloodGeometry) geometry.dispose();
7775
- }
7821
+ if (mark.kind === "scorch") conform(mark, scorchMaterials[mark.variant]);
7822
+ else if (mark.kind === "dirt") conform(mark, dirtMaterial);
7823
+ else if (mark.kind === "rock") addRock(mark);
7824
+ else addBlood(mark);
7825
+ }
7826
+ function clearMarks() {
7827
+ for (const drawn of groundMarks) group.remove(drawn.mesh);
7828
+ for (const drawn of groundMarks) drawn.mesh.geometry.dispose();
7829
+ groundMarks.length = 0;
7830
+ for (const geometry of spareGeometries) geometry.dispose();
7831
+ spareGeometries.length = 0;
7776
7832
  blood.length = 0;
7777
- meshCount = 0;
7833
+ placed.blood = placed.rock = placed.plate = 0;
7834
+ for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) if (mesh2) mesh2.count = 0;
7778
7835
  }
7779
7836
  return {
7780
7837
  group,
7781
7838
  handle(event) {
7782
- if (event.type === "clear") {
7783
- clearMeshes();
7784
- return;
7785
- }
7786
- group.add(build(event.mark));
7787
- meshCount++;
7839
+ if (event.type === "clear") clearMarks();
7840
+ else build(event.mark);
7841
+ },
7842
+ prepare() {
7843
+ bloodMesh();
7844
+ rockMesh();
7845
+ plateMesh();
7788
7846
  },
7789
7847
  update(dt) {
7790
- for (const drawn of blood) {
7791
- if (drawn.age > 1) continue;
7848
+ for (let index = 0; index < blood.length; index++) {
7849
+ const drawn = blood[index];
7850
+ if (!drawn || drawn.age > 1) continue;
7792
7851
  drawn.age += dt * BLOOD_GROW_RATE;
7793
- const ease3 = 1 - Math.pow(1 - Math.min(1, drawn.age), 2.5);
7794
- drawn.mesh.scale.set(drawn.scaleX * Math.max(0.05, ease3), 1, drawn.scaleZ * Math.max(0.05, ease3));
7852
+ writeBlood(index, drawn, Math.max(BLOOD_START_GROWTH, 1 - Math.pow(1 - Math.min(1, drawn.age), 2.5)));
7795
7853
  }
7796
7854
  },
7797
- count: () => meshCount,
7855
+ count: () => groundMarks.length + [instanced.blood, instanced.rock, instanced.plate].filter((mesh2) => mesh2 && mesh2.count > 0).length,
7798
7856
  dispose() {
7799
- clearMeshes();
7857
+ clearMarks();
7858
+ for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) mesh2?.dispose();
7800
7859
  for (const item of owned) item.dispose();
7801
7860
  }
7802
7861
  };
7803
7862
  }
7804
7863
  var SPHERE_DEFAULT_WIDTH = 8;
7805
7864
  var SPHERE_DEFAULT_HEIGHT = 6;
7865
+ var MAX_PARKED_MESHES_PER_KIND = 256;
7806
7866
  function geometryOf(THREE, particle) {
7807
7867
  switch (particle.shape) {
7808
7868
  case "cone":
7809
- return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, false);
7869
+ return new THREE.ConeGeometry(1, 1, particle.segments || void 0, particle.segments2 || 1, false);
7810
7870
  case "cone_open":
7811
- return new THREE.ConeGeometry(particle.radius, particle.height, particle.segments || void 0, particle.segments2 || 1, true);
7871
+ return new THREE.ConeGeometry(1, 1, particle.segments || void 0, particle.segments2 || 1, true);
7812
7872
  case "sphere":
7813
- return new THREE.SphereGeometry(particle.radius, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
7873
+ return new THREE.SphereGeometry(1, particle.segments || SPHERE_DEFAULT_WIDTH, particle.segments2 || SPHERE_DEFAULT_HEIGHT);
7814
7874
  case "icosahedron":
7815
- return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
7875
+ return new THREE.IcosahedronGeometry(1, particle.segments);
7816
7876
  }
7817
7877
  }
7878
+ function isCone(particle) {
7879
+ return particle.shape === "cone" || particle.shape === "cone_open";
7880
+ }
7818
7881
  function materialOf(THREE, particle) {
7819
7882
  if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
7820
7883
  return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
@@ -7825,51 +7888,82 @@ void main(){
7825
7888
  (kind) => new THREE.Mesh(geometry, materialOf(THREE, { ...kind, color: 8421504, opacity: 0.5 }))
7826
7889
  );
7827
7890
  }
7891
+ function geometryKeyOf(particle) {
7892
+ return `${particle.shape}:${particle.segments}:${particle.segments2}`;
7893
+ }
7828
7894
  function createMeshLayer(THREE, pool) {
7829
7895
  const group = new THREE.Group();
7830
7896
  group.name = "workshop-meshes";
7831
7897
  const drawn = /* @__PURE__ */ new Map();
7898
+ const geometries = /* @__PURE__ */ new Map();
7899
+ const parked = /* @__PURE__ */ new Map();
7832
7900
  const frame = new THREE.Quaternion();
7833
7901
  const euler = new THREE.Euler();
7834
7902
  const turn = new THREE.Quaternion();
7835
7903
  const offset = new THREE.Vector3();
7836
- function release(mesh2) {
7837
- mesh2.removeFromParent();
7838
- mesh2.geometry.dispose();
7839
- mesh2.material.dispose();
7904
+ let stamp = 0;
7905
+ function geometryFor(particle) {
7906
+ const key = geometryKeyOf(particle);
7907
+ let geometry = geometries.get(key);
7908
+ if (!geometry) {
7909
+ geometry = geometryOf(THREE, particle);
7910
+ geometries.set(key, geometry);
7911
+ }
7912
+ return geometry;
7913
+ }
7914
+ function acquire(particle) {
7915
+ const parkingKey = `${geometryKeyOf(particle)}|${particle.lit}|${particle.additive}`;
7916
+ const mesh2 = parked.get(parkingKey)?.pop() ?? new THREE.Mesh(geometryFor(particle), materialOf(THREE, particle));
7917
+ mesh2.material.color.set(particle.color);
7918
+ mesh2.frustumCulled = false;
7919
+ mesh2.renderOrder = particle.additive ? 5 : 3;
7920
+ group.add(mesh2);
7921
+ return { mesh: mesh2, parkingKey, stamp };
7922
+ }
7923
+ function park(entry) {
7924
+ entry.mesh.removeFromParent();
7925
+ let free = parked.get(entry.parkingKey);
7926
+ if (!free) {
7927
+ free = [];
7928
+ parked.set(entry.parkingKey, free);
7929
+ }
7930
+ if (free.length < MAX_PARKED_MESHES_PER_KIND) free.push(entry.mesh);
7931
+ else entry.mesh.material.dispose();
7840
7932
  }
7841
7933
  return {
7842
7934
  group,
7843
7935
  sync() {
7844
- const live = /* @__PURE__ */ new Set();
7936
+ stamp++;
7845
7937
  for (const particle of pool.particles) {
7846
- live.add(particle.id);
7847
- let mesh2 = drawn.get(particle.id);
7848
- if (!mesh2) {
7849
- mesh2 = new THREE.Mesh(geometryOf(THREE, particle), materialOf(THREE, particle));
7850
- mesh2.frustumCulled = false;
7851
- mesh2.renderOrder = particle.additive ? 5 : 3;
7852
- drawn.set(particle.id, mesh2);
7853
- group.add(mesh2);
7938
+ let entry = drawn.get(particle.id);
7939
+ if (!entry) {
7940
+ entry = acquire(particle);
7941
+ drawn.set(particle.id, entry);
7854
7942
  }
7943
+ entry.stamp = stamp;
7944
+ const mesh2 = entry.mesh;
7855
7945
  frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
7856
7946
  offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
7857
7947
  mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
7858
7948
  turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
7859
7949
  mesh2.quaternion.copy(frame).multiply(turn);
7860
- mesh2.scale.setScalar(particle.scale);
7950
+ mesh2.scale.set(particle.radius * particle.scale, (isCone(particle) ? particle.height : particle.radius) * particle.scale, particle.radius * particle.scale);
7861
7951
  mesh2.material.opacity = particle.opacity;
7862
7952
  }
7863
- for (const [id, mesh2] of drawn) {
7864
- if (live.has(id)) continue;
7865
- release(mesh2);
7953
+ for (const [id, entry] of drawn) {
7954
+ if (entry.stamp === stamp) continue;
7955
+ park(entry);
7866
7956
  drawn.delete(id);
7867
7957
  }
7868
7958
  return pool.count;
7869
7959
  },
7870
7960
  dispose() {
7871
- for (const mesh2 of drawn.values()) release(mesh2);
7961
+ for (const entry of drawn.values()) park(entry);
7872
7962
  drawn.clear();
7963
+ for (const free of parked.values()) for (const mesh2 of free) mesh2.material.dispose();
7964
+ parked.clear();
7965
+ for (const geometry of geometries.values()) geometry.dispose();
7966
+ geometries.clear();
7873
7967
  group.removeFromParent();
7874
7968
  }
7875
7969
  };
@@ -8437,7 +8531,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8437
8531
  const shocks = createShockLayer(THREE, pools.shocks);
8438
8532
  const sprites = createSpriteLayer(THREE, world.sprites(), options.pointScale ?? 700);
8439
8533
  const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI);
8440
- const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry });
8534
+ const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
8441
8535
  root.add(puffs.mesh, sparks.mesh, glows.mesh, chunks.rocks, chunks.plates, shocks.group, lights.group, sprites.additive, sprites.normal, marks.group);
8442
8536
  const stopMarks = world.onMarks((event) => marks.handle(event));
8443
8537
  const overlay = options.overlay ? createScreenOverlay(options.overlay) : void 0;
@@ -8463,6 +8557,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8463
8557
  const position = options.camera.position;
8464
8558
  world.setCamera([position.x, position.y, position.z]);
8465
8559
  }
8560
+ function attachWorkshopLayers() {
8561
+ const engine = world.peekWorkshop();
8562
+ if (!engine || engine === workshopEngine) return;
8563
+ workshopLayers?.dispose();
8564
+ if (workshopLayers) root.remove(workshopLayers.group);
8565
+ workshopEngine = engine;
8566
+ workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
8567
+ root.add(workshopLayers.group);
8568
+ applyLayers();
8569
+ }
8466
8570
  function syncAll() {
8467
8571
  lastDrawn.puffs = puffs.sync();
8468
8572
  lastDrawn.sparks = sparks.sync();
@@ -8471,15 +8575,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8471
8575
  lastDrawn.shocks = shocks.sync();
8472
8576
  lastDrawn.sprites = sprites.sync();
8473
8577
  lights.sync();
8474
- const engine = world.peekWorkshop();
8475
- if (engine && engine !== workshopEngine) {
8476
- workshopLayers?.dispose();
8477
- if (workshopLayers) root.remove(workshopLayers.group);
8478
- workshopEngine = engine;
8479
- workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
8480
- root.add(workshopLayers.group);
8481
- applyLayers();
8482
- }
8578
+ attachWorkshopLayers();
8483
8579
  const workshopCalls = workshopLayers?.sync() ?? 0;
8484
8580
  const glow = pools.lights.glow;
8485
8581
  uniforms.uGlowPos.value.set(glow.x, glow.y, glow.z);
@@ -8532,6 +8628,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8532
8628
  },
8533
8629
  warmUpProbes() {
8534
8630
  releaseAnchors();
8631
+ marks.prepare();
8632
+ world.workshop();
8633
+ attachWorkshopLayers();
8535
8634
  const meshes = createMeshLayerProbes(THREE);
8536
8635
  for (const mesh2 of meshes) mesh2.frustumCulled = false;
8537
8636
  const group = new THREE.Group();