@adatechnology/game-fluids-three 0.4.4 → 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.
@@ -6358,6 +6358,24 @@ var GameFluidsThree = (() => {
6358
6358
  for (const decal2 of this.decals) decal2.on = false;
6359
6359
  this.count = 0;
6360
6360
  }
6361
+ /**
6362
+ * Takes away the marks inside a box: world `center`, `half` extents along the axes of the world `rotation` (a quaternion, x y z w; none means the
6363
+ * world axes). The surface they were on is gone (a brick came out of a wall with its bullet holes). Returns how many went.
6364
+ */
6365
+ clearInside(center, half, rotation = [0, 0, 0, 1]) {
6366
+ const qx = -rotation[0], qy = -rotation[1], qz = -rotation[2], qw = rotation[3];
6367
+ let gone = 0;
6368
+ for (const decal2 of this.decals) {
6369
+ if (!decal2.on) continue;
6370
+ const vx2 = decal2.x - center[0], vy2 = decal2.y - center[1], vz2 = decal2.z - center[2];
6371
+ const tx = 2 * (qy * vz2 - qz * vy2), ty = 2 * (qz * vx2 - qx * vz2), tz = 2 * (qx * vy2 - qy * vx2);
6372
+ const lx = vx2 + qw * tx + (qy * tz - qz * ty), ly = vy2 + qw * ty + (qz * tx - qx * tz), lz = vz2 + qw * tz + (qx * ty - qy * tx);
6373
+ if (Math.abs(lx) > half[0] || Math.abs(ly) > half[1] || Math.abs(lz) > half[2]) continue;
6374
+ decal2.on = false;
6375
+ gone++;
6376
+ }
6377
+ return gone;
6378
+ }
6361
6379
  update(dt) {
6362
6380
  this.count = 0;
6363
6381
  for (const decal2 of this.decals) {
@@ -7670,6 +7688,11 @@ void main(){
7670
7688
  var MAX_CONFORM_STEPS = 48;
7671
7689
  var MIN_CONFORM_STEPS = 16;
7672
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;
7673
7696
  function createMarkLayer(THREE, options) {
7674
7697
  const group = new THREE.Group();
7675
7698
  const textures = createMarkTextures(THREE, options.createCanvas);
@@ -7693,110 +7716,168 @@ void main(){
7693
7716
  const bloodMaterial = new THREE.MeshStandardMaterial({ color: 6031882, roughness: 0.15, metalness: 0.1, polygonOffset: true, polygonOffsetFactor: -3, polygonOffsetUnits: -3 });
7694
7717
  const bloodGeometry = new THREE.CircleGeometry(0.5, 9).rotateX(-Math.PI / 2);
7695
7718
  owned.push(bloodMaterial, bloodGeometry);
7696
- const rockMaterials = /* @__PURE__ */ new Map();
7697
- const blood = [];
7698
- let meshCount = 0;
7699
- function rockMaterial(color) {
7700
- let material = rockMaterials.get(color);
7701
- if (!material) {
7702
- material = new THREE.MeshStandardMaterial({ color, roughness: 0.9, metalness: 0, flatShading: true });
7703
- rockMaterials.set(color, material);
7704
- 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);
7705
7725
  }
7706
- 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();
7707
7769
  }
7708
7770
  function conform(mark, material) {
7709
7771
  const radius = mark.radius;
7710
7772
  const steps = Math.max(MIN_CONFORM_STEPS, Math.min(MAX_CONFORM_STEPS, Math.round(radius * 8)));
7711
- const geometry = new THREE.PlaneGeometry(2, 2, steps, steps).rotateX(-Math.PI / 2);
7712
- 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;
7713
7777
  const cos2 = Math.cos(mark.rotation);
7714
7778
  const sin2 = Math.sin(mark.rotation);
7715
- for (let index = 0; index < position.count; index++) {
7716
- const localX = position.getX(index) * radius;
7717
- 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;
7718
7782
  const worldX = mark.x + localX * cos2 - localZ * sin2;
7719
7783
  const worldZ = mark.z + localX * sin2 + localZ * cos2;
7720
- 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);
7721
7785
  }
7786
+ grid.needsUpdate = true;
7722
7787
  geometry.computeVertexNormals();
7788
+ geometry.attributes.normal.needsUpdate = true;
7789
+ geometry.boundingSphere = null;
7790
+ geometry.boundingBox = null;
7723
7791
  const mesh2 = new THREE.Mesh(geometry, material);
7724
7792
  mesh2.receiveShadow = true;
7725
- return mesh2;
7793
+ group.add(mesh2);
7794
+ groundMarks.push({ mesh: mesh2, steps });
7726
7795
  }
7727
7796
  function addRock(mark) {
7728
- const mesh2 = new THREE.Mesh(mark.plate ? options.plateGeometry : options.rockGeometry, rockMaterial(mark.color));
7729
- mesh2.position.set(mark.x, mark.y, mark.z);
7730
- mesh2.scale.set(mark.scale[0], mark.scale[1], mark.scale[2]);
7731
- mesh2.rotation.set(mark.rotation[0], mark.rotation[1], mark.rotation[2]);
7732
- mesh2.castShadow = true;
7733
- mesh2.receiveShadow = true;
7734
- 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;
7735
7812
  }
7736
7813
  function addBlood(mark) {
7737
- const mesh2 = new THREE.Mesh(bloodGeometry, bloodMaterial);
7738
- mesh2.position.set(mark.x, mark.y, mark.z);
7739
- mesh2.scale.set(mark.scaleX, 1, mark.scaleZ);
7740
- mesh2.rotation.y = mark.rotation;
7741
- mesh2.receiveShadow = true;
7742
- mesh2.scale.multiplyScalar(0.05);
7743
- blood.push({ mesh: mesh2, scaleX: mark.scaleX, scaleZ: mark.scaleZ, age: 0 });
7744
- 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);
7745
7819
  }
7746
7820
  function build(mark) {
7747
- if (mark.kind === "scorch") return conform(mark, scorchMaterials[mark.variant]);
7748
- if (mark.kind === "dirt") return conform(mark, dirtMaterial);
7749
- if (mark.kind === "rock") return addRock(mark);
7750
- return addBlood(mark);
7751
- }
7752
- function clearMeshes() {
7753
- for (const child of [...group.children]) {
7754
- group.remove(child);
7755
- const geometry = child.geometry;
7756
- if (geometry !== options.rockGeometry && geometry !== options.plateGeometry && geometry !== bloodGeometry) geometry.dispose();
7757
- }
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;
7758
7832
  blood.length = 0;
7759
- 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;
7760
7835
  }
7761
7836
  return {
7762
7837
  group,
7763
7838
  handle(event) {
7764
- if (event.type === "clear") {
7765
- clearMeshes();
7766
- return;
7767
- }
7768
- group.add(build(event.mark));
7769
- meshCount++;
7839
+ if (event.type === "clear") clearMarks();
7840
+ else build(event.mark);
7841
+ },
7842
+ prepare() {
7843
+ bloodMesh();
7844
+ rockMesh();
7845
+ plateMesh();
7770
7846
  },
7771
7847
  update(dt) {
7772
- for (const drawn of blood) {
7773
- 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;
7774
7851
  drawn.age += dt * BLOOD_GROW_RATE;
7775
- const ease3 = 1 - Math.pow(1 - Math.min(1, drawn.age), 2.5);
7776
- 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)));
7777
7853
  }
7778
7854
  },
7779
- count: () => meshCount,
7855
+ count: () => groundMarks.length + [instanced.blood, instanced.rock, instanced.plate].filter((mesh2) => mesh2 && mesh2.count > 0).length,
7780
7856
  dispose() {
7781
- clearMeshes();
7857
+ clearMarks();
7858
+ for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) mesh2?.dispose();
7782
7859
  for (const item of owned) item.dispose();
7783
7860
  }
