@polarfront-lab/ionian 1.7.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/ionian.js CHANGED
@@ -92,10 +92,12 @@ class AssetService {
92
92
  __publicField(this, "gltfLoader", new GLTFLoader());
93
93
  __publicField(this, "textureLoader", new THREE.TextureLoader());
94
94
  __publicField(this, "dracoLoader", new DRACOLoader());
95
- __publicField(this, "solidColorTexture", new THREE.DataTexture(new Uint8Array([127, 127, 127, 255]), 1, 1, THREE.RGBAFormat));
95
+ __publicField(this, "solidColorTextures", /* @__PURE__ */ new Map());
96
+ __publicField(this, "fallbackTexture", new THREE.DataTexture(new Uint8Array([127, 127, 127, 255]), 1, 1, THREE.RGBAFormat));
96
97
  this.eventEmitter = eventEmitter;
97
98
  this.dracoLoader.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.5.7/");
98
99
  this.gltfLoader.setDRACOLoader(this.dracoLoader);
100
+ this.fallbackTexture.name = "default-fallback-texture";
99
101
  this.updateServiceState("ready");
100
102
  }
101
103
  /**
@@ -116,19 +118,32 @@ class AssetService {
116
118
  }
117
119
  this.eventEmitter.emit("assetRegistered", { id });
118
120
  }
119
- setSolidColor(color) {
120
- this.changeColor(color);
121
- }
122
- getSolidColorTexture() {
123
- return this.solidColorTexture;
124
- }
125
121
  getMesh(id) {
126
122
  return this.meshes.get(id) ?? null;
127
123
  }
128
- getMatcap(id) {
124
+ getMatcapTexture(id) {
129
125
  const texture = this.textures.get(id);
130
126
  if (!texture) this.eventEmitter.emit("invalidRequest", { message: `texture with id "${id}" not found. using solid color texture instead...` });
131
- return texture ?? this.solidColorTexture;
127
+ return texture ?? this.fallbackTexture;
128
+ }
129
+ getSolidColorTexture(colorValue) {
130
+ const colorKey = new THREE.Color(colorValue).getHexString();
131
+ let texture = this.solidColorTextures.get(colorKey);
132
+ if (texture) {
133
+ return texture;
134
+ }
135
+ try {
136
+ const texture2 = this.createSolidColorDataTexture(new THREE.Color(colorValue));
137
+ this.solidColorTextures.set(colorKey, texture2);
138
+ return texture2;
139
+ } catch (error) {
140
+ console.error(`Invalid color value provided to getSolidColorTexture: ${colorValue}`, error);
141
+ this.eventEmitter.emit("invalidRequest", { message: `Invalid color value: ${colorValue}. Using fallback texture.` });
142
+ return this.fallbackTexture;
143
+ }
144
+ }
145
+ getFallbackTexture() {
146
+ return this.fallbackTexture;
132
147
  }
133
148
  getMeshIDs() {
134
149
  return Array.from(this.meshes.keys());
@@ -142,8 +157,29 @@ class AssetService {
142
157
  getTextures() {
143
158
  return Array.from(this.textures.values());
144
159
  }
145
- hasMatcap(id) {
146
- return this.textures.has(id);
160
+ createSolidColorDataTexture(color, size = 16) {
161
+ const col = new THREE.Color(color);
162
+ const width = size;
163
+ const height = size;
164
+ const data = new Uint8Array(width * height * 4);
165
+ const r = Math.floor(col.r * 255);
166
+ const g = Math.floor(col.g * 255);
167
+ const b = Math.floor(col.b * 255);
168
+ for (let i = 0; i < width * height; i++) {
169
+ const index = i * 4;
170
+ data[index] = r;
171
+ data[index + 1] = g;
172
+ data[index + 2] = b;
173
+ data[index + 3] = 255;
174
+ }
175
+ const texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
176
+ texture.type = THREE.UnsignedByteType;
177
+ texture.wrapS = THREE.RepeatWrapping;
178
+ texture.wrapT = THREE.RepeatWrapping;
179
+ texture.minFilter = THREE.NearestFilter;
180
+ texture.magFilter = THREE.NearestFilter;
181
+ texture.needsUpdate = true;
182
+ return texture;
147
183
  }
148
184
  /**
149
185
  * Loads a mesh asynchronously.
@@ -191,11 +227,9 @@ class AssetService {
191
227
  this.meshes.clear();
192
228
  this.textures.forEach((texture) => texture.dispose());
193
229
  this.textures.clear();
194
- }
195
- changeColor(color) {
196
- const actual = new THREE.Color(color);
197
- this.solidColorTexture = new THREE.DataTexture(new Uint8Array([actual.r, actual.g, actual.b, 255]), 1, 1, THREE.RGBAFormat);
198
- this.solidColorTexture.needsUpdate = true;
230
+ this.solidColorTextures.forEach((texture) => texture.dispose());
231
+ this.solidColorTextures.clear();
232
+ this.fallbackTexture.dispose();
199
233
  }
200
234
  updateServiceState(serviceState) {
201
235
  this.serviceState = serviceState;
@@ -206,6 +240,11 @@ const _face = new Triangle();
206
240
  const _color = new Vector3();
207
241
  const _uva = new Vector2(), _uvb = new Vector2(), _uvc = new Vector2();
208
242
  class MeshSurfaceSampler {
243
+ /**
244
+ * Constructs a mesh surface sampler.
245
+ *
246
+ * @param {Mesh} mesh - Surface mesh from which to sample.
247
+ */
209
248
  constructor(mesh) {
210
249
  this.geometry = mesh.geometry;
211
250
  this.randomFunction = Math.random;
@@ -217,10 +256,27 @@ class MeshSurfaceSampler {
217
256
  this.weightAttribute = null;
218
257
  this.distribution = null;
219
258
  }
259
+ /**
260
+ * Specifies a vertex attribute to be used as a weight when sampling from the surface.
261
+ * Faces with higher weights are more likely to be sampled, and those with weights of
262
+ * zero will not be sampled at all. For vector attributes, only .x is used in sampling.
263
+ *
264
+ * If no weight attribute is selected, sampling is randomly distributed by area.
265
+ *
266
+ * @param {string} name - The attribute name.
267
+ * @return {MeshSurfaceSampler} A reference to this sampler.
268
+ */
220
269
  setWeightAttribute(name) {
221
270
  this.weightAttribute = name ? this.geometry.getAttribute(name) : null;
222
271
  return this;
223
272
  }
273
+ /**
274
+ * Processes the input geometry and prepares to return samples. Any configuration of the
275
+ * geometry or sampler must occur before this method is called. Time complexity is O(n)
276
+ * for a surface with n faces.
277
+ *
278
+ * @return {MeshSurfaceSampler} A reference to this sampler.
279
+ */
224
280
  build() {
225
281
  const indexAttribute = this.indexAttribute;
226
282
  const positionAttribute = this.positionAttribute;
@@ -255,19 +311,37 @@ class MeshSurfaceSampler {
255
311
  this.distribution = distribution;
256
312
  return this;
257
313
  }
314
+ /**
315
+ * Allows to set a custom random number generator. Default is `Math.random()`.
316
+ *
317
+ * @param {Function} randomFunction - A random number generator.
318
+ * @return {MeshSurfaceSampler} A reference to this sampler.
319
+ */
258
320
  setRandomGenerator(randomFunction) {
259
321
  this.randomFunction = randomFunction;
260
322
  return this;
261
323
  }
324
+ /**
325
+ * Selects a random point on the surface of the input geometry, returning the
326
+ * position and optionally the normal vector, color and UV Coordinate at that point.
327
+ * Time complexity is O(log n) for a surface with n faces.
328
+ *
329
+ * @param {Vector3} targetPosition - The target object holding the sampled position.
330
+ * @param {Vector3} targetNormal - The target object holding the sampled normal.
331
+ * @param {Color} targetColor - The target object holding the sampled color.
332
+ * @param {Vector2} targetUV - The target object holding the sampled uv coordinates.
333
+ * @return {MeshSurfaceSampler} A reference to this sampler.
334
+ */
262
335
  sample(targetPosition, targetNormal, targetColor, targetUV) {
263
- const faceIndex = this.sampleFaceIndex();
264
- return this.sampleFace(faceIndex, targetPosition, targetNormal, targetColor, targetUV);
336
+ const faceIndex = this._sampleFaceIndex();
337
+ return this._sampleFace(faceIndex, targetPosition, targetNormal, targetColor, targetUV);
265
338
  }
266
- sampleFaceIndex() {
339
+ // private
340
+ _sampleFaceIndex() {
267
341
  const cumulativeTotal = this.distribution[this.distribution.length - 1];
268
- return this.binarySearch(this.randomFunction() * cumulativeTotal);
342
+ return this._binarySearch(this.randomFunction() * cumulativeTotal);
269
343
  }
270
- binarySearch(x) {
344
+ _binarySearch(x) {
271
345
  const dist = this.distribution;
272
346
  let start = 0;
273
347
  let end = dist.length - 1;
@@ -285,7 +359,7 @@ class MeshSurfaceSampler {
285
359
  }
286
360
  return index;
287
361
  }
288
- sampleFace(faceIndex, targetPosition, targetNormal, targetColor, targetUV) {
362
+ _sampleFace(faceIndex, targetPosition, targetNormal, targetColor, targetUV) {
289
363
  let u = this.randomFunction();
290
364
  let v = this.randomFunction();
291
365
  if (u + v > 1) {
@@ -471,7 +545,6 @@ function sampleMesh(meshData, size) {
471
545
  }
472
546
  const instanceFragmentShader = `
473
547
  varying vec2 vUv;
474
- uniform sampler2D uTexture;
475
548
 
476
549
  uniform sampler2D uOriginTexture;
477
550
  uniform sampler2D uDestinationTexture;
@@ -485,11 +558,11 @@ void main() {
485
558
  vec3 y = cross( viewDir, x );
486
559
  vec2 uv = vec2( dot( x, vNormal ), dot( y, vNormal ) ) * 0.495 + 0.5; // 0.495 to remove artifacts caused by undersized matcap disks
487
560
 
488
- vec4 sourceMatcap = texture2D( uOriginTexture, uv );
489
- vec4 targetMatcap = texture2D( uDestinationTexture, uv );
561
+ vec4 textureA = texture2D( uOriginTexture, uv );
562
+ vec4 textureB = texture2D( uDestinationTexture, uv );
490
563
 
491
- vec4 matcap = mix(sourceMatcap, targetMatcap, uProgress);
492
- gl_FragColor = matcap;
564
+ vec4 finalColor = mix(textureA, textureB, uProgress);
565
+ gl_FragColor = finalColor;
493
566
  }
494
567
  `;
495
568
  const instanceVertexShader = `
@@ -545,11 +618,9 @@ class InstancedMeshManager {
545
618
  constructor(initialSize) {
546
619
  __publicField(this, "size");
547
620
  __publicField(this, "mesh");
548
- __publicField(this, "matcapMaterial");
621
+ __publicField(this, "shaderMaterial");
549
622
  __publicField(this, "fallbackGeometry");
550
623
  __publicField(this, "uniforms");
551
- __publicField(this, "originColor");
552
- __publicField(this, "destinationColor");
553
624
  __publicField(this, "geometries");
554
625
  __publicField(this, "uvRefsCache");
555
626
  __publicField(this, "previousScale");
@@ -557,8 +628,6 @@ class InstancedMeshManager {
557
628
  this.geometries = /* @__PURE__ */ new Map();
558
629
  this.uvRefsCache = /* @__PURE__ */ new Map();
559
630
  this.previousScale = { x: 1, y: 1, z: 1 };
560
- this.originColor = "grey";
561
- this.destinationColor = "grey";
562
631
  this.uniforms = {
563
632
  uTime: { value: 0 },
564
633
  uProgress: { value: 0 },
@@ -567,15 +636,14 @@ class InstancedMeshManager {
567
636
  uOriginTexture: { value: null },
568
637
  uDestinationTexture: { value: null }
569
638
  };
570
- this.matcapMaterial = new THREE.ShaderMaterial({
639
+ this.shaderMaterial = new THREE.ShaderMaterial({
571
640
  uniforms: this.uniforms,
572
641
  vertexShader: instanceVertexShader,
573
642
  fragmentShader: instanceFragmentShader
574
643
  });
575
- this.setOriginColor(this.originColor);
576
- this.setDestinationColor(this.destinationColor);
577
644
  this.fallbackGeometry = new THREE.BoxGeometry(1e-3, 1e-3, 1e-3);
578
- this.mesh = this.createInstancedMesh(initialSize, this.fallbackGeometry, this.matcapMaterial);
645
+ this.mesh = this.createInstancedMesh(initialSize, this.fallbackGeometry, this.shaderMaterial);
646
+ this.mesh.material = this.shaderMaterial;
579
647
  }
580
648
  /**
581
649
  * Gets the instanced mesh.
@@ -594,22 +662,10 @@ class InstancedMeshManager {
594
662
  material.uniforms.uTime.value = elapsedTime;
595
663
  }
596
664
  }
597
- /**
598
- * Sets the matcap texture.
599
- * @param matcap The matcap texture to set.
600
- */
601
- setOriginMatcap(matcap) {
602
- this.disposeSolidColorOriginTexture();
603
- this.matcapMaterial.uniforms.uOriginTexture.value = matcap;
604
- }
605
- setDestinationMatcap(matcap) {
606
- this.disposeSolidColorDestinationTexture();
607
- this.matcapMaterial.uniforms.uDestinationTexture.value = matcap;
608
- }
609
- setProgress(float) {
610
- float = Math.max(0, float);
611
- float = Math.min(1, float);
612
- this.matcapMaterial.uniforms.uProgress.value = float;
665
+ updateTextureInterpolation(textureA, textureB, progress) {
666
+ this.uniforms.uOriginTexture.value = textureA;
667
+ this.uniforms.uDestinationTexture.value = textureB;
668
+ this.uniforms.uProgress.value = progress;
613
669
  }
614
670
  setGeometrySize(size) {
615
671
  this.mesh.geometry.scale(1 / this.previousScale.x, 1 / this.previousScale.y, 1 / this.previousScale.z);
@@ -620,7 +676,7 @@ class InstancedMeshManager {
620
676
  * Use the matcap material for the instanced mesh.
621
677
  */
622
678
  useMatcapMaterial() {
623
- this.mesh.material = this.matcapMaterial;
679
+ this.mesh.material = this.shaderMaterial;
624
680
  }
625
681
  /**
626
682
  * Use the specified geometry for the instanced mesh.
@@ -637,14 +693,14 @@ class InstancedMeshManager {
637
693
  * @param texture The velocity texture to update with.
638
694
  */
639
695
  updateVelocityTexture(texture) {
640
- this.matcapMaterial.uniforms.uVelocity.value = texture;
696
+ this.shaderMaterial.uniforms.uVelocity.value = texture;
641
697
  }
642
698
  /**
643
699
  * Updates the position texture.
644
700
  * @param texture The position texture to update with.
645
701
  */
646
702
  updatePositionTexture(texture) {
647
- this.matcapMaterial.uniforms.uTexture.value = texture;
703
+ this.shaderMaterial.uniforms.uTexture.value = texture;
648
704
  }
649
705
  /**
650
706
  * Resizes or replaces the instanced mesh.
@@ -664,9 +720,7 @@ class InstancedMeshManager {
664
720
  dispose() {
665
721
  this.mesh.dispose();
666
722
  this.geometries.forEach((geometry) => geometry.dispose());
667
- this.disposeSolidColorOriginTexture();
668
- this.disposeSolidColorDestinationTexture();
669
- this.matcapMaterial.dispose();
723
+ this.shaderMaterial.dispose();
670
724
  this.uvRefsCache.clear();
671
725
  this.geometries.clear();
672
726
  }
@@ -690,16 +744,6 @@ class InstancedMeshManager {
690
744
  }
691
745
  previous == null ? void 0 : previous.dispose();
692
746
  }
693
- setOriginColor(color) {
694
- this.disposeSolidColorOriginTexture();
695
- this.originColor = color;
696
- this.uniforms.uOriginTexture.value = this.createSolidColorDataTexture(color);
697
- }
698
- setDestinationColor(color) {
699
- this.disposeSolidColorDestinationTexture();
700
- this.destinationColor = color;
701
- this.uniforms.uDestinationTexture.value = this.createSolidColorDataTexture(color);
702
- }
703
747
  /**
704
748
  * Gets the UV references for the specified size.
705
749
  * @param size The size for which to generate UV references.
@@ -735,46 +779,6 @@ class InstancedMeshManager {
735
779
  const count = size * size;
736
780
  return new THREE.InstancedMesh(geometry, material, count);
737
781
  }
738
- createSolidColorDataTexture(color, size = 16) {
739
- const col = new THREE.Color(color);
740
- const width = size;
741
- const height = size;
742
- const data = new Uint8Array(width * height * 4);
743
- const r = Math.floor(col.r * 255);
744
- const g = Math.floor(col.g * 255);
745
- const b = Math.floor(col.b * 255);
746
- for (let i = 0; i < width * height; i++) {
747
- const index = i * 4;
748
- data[index] = r;
749
- data[index + 1] = g;
750
- data[index + 2] = b;
751
- data[index + 3] = 255;
752
- }
753
- const texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
754
- texture.type = THREE.UnsignedByteType;
755
- texture.wrapS = THREE.RepeatWrapping;
756
- texture.wrapT = THREE.RepeatWrapping;
757
- texture.minFilter = THREE.NearestFilter;
758
- texture.magFilter = THREE.NearestFilter;
759
- texture.needsUpdate = true;
760
- return texture;
761
- }
762
- disposeSolidColorOriginTexture() {
763
- if (this.originColor) {
764
- this.originColor = null;
765
- if (this.uniforms.uOriginTexture.value) {
766
- this.uniforms.uOriginTexture.value.dispose();
767
- }
768
- }
769
- }
770
- disposeSolidColorDestinationTexture() {
771
- if (this.destinationColor) {
772
- this.destinationColor = null;
773
- if (this.uniforms.uDestinationTexture.value) {
774
- this.uniforms.uDestinationTexture.value.dispose();
775
- }
776
- }
777
- }
778
782
  }
779
783
  class IntersectionService {
780
784
  /**
@@ -1012,15 +1016,34 @@ class FullscreenTriangleGeometry extends BufferGeometry {
1012
1016
  }
1013
1017
  const _geometry = new FullscreenTriangleGeometry();
1014
1018
  class FullScreenQuad {
1019
+ /**
1020
+ * Constructs a new full screen quad.
1021
+ *
1022
+ * @param {?Material} material - The material to render te full screen quad with.
1023
+ */
1015
1024
  constructor(material) {
1016
1025
  this._mesh = new Mesh(_geometry, material);
1017
1026
  }
1027
+ /**
1028
+ * Frees the GPU-related resources allocated by this instance. Call this
1029
+ * method whenever the instance is no longer used in your app.
1030
+ */
1018
1031
  dispose() {
1019
1032
  this._mesh.geometry.dispose();
1020
1033
  }
1034
+ /**
1035
+ * Renders the full screen quad.
1036
+ *
1037
+ * @param {WebGLRenderer} renderer - The renderer.
1038
+ */
1021
1039
  render(renderer) {
1022
1040
  renderer.render(this._mesh, _camera);
1023
1041
  }
1042
+ /**
1043
+ * The quad's material.
1044
+ *
1045
+ * @type {?Material}
1046
+ */
1024
1047
  get material() {
1025
1048
  return this._mesh.material;
1026
1049
  }
@@ -1030,9 +1053,11 @@ class FullScreenQuad {
1030
1053
  }
1031
1054
  class GPUComputationRenderer {
1032
1055
  /**
1033
- * @param {number} sizeX Computation problem size is always 2d: sizeX * sizeY elements.
1034
- * @param {number} sizeY Computation problem size is always 2d: sizeX * sizeY elements.
1035
- * @param {WebGLRenderer} renderer The renderer
1056
+ * Constructs a new GPU computation renderer.
1057
+ *
1058
+ * @param {number} sizeX - Computation problem size is always 2d: sizeX * sizeY elements.
1059
+ * @param {number} sizeY - Computation problem size is always 2d: sizeX * sizeY elements.
1060
+ * @param {WebGLRenderer} renderer - The renderer.
1036
1061
  */
1037
1062
  constructor(sizeX, sizeY, renderer) {
1038
1063
  this.variables = [];
@@ -1742,10 +1767,10 @@ class ParticlesEngine {
1742
1767
  this.dataTextureManager = new DataTextureService(this.eventEmitter, textureSize);
1743
1768
  this.simulationRendererService = new SimulationRendererService(this.eventEmitter, textureSize, this.renderer);
1744
1769
  this.instancedMeshManager = new InstancedMeshManager(textureSize);
1745
- this.instancedMeshManager.useMatcapMaterial();
1746
1770
  this.scene.add(this.instancedMeshManager.getMesh());
1747
1771
  this.intersectionService = new IntersectionService(this.eventEmitter, camera);
1748
1772
  if (!useIntersection) this.intersectionService.setActive(false);
1773
+ this.setOverallProgress(0, false);
1749
1774
  this.eventEmitter.on("interactionPositionUpdated", this.handleInteractionPositionUpdated.bind(this));
1750
1775
  }
1751
1776
  /**
@@ -1761,45 +1786,10 @@ class ParticlesEngine {
1761
1786
  this.instancedMeshManager.updateVelocityTexture(this.simulationRendererService.getVelocityTexture());
1762
1787
  this.instancedMeshManager.updatePositionTexture(this.simulationRendererService.getPositionTexture());
1763
1788
  }
1764
- setOriginMatcap(matcapID, override = false) {
1765
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1766
- this.engineState.originMatcapID = matcapID;
1767
- this.instancedMeshManager.setOriginMatcap(this.assetService.getMatcap(matcapID));
1768
- }
1769
- setDestinationMatcap(matcapID, override = false) {
1770
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1771
- this.engineState.destinationMatcapID = matcapID;
1772
- this.instancedMeshManager.setDestinationMatcap(this.assetService.getMatcap(matcapID));
1773
- }
1774
- setOriginColor(color, override = false) {
1775
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1776
- this.instancedMeshManager.setOriginColor(color);
1777
- }
1778
- setDestinationColor(color, override = false) {
1779
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1780
- this.instancedMeshManager.setDestinationColor(color);
1781
- }
1782
- setOriginTexture(id, override = false) {
1783
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1784
- if (typeof id === "string" && this.assetService.hasMatcap(id)) {
1785
- this.setOriginMatcap(id);
1786
- } else {
1787
- this.setOriginColor(id);
1788
- }
1789
- }
1790
- setDestinationTexture(id, override = false) {
1791
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1792
- if (typeof id === "string" && this.assetService.hasMatcap(id)) {
1793
- this.setDestinationMatcap(id);
1794
- } else {
1795
- this.setDestinationColor(id);
1796
- }
1797
- }
1798
- setMatcapProgress(progress, override = false) {
1799
- const clampedProgress = clamp(progress, 0, 1);
1800
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1801
- this.engineState.matcapTransitionProgress = clampedProgress;
1802
- this.instancedMeshManager.setProgress(clampedProgress);
1789
+ setTextureSequence(sequence) {
1790
+ this.engineState.textureSequence = sequence;
1791
+ this.eventEmitter.emit("textureSequenceUpdated", { sequence });
1792
+ this.setOverallProgress(0, false);
1803
1793
  }
1804
1794
  // --- Update setTextureSize ---
1805
1795
  async setTextureSize(size) {
@@ -1821,10 +1811,8 @@ class ParticlesEngine {
1821
1811
  this.simulationRendererService.setMaxRepelDistance(this.engineState.maxRepelDistance);
1822
1812
  this.simulationRendererService.setOverallProgress(this.engineState.overallProgress);
1823
1813
  this.intersectionService.setOverallProgress(this.engineState.overallProgress);
1824
- this.instancedMeshManager.setOriginMatcap(this.assetService.getMatcap(this.engineState.originMatcapID));
1825
- this.instancedMeshManager.setDestinationMatcap(this.assetService.getMatcap(this.engineState.destinationMatcapID));
1826
- this.instancedMeshManager.setProgress(this.engineState.matcapTransitionProgress);
1827
1814
  this.instancedMeshManager.setGeometrySize(this.engineState.instanceGeometryScale);
1815
+ this.setOverallProgress(this.engineState.overallProgress, false);
1828
1816
  }
1829
1817
  registerMesh(id, mesh) {
1830
1818
  this.assetService.register(id, mesh);
@@ -1907,6 +1895,7 @@ class ParticlesEngine {
1907
1895
  this.simulationRendererService.setOverallProgress(this.engineState.overallProgress);
1908
1896
  this.intersectionService.setMeshSequence(meshes);
1909
1897
  this.intersectionService.setOverallProgress(this.engineState.overallProgress);
1898
+ this.setOverallProgress(0, false);
1910
1899
  } catch (error) {
1911
1900
  console.error("Failed during mesh sequence setup:", error);
1912
1901
  this.meshSequenceAtlasTexture = null;
@@ -1925,79 +1914,8 @@ class ParticlesEngine {
1925
1914
  this.engineState.overallProgress = clampedProgress;
1926
1915
  this.simulationRendererService.setOverallProgress(clampedProgress);
1927
1916
  this.intersectionService.setOverallProgress(clampedProgress);
1928
- }
1929
- // --- Transition scheduling methods remain the same ---
1930
- scheduleMatcapTransition(originMatcapID, destinationMatcapID, easing = linear, duration = 1e3, override = false, options = {}) {
1931
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1932
- const handleProgressUpdate = (transitionProgress) => {
1933
- var _a;
1934
- this.setMatcapProgress(transitionProgress, false);
1935
- (_a = options.onTransitionProgress) == null ? void 0 : _a.call(options, transitionProgress);
1936
- };
1937
- this.transitionService.enqueue(
1938
- "matcap",
1939
- { easing, duration },
1940
- {
1941
- ...options,
1942
- onTransitionProgress: handleProgressUpdate,
1943
- onTransitionBegin: () => {
1944
- var _a;
1945
- this.setOriginMatcap(originMatcapID, false);
1946
- this.setDestinationMatcap(destinationMatcapID, false);
1947
- this.setMatcapProgress(0, false);
1948
- (_a = options.onTransitionBegin) == null ? void 0 : _a.call(options);
1949
- },
1950
- onTransitionFinished: () => {
1951
- var _a;
1952
- this.setMatcapProgress(1, false);
1953
- (_a = options.onTransitionFinished) == null ? void 0 : _a.call(options);
1954
- },
1955
- onTransitionCancelled: options.onTransitionCancelled
1956
- }
1957
- );
1958
- }
1959
- scheduleTextureTransition(origin, destination, options = {}) {
1960
- const easing = (options == null ? void 0 : options.easing) ?? linear;
1961
- const duration = (options == null ? void 0 : options.duration) ?? 1e3;
1962
- const userCallbacks = {
1963
- // Extract user callbacks
1964
- onTransitionBegin: options == null ? void 0 : options.onTransitionBegin,
1965
- onTransitionProgress: options == null ? void 0 : options.onTransitionProgress,
1966
- onTransitionFinished: options == null ? void 0 : options.onTransitionFinished,
1967
- onTransitionCancelled: options == null ? void 0 : options.onTransitionCancelled
1968
- };
1969
- if (options == null ? void 0 : options.override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1970
- const handleProgressUpdate = (transitionProgress) => {
1971
- var _a;
1972
- this.setMatcapProgress(transitionProgress, false);
1973
- (_a = userCallbacks.onTransitionProgress) == null ? void 0 : _a.call(userCallbacks, transitionProgress);
1974
- };
1975
- this.transitionService.enqueue(
1976
- "matcap",
1977
- // Still uses matcap type internally
1978
- { easing, duration },
1979
- {
1980
- ...userCallbacks,
1981
- // Pass user callbacks
1982
- onTransitionProgress: handleProgressUpdate,
1983
- onTransitionBegin: () => {
1984
- var _a;
1985
- this.setOriginTexture(origin);
1986
- this.setDestinationTexture(destination);
1987
- this.setMatcapProgress(0);
1988
- (_a = userCallbacks.onTransitionBegin) == null ? void 0 : _a.call(userCallbacks);
1989
- },
1990
- onTransitionFinished: () => {
1991
- var _a;
1992
- this.setMatcapProgress(1);
1993
- (_a = userCallbacks.onTransitionFinished) == null ? void 0 : _a.call(userCallbacks);
1994
- },
1995
- onTransitionCancelled: () => {
1996
- var _a;
1997
- (_a = userCallbacks.onTransitionCancelled) == null ? void 0 : _a.call(userCallbacks);
1998
- }
1999
- }
2000
- );
1917
+ const { textureA, textureB, localProgress } = this.calculateTextureInterpolation(progress);
1918
+ this.instancedMeshManager.updateTextureInterpolation(textureA, textureB, localProgress);
2001
1919
  }
2002
1920
  /**
2003
1921
  * Schedules a smooth transition for the overall mesh sequence progress.
@@ -2080,9 +1998,7 @@ class ParticlesEngine {
2080
1998
  // ADDED
2081
1999
  overallProgress: 0,
2082
2000
  // ADDED
2083
- originMatcapID: "",
2084
- destinationMatcapID: "",
2085
- matcapTransitionProgress: 0,
2001
+ textureSequence: [],
2086
2002
  velocityTractionForce: 0.1,
2087
2003
  positionalTractionForce: 0.1,
2088
2004
  maxRepelDistance: 0.3,
@@ -2103,6 +2019,47 @@ class ParticlesEngine {
2103
2019
  handleInteractionPositionUpdated({ position }) {
2104
2020
  this.simulationRendererService.setInteractionPosition(position);
2105
2021
  }
2022
+ calculateTextureInterpolation(progress) {
2023
+ const sequence = this.engineState.textureSequence;
2024
+ const numItems = sequence.length;
2025
+ if (numItems === 0) {
2026
+ const defaultTex = this.assetService.getFallbackTexture();
2027
+ return { textureA: defaultTex, textureB: defaultTex, localProgress: 0 };
2028
+ }
2029
+ if (numItems === 1) {
2030
+ const tex = this.getTextureForSequenceItem(sequence[0]);
2031
+ return { textureA: tex, textureB: tex, localProgress: 0 };
2032
+ }
2033
+ const totalSegments = numItems - 1;
2034
+ const progressPerSegment = 1 / totalSegments;
2035
+ const scaledProgress = progress * totalSegments;
2036
+ let indexA = Math.floor(scaledProgress);
2037
+ let indexB = indexA + 1;
2038
+ indexA = Math.max(0, Math.min(indexA, totalSegments));
2039
+ indexB = Math.max(0, Math.min(indexB, totalSegments));
2040
+ let localProgress = 0;
2041
+ if (progressPerSegment > 0) {
2042
+ localProgress = (progress - indexA * progressPerSegment) / progressPerSegment;
2043
+ }
2044
+ if (progress >= 1) {
2045
+ indexA = totalSegments;
2046
+ indexB = totalSegments;
2047
+ localProgress = 1;
2048
+ }
2049
+ localProgress = Math.max(0, Math.min(localProgress, 1));
2050
+ const itemA = sequence[indexA];
2051
+ const itemB = sequence[indexB];
2052
+ const textureA = this.getTextureForSequenceItem(itemA);
2053
+ const textureB = this.getTextureForSequenceItem(itemB);
2054
+ return { textureA, textureB, localProgress };
2055
+ }
2056
+ getTextureForSequenceItem(item) {
2057
+ if (item.type === "matcap") {
2058
+ return this.assetService.getMatcapTexture(item.id);
2059
+ } else {
2060
+ return this.assetService.getSolidColorTexture(item.value);
2061
+ }
2062
+ }
2106
2063
  }
2107
2064
  export {
2108
2065
  ParticlesEngine