7784
7861
  };
7785
7862
  }
7786
7863
  var SPHERE_DEFAULT_WIDTH = 8;
7787
7864
  var SPHERE_DEFAULT_HEIGHT = 6;
7865
+ var MAX_PARKED_MESHES_PER_KIND = 256;
7788
7866
  function geometryOf(THREE, particle) {
7789
7867
  switch (particle.shape) {
7790
7868
  case "cone":
7791
- 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);
7792
7870
  case "cone_open":
7793
- 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);
7794
7872
  case "sphere":
7795
- 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);
7796
7874
  case "icosahedron":
7797
- return new THREE.IcosahedronGeometry(particle.radius, particle.segments);
7875
+ return new THREE.IcosahedronGeometry(1, particle.segments);
7798
7876
  }
7799
7877
  }
7878
+ function isCone(particle) {
7879
+ return particle.shape === "cone" || particle.shape === "cone_open";
7880
+ }
7800
7881
  function materialOf(THREE, particle) {
7801
7882
  if (particle.lit) return new THREE.MeshStandardMaterial({ color: particle.color, roughness: 1, metalness: 0, transparent: true, opacity: particle.opacity, depthWrite: false });
7802
7883
  return new THREE.MeshBasicMaterial({ color: particle.color, transparent: true, opacity: particle.opacity, depthWrite: false, ...particle.additive ? { blending: THREE.AdditiveBlending } : {} });
@@ -7807,51 +7888,82 @@ void main(){
7807
7888
  (kind) => new THREE.Mesh(geometry, materialOf(THREE, { ...kind, color: 8421504, opacity: 0.5 }))
7808
7889
  );
7809
7890
  }
7891
+ function geometryKeyOf(particle) {
7892
+ return `${particle.shape}:${particle.segments}:${particle.segments2}`;
7893
+ }
7810
7894
  function createMeshLayer(THREE, pool) {
7811
7895
  const group = new THREE.Group();
7812
7896
  group.name = "workshop-meshes";
7813
7897
  const drawn = /* @__PURE__ */ new Map();
7898
+ const geometries = /* @__PURE__ */ new Map();
7899
+ const parked = /* @__PURE__ */ new Map();
7814
7900
  const frame = new THREE.Quaternion();
7815
7901
  const euler = new THREE.Euler();
7816
7902
  const turn = new THREE.Quaternion();
7817
7903
  const offset = new THREE.Vector3();
7818
- function release(mesh2) {
7819
- mesh2.removeFromParent();
7820
- mesh2.geometry.dispose();
7821
- 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();
7822
7932
  }
7823
7933
  return {
7824
7934
  group,
7825
7935
  sync() {
7826
- const live = /* @__PURE__ */ new Set();
7936
+ stamp++;
7827
7937
  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);
7938
+ let entry = drawn.get(particle.id);
7939
+ if (!entry) {
7940
+ entry = acquire(particle);
7941
+ drawn.set(particle.id, entry);
7836
7942
  }
7943
+ entry.stamp = stamp;
7944
+ const mesh2 = entry.mesh;
7837
7945
  frame.set(particle.frame[0], particle.frame[1], particle.frame[2], particle.frame[3]);
7838
7946
  offset.set(particle.at[0], particle.at[1], particle.at[2]).multiplyScalar(particle.scale).applyQuaternion(frame);
7839
7947
  mesh2.position.set(particle.x + offset.x, particle.y + offset.y, particle.z + offset.z);
7840
7948
  turn.setFromEuler(euler.set(particle.rx, particle.ry, particle.rz));
7841
7949
  mesh2.quaternion.copy(frame).multiply(turn);
7842
- 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);
7843
7951
  mesh2.material.opacity = particle.opacity;
7844
7952
  }
7845
- for (const [id, mesh2] of drawn) {
7846
- if (live.has(id)) continue;
7847
- release(mesh2);
7953
+ for (const [id, entry] of drawn) {
7954
+ if (entry.stamp === stamp) continue;
7955
+ park(entry);
7848
7956
  drawn.delete(id);
7849
7957
  }
7850
7958
  return pool.count;
7851
7959
  },
7852
7960
  dispose() {
7853
- for (const mesh2 of drawn.values()) release(mesh2);
7961
+ for (const entry of drawn.values()) park(entry);
7854
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();
7855
7967
  group.removeFromParent();
7856
7968
  }
7857
7969
  };
@@ -8419,7 +8531,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8419
8531
  const shocks = createShockLayer(THREE, pools.shocks);
8420
8532
  const sprites = createSpriteLayer(THREE, world.sprites(), options.pointScale ?? 700);
8421
8533
  const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI);
8422
- 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 });
8423
8535
  root.add(puffs.mesh, sparks.mesh, glows.mesh, chunks.rocks, chunks.plates, shocks.group, lights.group, sprites.additive, sprites.normal, marks.group);
8424
8536
  const stopMarks = world.onMarks((event) => marks.handle(event));
8425
8537
  const overlay = options.overlay ? createScreenOverlay(options.overlay) : void 0;
@@ -8445,6 +8557,16 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8445
8557
  const position = options.camera.position;
8446
8558
  world.setCamera([position.x, position.y, position.z]);
8447
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
+ }
8448
8570
  function syncAll() {
8449
8571
  lastDrawn.puffs = puffs.sync();
8450
8572
  lastDrawn.sparks = sparks.sync();
@@ -8453,15 +8575,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8453
8575
  lastDrawn.shocks = shocks.sync();
8454
8576
  lastDrawn.sprites = sprites.sync();
8455
8577
  lights.sync();
8456
- const engine = world.peekWorkshop();
8457
- if (engine && engine !== workshopEngine) {
8458
- workshopLayers?.dispose();
8459
- if (workshopLayers) root.remove(workshopLayers.group);
8460
- workshopEngine = engine;
8461
- workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
8462
- root.add(workshopLayers.group);
8463
- applyLayers();
8464
- }
8578
+ attachWorkshopLayers();
8465
8579
  const workshopCalls = workshopLayers?.sync() ?? 0;
8466
8580
  const glow = pools.lights.glow;
8467
8581
  uniforms.uGlowPos.value.set(glow.x, glow.y, glow.z);
@@ -8514,6 +8628,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8514
8628
  },
8515
8629
  warmUpProbes() {
8516
8630
  releaseAnchors();
8631
+ marks.prepare();
8632
+ world.workshop();
8633
+ attachWorkshopLayers();
8517
8634
  const meshes = createMeshLayerProbes(THREE);
8518
8635
  for (const mesh2 of meshes) mesh2.frustumCulled = false;
8519
8636
  const group = new THREE.Group();