@polarfront-lab/ionian 1.6.0 → 2.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
@@ -206,6 +206,11 @@ const _face = new Triangle();
206
206
  const _color = new Vector3();
207
207
  const _uva = new Vector2(), _uvb = new Vector2(), _uvc = new Vector2();
208
208
  class MeshSurfaceSampler {
209
+ /**
210
+ * Constructs a mesh surface sampler.
211
+ *
212
+ * @param {Mesh} mesh - Surface mesh from which to sample.
213
+ */
209
214
  constructor(mesh) {
210
215
  this.geometry = mesh.geometry;
211
216
  this.randomFunction = Math.random;
@@ -217,10 +222,27 @@ class MeshSurfaceSampler {
217
222
  this.weightAttribute = null;
218
223
  this.distribution = null;
219
224
  }
225
+ /**
226
+ * Specifies a vertex attribute to be used as a weight when sampling from the surface.
227
+ * Faces with higher weights are more likely to be sampled, and those with weights of
228
+ * zero will not be sampled at all. For vector attributes, only .x is used in sampling.
229
+ *
230
+ * If no weight attribute is selected, sampling is randomly distributed by area.
231
+ *
232
+ * @param {string} name - The attribute name.
233
+ * @return {MeshSurfaceSampler} A reference to this sampler.
234
+ */
220
235
  setWeightAttribute(name) {
221
236
  this.weightAttribute = name ? this.geometry.getAttribute(name) : null;
222
237
  return this;
223
238
  }
239
+ /**
240
+ * Processes the input geometry and prepares to return samples. Any configuration of the
241
+ * geometry or sampler must occur before this method is called. Time complexity is O(n)
242
+ * for a surface with n faces.
243
+ *
244
+ * @return {MeshSurfaceSampler} A reference to this sampler.
245
+ */
224
246
  build() {
225
247
  const indexAttribute = this.indexAttribute;
226
248
  const positionAttribute = this.positionAttribute;
@@ -255,19 +277,37 @@ class MeshSurfaceSampler {
255
277
  this.distribution = distribution;
256
278
  return this;
257
279
  }
280
+ /**
281
+ * Allows to set a custom random number generator. Default is `Math.random()`.
282
+ *
283
+ * @param {Function} randomFunction - A random number generator.
284
+ * @return {MeshSurfaceSampler} A reference to this sampler.
285
+ */
258
286
  setRandomGenerator(randomFunction) {
259
287
  this.randomFunction = randomFunction;
260
288
  return this;
261
289
  }
290
+ /**
291
+ * Selects a random point on the surface of the input geometry, returning the
292
+ * position and optionally the normal vector, color and UV Coordinate at that point.
293
+ * Time complexity is O(log n) for a surface with n faces.
294
+ *
295
+ * @param {Vector3} targetPosition - The target object holding the sampled position.
296
+ * @param {Vector3} targetNormal - The target object holding the sampled normal.
297
+ * @param {Color} targetColor - The target object holding the sampled color.
298
+ * @param {Vector2} targetUV - The target object holding the sampled uv coordinates.
299
+ * @return {MeshSurfaceSampler} A reference to this sampler.
300
+ */
262
301
  sample(targetPosition, targetNormal, targetColor, targetUV) {
263
- const faceIndex = this.sampleFaceIndex();
264
- return this.sampleFace(faceIndex, targetPosition, targetNormal, targetColor, targetUV);
302
+ const faceIndex = this._sampleFaceIndex();
303
+ return this._sampleFace(faceIndex, targetPosition, targetNormal, targetColor, targetUV);
265
304
  }
266
- sampleFaceIndex() {
305
+ // private
306
+ _sampleFaceIndex() {
267
307
  const cumulativeTotal = this.distribution[this.distribution.length - 1];
268
- return this.binarySearch(this.randomFunction() * cumulativeTotal);
308
+ return this._binarySearch(this.randomFunction() * cumulativeTotal);
269
309
  }
270
- binarySearch(x) {
310
+ _binarySearch(x) {
271
311
  const dist = this.distribution;
272
312
  let start = 0;
273
313
  let end = dist.length - 1;
@@ -285,7 +325,7 @@ class MeshSurfaceSampler {
285
325
  }
286
326
  return index;
287
327
  }
288
- sampleFace(faceIndex, targetPosition, targetNormal, targetColor, targetUV) {
328
+ _sampleFace(faceIndex, targetPosition, targetNormal, targetColor, targetUV) {
289
329
  let u = this.randomFunction();
290
330
  let v = this.randomFunction();
291
331
  if (u + v > 1) {
@@ -334,6 +374,7 @@ class MeshSurfaceSampler {
334
374
  }
335
375
  }
336
376
  class DataTextureService {
377
+ // Cache the current atlas
337
378
  /**
338
379
  * Creates a new DataTextureManager instance.
339
380
  * @param eventEmitter
@@ -343,6 +384,7 @@ class DataTextureService {
343
384
  __publicField(this, "textureSize");
344
385
  __publicField(this, "dataTextures");
345
386
  __publicField(this, "eventEmitter");
387
+ __publicField(this, "currentAtlas", null);
346
388
  this.eventEmitter = eventEmitter;
347
389
  this.textureSize = textureSize;
348
390
  this.dataTextures = /* @__PURE__ */ new Map();
@@ -353,6 +395,10 @@ class DataTextureService {
353
395
  this.textureSize = textureSize;
354
396
  this.dataTextures.forEach((texture) => texture.dispose());
355
397
  this.dataTextures.clear();
398
+ if (this.currentAtlas) {
399
+ this.currentAtlas.dispose();
400
+ this.currentAtlas = null;
401
+ }
356
402
  }
357
403
  /**
358
404
  * Prepares a mesh for sampling.
@@ -360,23 +406,76 @@ class DataTextureService {
360
406
  * @param asset The asset to prepare.
361
407
  */
362
408
  async getDataTexture(asset) {
363
- const texture = this.dataTextures.get(asset.name);
364
- if (texture) {
365
- return texture;
409
+ const cachedTexture = this.dataTextures.get(asset.uuid);
410
+ if (cachedTexture) {
411
+ return cachedTexture;
366
412
  }
367
413
  const meshData = parseMeshData(asset);
368
414
  const array = sampleMesh(meshData, this.textureSize);
369
415
  const dataTexture = createDataTexture(array, this.textureSize);
370
416
  dataTexture.name = asset.name;
417
+ this.dataTextures.set(asset.uuid, dataTexture);
371
418
  return dataTexture;
372
419
  }
373
420
  async dispose() {
421
+ this.dataTextures.forEach((texture) => texture.dispose());
374
422
  this.dataTextures.clear();
423
+ if (this.currentAtlas) {
424
+ this.currentAtlas.dispose();
425
+ this.currentAtlas = null;
426
+ }
375
427
  this.updateServiceState("disposed");
376
428
  }
377
429
  updateServiceState(serviceState) {
378
430
  this.eventEmitter.emit("serviceStateUpdated", { type: "data-texture", state: serviceState });
379
431
  }
432
+ /**
433
+ * Creates a Texture Atlas containing position data for a sequence of meshes.
434
+ * @param meshes An array of THREE.Mesh objects in the desired sequence.
435
+ * @param singleTextureSize The desired resolution (width/height) for each mesh's data within the atlas.
436
+ * @returns A Promise resolving to the generated DataTexture atlas.
437
+ */
438
+ async createSequenceDataTextureAtlas(meshes, singleTextureSize) {
439
+ this.updateServiceState("loading");
440
+ if (this.currentAtlas) {
441
+ this.currentAtlas.dispose();
442
+ this.currentAtlas = null;
443
+ }
444
+ const numMeshes = meshes.length;
445
+ if (numMeshes === 0) {
446
+ throw new Error("Mesh array cannot be empty.");
447
+ }
448
+ const atlasWidth = singleTextureSize * numMeshes;
449
+ const atlasHeight = singleTextureSize;
450
+ const atlasData = new Float32Array(atlasWidth * atlasHeight * 4);
451
+ try {
452
+ for (let i = 0; i < numMeshes; i++) {
453
+ const mesh = meshes[i];
454
+ const meshDataTexture = await this.getDataTexture(mesh);
455
+ const meshTextureData = meshDataTexture.image.data;
456
+ for (let y = 0; y < singleTextureSize; y++) {
457
+ for (let x = 0; x < singleTextureSize; x++) {
458
+ const sourceIndex = (y * singleTextureSize + x) * 4;
459
+ const targetX = x + i * singleTextureSize;
460
+ const targetIndex = (y * atlasWidth + targetX) * 4;
461
+ atlasData[targetIndex] = meshTextureData[sourceIndex];
462
+ atlasData[targetIndex + 1] = meshTextureData[sourceIndex + 1];
463
+ atlasData[targetIndex + 2] = meshTextureData[sourceIndex + 2];
464
+ atlasData[targetIndex + 3] = meshTextureData[sourceIndex + 3];
465
+ }
466
+ }
467
+ }
468
+ const atlasTexture = new THREE.DataTexture(atlasData, atlasWidth, atlasHeight, THREE.RGBAFormat, THREE.FloatType);
469
+ atlasTexture.needsUpdate = true;
470
+ atlasTexture.name = `atlas-${meshes.map((m) => m.name).join("-")}`;
471
+ this.currentAtlas = atlasTexture;
472
+ this.updateServiceState("ready");
473
+ return atlasTexture;
474
+ } catch (error) {
475
+ this.updateServiceState("error");
476
+ throw error;
477
+ }
478
+ }
380
479
  }
381
480
  function parseMeshData(mesh) {
382
481
  var _a;
@@ -722,32 +821,35 @@ class IntersectionService {
722
821
  * Creates a new IntersectionService instance.
723
822
  * @param eventEmitter The event emitter used for emitting events.
724
823
  * @param camera The camera used for raycasting.
725
- * @param originGeometry The origin geometry.
726
- * @param destinationGeometry The destination geometry.
727
824
  */
728
- constructor(eventEmitter, camera, originGeometry, destinationGeometry) {
825
+ constructor(eventEmitter, camera) {
729
826
  __publicField(this, "active", true);
730
827
  __publicField(this, "raycaster", new THREE.Raycaster());
731
828
  __publicField(this, "mousePosition", new THREE.Vector2());
732
829
  __publicField(this, "camera");
733
- __publicField(this, "originGeometry");
734
- __publicField(this, "destinationGeometry");
735
- __publicField(this, "progress", 0);
830
+ __publicField(this, "meshSequenceGeometries", []);
831
+ // ADDED: Store cloned geometries
832
+ __publicField(this, "meshSequenceUUIDs", []);
833
+ // ADDED: Track UUIDs to avoid redundant cloning
834
+ __publicField(this, "overallProgress", 0);
835
+ // ADDED: Store overall progress (0-1)
736
836
  __publicField(this, "intersectionMesh", new THREE.Mesh());
837
+ // Use a single mesh for intersection target
737
838
  __publicField(this, "geometryNeedsUpdate");
738
839
  __publicField(this, "eventEmitter");
739
840
  __publicField(this, "blendedGeometry");
841
+ // Keep for the final blended result
740
842
  __publicField(this, "intersection");
741
- __publicField(this, "lastKnownOriginMeshID");
742
- __publicField(this, "lastKnownDestinationMeshID");
743
843
  this.camera = camera;
744
- this.originGeometry = originGeometry;
745
844
  this.eventEmitter = eventEmitter;
746
- this.destinationGeometry = destinationGeometry;
747
845
  this.geometryNeedsUpdate = true;
748
846
  }
749
847
  setActive(active) {
750
848
  this.active = active;
849
+ if (!active) {
850
+ this.intersection = void 0;
851
+ this.eventEmitter.emit("interactionPositionUpdated", { position: { x: 0, y: 0, z: 0, w: 0 } });
852
+ }
751
853
  }
752
854
  getIntersectionMesh() {
753
855
  return this.intersectionMesh;
@@ -760,36 +862,40 @@ class IntersectionService {
760
862
  this.camera = camera;
761
863
  }
762
864
  /**
763
- * Set the origin geometry.
764
- * @param source
765
- */
766
- setOriginGeometry(source) {
767
- if (this.lastKnownOriginMeshID === source.uuid) return;
768
- if (this.originGeometry) this.originGeometry.dispose();
769
- this.lastKnownOriginMeshID = source.uuid;
770
- this.originGeometry = source.geometry.clone();
771
- this.originGeometry.applyMatrix4(source.matrixWorld);
772
- this.geometryNeedsUpdate = true;
773
- }
774
- /**
775
- * Set the destination geometry.
776
- * @param source
865
+ * Sets the sequence of meshes used for intersection calculations.
866
+ * Clones the geometries to avoid modifying originals.
867
+ * @param meshes An array of THREE.Mesh objects in sequence.
777
868
  */
778
- setDestinationGeometry(source) {
779
- if (this.lastKnownDestinationMeshID === source.uuid) return;
780
- if (this.destinationGeometry) this.destinationGeometry.dispose();
781
- this.lastKnownDestinationMeshID = source.uuid;
782
- this.destinationGeometry = source.geometry.clone();
783
- this.destinationGeometry.applyMatrix4(source.matrixWorld);
869
+ setMeshSequence(meshes) {
870
+ this.meshSequenceGeometries.forEach((geom) => geom.dispose());
871
+ this.meshSequenceGeometries = [];
872
+ this.meshSequenceUUIDs = [];
873
+ if (!meshes || meshes.length === 0) {
874
+ this.geometryNeedsUpdate = true;
875
+ return;
876
+ }
877
+ meshes.forEach((mesh) => {
878
+ if (mesh && mesh.geometry) {
879
+ const clonedGeometry = mesh.geometry.clone();
880
+ clonedGeometry.applyMatrix4(mesh.matrixWorld);
881
+ this.meshSequenceGeometries.push(clonedGeometry);
882
+ this.meshSequenceUUIDs.push(mesh.uuid);
883
+ } else {
884
+ console.warn("Invalid mesh provided to IntersectionService sequence.");
885
+ }
886
+ });
784
887
  this.geometryNeedsUpdate = true;
785
888
  }
786
889
  /**
787
- * Set the progress of the morphing animation.
788
- * @param progress
890
+ * Set the overall progress through the mesh sequence.
891
+ * @param progress Value between 0.0 (first mesh) and 1.0 (last mesh).
789
892
  */
790
- setProgress(progress) {
791
- this.progress = progress;
792
- this.geometryNeedsUpdate = true;
893
+ setOverallProgress(progress) {
894
+ const newProgress = THREE.MathUtils.clamp(progress, 0, 1);
895
+ if (this.overallProgress !== newProgress) {
896
+ this.overallProgress = newProgress;
897
+ this.geometryNeedsUpdate = true;
898
+ }
793
899
  }
794
900
  /**
795
901
  * Set the mouse position.
@@ -803,22 +909,46 @@ class IntersectionService {
803
909
  * @returns The intersection point or undefined if no intersection was found.
804
910
  */
805
911
  calculate(instancedMesh) {
806
- if (!this.active) return;
807
- this.updateIntersectionMesh(instancedMesh);
808
- if (!this.camera) return;
912
+ var _a, _b, _c;
913
+ if (!this.active || !this.camera || this.meshSequenceGeometries.length === 0) {
914
+ if (this.intersection) {
915
+ this.intersection = void 0;
916
+ this.eventEmitter.emit("interactionPositionUpdated", { position: { x: 0, y: 0, z: 0, w: 0 } });
917
+ }
918
+ return void 0;
919
+ }
809
920
  if (this.geometryNeedsUpdate) {
810
- this.geometryNeedsUpdate = false;
921
+ if (this.blendedGeometry && this.blendedGeometry !== this.intersectionMesh.geometry) {
922
+ this.blendedGeometry.dispose();
923
+ }
811
924
  this.blendedGeometry = this.getBlendedGeometry();
925
+ this.geometryNeedsUpdate = false;
926
+ if (this.blendedGeometry) {
927
+ if (this.intersectionMesh.geometry !== this.blendedGeometry) {
928
+ if (this.intersectionMesh.geometry) this.intersectionMesh.geometry.dispose();
929
+ this.intersectionMesh.geometry = this.blendedGeometry;
930
+ }
931
+ } else {
932
+ if (this.intersectionMesh.geometry) this.intersectionMesh.geometry.dispose();
933
+ this.intersectionMesh.geometry = new THREE.BufferGeometry();
934
+ }
812
935
  }
813
- if (this.blendedGeometry) {
814
- this.intersection = this.getFirstIntersection(this.camera, instancedMesh);
815
- } else {
816
- this.intersection = void 0;
936
+ this.intersectionMesh.matrixWorld.copy(instancedMesh.matrixWorld);
937
+ let newIntersection = void 0;
938
+ if (this.blendedGeometry && this.blendedGeometry.attributes.position) {
939
+ newIntersection = this.getFirstIntersection(this.camera, this.intersectionMesh);
817
940
  }
818
- if (this.intersection) {
819
- this.eventEmitter.emit("interactionPositionUpdated", { position: this.intersection });
820
- } else {
821
- this.eventEmitter.emit("interactionPositionUpdated", { position: { x: 0, y: 0, z: 0, w: 0 } });
941
+ const hasChanged = ((_a = this.intersection) == null ? void 0 : _a.x) !== (newIntersection == null ? void 0 : newIntersection.x) || ((_b = this.intersection) == null ? void 0 : _b.y) !== (newIntersection == null ? void 0 : newIntersection.y) || ((_c = this.intersection) == null ? void 0 : _c.z) !== (newIntersection == null ? void 0 : newIntersection.z) || this.intersection && !newIntersection || !this.intersection && newIntersection;
942
+ if (hasChanged) {
943
+ this.intersection = newIntersection;
944
+ if (this.intersection) {
945
+ const worldPoint = new THREE.Vector3(this.intersection.x, this.intersection.y, this.intersection.z);
946
+ const localPoint = instancedMesh.worldToLocal(worldPoint.clone());
947
+ this.intersection.set(localPoint.x, localPoint.y, localPoint.z, 1);
948
+ this.eventEmitter.emit("interactionPositionUpdated", { position: this.intersection });
949
+ } else {
950
+ this.eventEmitter.emit("interactionPositionUpdated", { position: { x: 0, y: 0, z: 0, w: 0 } });
951
+ }
822
952
  }
823
953
  return this.intersection;
824
954
  }
@@ -827,8 +957,13 @@ class IntersectionService {
827
957
  */
828
958
  dispose() {
829
959
  var _a;
830
- (_a = this.blendedGeometry) == null ? void 0 : _a.dispose();
831
- this.intersectionMesh.geometry.dispose();
960
+ this.meshSequenceGeometries.forEach((geom) => geom.dispose());
961
+ this.meshSequenceGeometries = [];
962
+ this.meshSequenceUUIDs = [];
963
+ if (this.blendedGeometry && this.blendedGeometry !== this.intersectionMesh.geometry) {
964
+ this.blendedGeometry.dispose();
965
+ }
966
+ (_a = this.intersectionMesh.geometry) == null ? void 0 : _a.dispose();
832
967
  }
833
968
  updateIntersectionMesh(instancedMesh) {
834
969
  if (this.blendedGeometry) {
@@ -842,29 +977,50 @@ class IntersectionService {
842
977
  this.intersectionMesh.matrixAutoUpdate = false;
843
978
  this.intersectionMesh.updateMatrixWorld(true);
844
979
  }
845
- getFirstIntersection(camera, instancedMesh) {
980
+ getFirstIntersection(camera, targetMesh) {
846
981
  this.raycaster.setFromCamera(this.mousePosition, camera);
847
- const intersection = this.raycaster.intersectObject(this.intersectionMesh, false)[0];
848
- if (intersection) {
849
- const worldPoint = intersection.point.clone();
850
- const localPoint = instancedMesh.worldToLocal(worldPoint);
851
- return new THREE.Vector4(localPoint.x, localPoint.y, localPoint.z, 1);
982
+ const intersects = this.raycaster.intersectObject(targetMesh, false);
983
+ if (intersects.length > 0 && intersects[0].point) {
984
+ const worldPoint = intersects[0].point;
985
+ return new THREE.Vector4(worldPoint.x, worldPoint.y, worldPoint.z, 1);
852
986
  }
987
+ return void 0;
853
988
  }
854
989
  getBlendedGeometry() {
855
- if (this.progress === 0) {
856
- return this.originGeometry;
990
+ const numGeometries = this.meshSequenceGeometries.length;
991
+ if (numGeometries === 0) {
992
+ return void 0;
857
993
  }
858
- if (this.progress === 1) {
859
- return this.destinationGeometry;
994
+ if (numGeometries === 1) {
995
+ return this.meshSequenceGeometries[0];
860
996
  }
861
- if (!this.originGeometry || !this.destinationGeometry) {
862
- return;
997
+ const totalSegments = numGeometries - 1;
998
+ const progressPerSegment = 1 / totalSegments;
999
+ const scaledProgress = this.overallProgress * totalSegments;
1000
+ let indexA = Math.floor(scaledProgress);
1001
+ let indexB = indexA + 1;
1002
+ indexA = THREE.MathUtils.clamp(indexA, 0, totalSegments);
1003
+ indexB = THREE.MathUtils.clamp(indexB, 0, totalSegments);
1004
+ let localProgress = 0;
1005
+ if (progressPerSegment > 0) {
1006
+ localProgress = scaledProgress - indexA;
863
1007
  }
864
- if (this.originGeometry === this.destinationGeometry) {
865
- return this.originGeometry;
1008
+ if (this.overallProgress >= 1) {
1009
+ indexA = totalSegments;
1010
+ indexB = totalSegments;
1011
+ localProgress = 1;
866
1012
  }
867
- return this.blendGeometry(this.originGeometry, this.destinationGeometry, this.progress);
1013
+ localProgress = THREE.MathUtils.clamp(localProgress, 0, 1);
1014
+ const geomA = this.meshSequenceGeometries[indexA];
1015
+ const geomB = this.meshSequenceGeometries[indexB];
1016
+ if (!geomA || !geomB) {
1017
+ console.error("IntersectionService: Invalid geometries found for blending at indices", indexA, indexB);
1018
+ return this.meshSequenceGeometries[0];
1019
+ }
1020
+ if (indexA === indexB) {
1021
+ return geomA;
1022
+ }
1023
+ return this.blendGeometry(geomA, geomB, localProgress);
868
1024
  }
869
1025
  blendGeometry(from, to, progress) {
870
1026
  const blended = new THREE.BufferGeometry();
@@ -896,15 +1052,34 @@ class FullscreenTriangleGeometry extends BufferGeometry {
896
1052
  }
897
1053
  const _geometry = new FullscreenTriangleGeometry();
898
1054
  class FullScreenQuad {
1055
+ /**
1056
+ * Constructs a new full screen quad.
1057
+ *
1058
+ * @param {?Material} material - The material to render te full screen quad with.
1059
+ */
899
1060
  constructor(material) {
900
1061
  this._mesh = new Mesh(_geometry, material);
901
1062
  }
1063
+ /**
1064
+ * Frees the GPU-related resources allocated by this instance. Call this
1065
+ * method whenever the instance is no longer used in your app.
1066
+ */
902
1067
  dispose() {
903
1068
  this._mesh.geometry.dispose();
904
1069
  }
1070
+ /**
1071
+ * Renders the full screen quad.
1072
+ *
1073
+ * @param {WebGLRenderer} renderer - The renderer.
1074
+ */
905
1075
  render(renderer) {
906
1076
  renderer.render(this._mesh, _camera);
907
1077
  }
1078
+ /**
1079
+ * The quad's material.
1080
+ *
1081
+ * @type {?Material}
1082
+ */
908
1083
  get material() {
909
1084
  return this._mesh.material;
910
1085
  }
@@ -914,9 +1089,11 @@ class FullScreenQuad {
914
1089
  }
915
1090
  class GPUComputationRenderer {
916
1091
  /**
917
- * @param {Number} sizeX Computation problem size is always 2d: sizeX * sizeY elements.
918
- * @param {Number} sizeY Computation problem size is always 2d: sizeX * sizeY elements.
919
- * @param {WebGLRenderer} renderer The renderer
1092
+ * Constructs a new GPU computation renderer.
1093
+ *
1094
+ * @param {number} sizeX - Computation problem size is always 2d: sizeX * sizeY elements.
1095
+ * @param {number} sizeY - Computation problem size is always 2d: sizeX * sizeY elements.
1096
+ * @param {WebGLRenderer} renderer - The renderer.
920
1097
  */
921
1098
  constructor(sizeX, sizeY, renderer) {
922
1099
  this.variables = [];
@@ -1087,179 +1264,283 @@ class GPUComputationRenderer {
1087
1264
  }
1088
1265
  }
1089
1266
  }
1090
- const mixShader = `
1091
- uniform sampler2D uPositionA;
1092
- uniform sampler2D uPositionB;
1093
- uniform float uProgress;
1094
-
1095
- void main() {
1096
- vec2 uv = gl_FragCoord.xy / resolution.xy;
1097
- vec3 positionA = texture2D(uPositionA, uv).xyz;
1098
- vec3 positionB = texture2D(uPositionB, uv).xyz;
1099
- vec3 mixedPosition = mix(positionA, positionB, uProgress);
1100
- gl_FragColor = vec4(mixedPosition, 1.0);
1101
- }
1102
- `;
1103
1267
  const positionShader = `
1104
- uniform float uProgress;
1105
1268
  uniform vec4 uInteractionPosition;
1106
1269
  uniform float uTime;
1107
1270
  uniform float uTractionForce;
1271
+ uniform sampler2D uPositionAtlas;
1272
+ uniform float uOverallProgress; // (0.0 to 1.0)
1273
+ uniform int uNumMeshes;
1274
+ uniform float uSingleTextureSize;
1108
1275
 
1109
1276
  float rand(vec2 co) {
1110
1277
  return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
1111
1278
  }
1112
1279
 
1280
+ // Helper function to get position from atlas
1281
+ vec3 getAtlasPosition(vec2 uv, int meshIndex) {
1282
+ float atlasWidth = uSingleTextureSize * float(uNumMeshes);
1283
+ float atlasHeight = uSingleTextureSize; // Assuming height is single texture size
1284
+
1285
+ // Calculate UV within the specific mesh's section of the atlas
1286
+ float segmentWidthRatio = uSingleTextureSize / atlasWidth;
1287
+ vec2 atlasUV = vec2(
1288
+ uv.x * segmentWidthRatio + segmentWidthRatio * float(meshIndex),
1289
+ uv.y // Assuming vertical layout doesn't change y
1290
+ );
1291
+
1292
+ return texture2D(uPositionAtlas, atlasUV).xyz;
1293
+ }
1294
+
1113
1295
  void main() {
1296
+ // GPGPU UV calculation
1297
+ vec2 uv = gl_FragCoord.xy / resolution.xy; // resolution is the size of the *output* texture (e.g., 256x256)
1114
1298
 
1115
- // in GPGPU, we calculate the uv on each fragment shader, not using the static varying passed over from the v shader.
1116
- vec2 uv = gl_FragCoord.xy / resolution.xy;
1117
- float offset = rand(uv);
1299
+ vec3 currentPosition = texture2D(uCurrentPosition, uv).xyz;
1300
+ vec3 currentVelocity = texture2D(uCurrentVelocity, uv).xyz;
1118
1301
 
1119
- vec3 position = texture2D(uCurrentPosition, uv).xyz;
1120
- vec3 velocity = texture2D(uCurrentVelocity, uv).xyz;
1121
- vec3 mixedPosition = texture2D(uMixedPosition, uv).xyz;
1302
+ // --- Calculate Target Position from Atlas ---
1303
+ vec3 targetPosition;
1304
+ if (uNumMeshes <= 1) {
1305
+ targetPosition = getAtlasPosition(uv, 0);
1306
+ } else {
1307
+ float totalSegments = float(uNumMeshes - 1);
1308
+ float progressPerSegment = 1.0 / totalSegments;
1309
+ float scaledProgress = uOverallProgress * totalSegments;
1122
1310
 
1123
- // particle attraction to original position.
1124
- vec3 direction = normalize(mixedPosition - position); // direction vector
1125
- float dist = length ( mixedPosition - position ); // distance from where it was supposed to be, and currently are.
1311
+ int indexA = int(floor(scaledProgress));
1312
+ // Clamp indexB to avoid going out of bounds
1313
+ int indexB = min(indexA + 1, uNumMeshes - 1);
1126
1314
 
1127
- if (dist > 0.01) {
1128
- position = mix(position, mixedPosition, 0.1 * uTractionForce); // 0.1 ~ 0.001 (faster, slower)
1315
+ // Ensure indexA is also within bounds (important if uOverallProgress is exactly 1.0)
1316
+ indexA = min(indexA, uNumMeshes - 1);
1317
+
1318
+
1319
+ float localProgress = fract(scaledProgress);
1320
+
1321
+ // Handle edge case where progress is exactly 1.0
1322
+ if (uOverallProgress == 1.0) {
1323
+ indexA = uNumMeshes - 1;
1324
+ indexB = uNumMeshes - 1;
1325
+ localProgress = 1.0; // or 0.0 depending on how you want to handle it
1326
+ }
1327
+
1328
+
1329
+ vec3 positionA = getAtlasPosition(uv, indexA);
1330
+ vec3 positionB = getAtlasPosition(uv, indexB);
1331
+
1332
+ targetPosition = mix(positionA, positionB, localProgress);
1333
+ }
1334
+ // --- End Target Position Calculation ---
1335
+
1336
+ // Particle attraction to target position
1337
+ vec3 direction = normalize(targetPosition - currentPosition);
1338
+ float dist = length(targetPosition - currentPosition);
1339
+
1340
+ vec3 finalPosition = currentPosition;
1341
+
1342
+ // Apply attraction force (simplified mix)
1343
+ if (dist > 0.01) { // Only apply if significantly far
1344
+ finalPosition = mix(currentPosition, targetPosition, 0.1 * uTractionForce);
1129
1345
  }
1130
1346
 
1131
- position += velocity;
1132
- gl_FragColor = vec4(position, 1.0);
1347
+ finalPosition += currentVelocity;
1348
+ gl_FragColor = vec4(finalPosition, 1.0);
1133
1349
  }
1134
1350
  `;
1135
1351
  const velocityShader = `
1136
- uniform float uProgress;
1137
1352
  uniform vec4 uInteractionPosition;
1138
1353
  uniform float uTime;
1139
1354
  uniform float uTractionForce;
1140
1355
  uniform float uMaxRepelDistance;
1356
+ uniform sampler2D uPositionAtlas;
1357
+ uniform float uOverallProgress;
1358
+ uniform int uNumMeshes;
1359
+ uniform float uSingleTextureSize;
1141
1360
 
1142
1361
  float rand(vec2 co) {
1143
1362
  return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
1144
1363
  }
1145
1364
 
1365
+ // Helper function (same as in position shader)
1366
+ vec3 getAtlasPosition(vec2 uv, int meshIndex) {
1367
+ float atlasWidth = uSingleTextureSize * float(uNumMeshes);
1368
+ float atlasHeight = uSingleTextureSize;
1369
+ float segmentWidthRatio = uSingleTextureSize / atlasWidth;
1370
+ vec2 atlasUV = vec2(uv.x * segmentWidthRatio + segmentWidthRatio * float(meshIndex), uv.y);
1371
+ return texture2D(uPositionAtlas, atlasUV).xyz;
1372
+ }
1373
+
1146
1374
  void main() {
1147
- vec2 uv = gl_FragCoord.xy / resolution.xy;
1375
+ vec2 uv = gl_FragCoord.xy / resolution.xy;
1148
1376
  float offset = rand(uv);
1149
1377
 
1150
- vec3 position = texture2D(uCurrentPosition, uv).xyz;
1151
- vec3 velocity = texture2D(uCurrentVelocity, uv).xyz;
1152
- vec3 mixedPosition = texture2D(uMixedPosition, uv).xyz;
1378
+ vec3 currentPosition = texture2D(uCurrentPosition, uv).xyz;
1379
+ vec3 currentVelocity = texture2D(uCurrentVelocity, uv).xyz;
1380
+
1381
+ // --- Calculate Target Position from Atlas (same logic as position shader) ---
1382
+ vec3 targetPosition;
1383
+ if (uNumMeshes <= 1) {
1384
+ targetPosition = getAtlasPosition(uv, 0);
1385
+ } else {
1386
+ float totalSegments = float(uNumMeshes - 1);
1387
+ float progressPerSegment = 1.0 / totalSegments;
1388
+ float scaledProgress = uOverallProgress * totalSegments;
1389
+ int indexA = int(floor(scaledProgress));
1390
+ int indexB = min(indexA + 1, uNumMeshes - 1);
1391
+ indexA = min(indexA, uNumMeshes - 1);
1392
+ float localProgress = fract(scaledProgress);
1393
+ if (uOverallProgress == 1.0) {
1394
+ indexA = uNumMeshes - 1;
1395
+ indexB = uNumMeshes - 1;
1396
+ localProgress = 1.0;
1397
+ }
1398
+ vec3 positionA = getAtlasPosition(uv, indexA);
1399
+ vec3 positionB = getAtlasPosition(uv, indexB);
1400
+ targetPosition = mix(positionA, positionB, localProgress);
1401
+ }
1402
+ // --- End Target Position Calculation ---
1153
1403
 
1154
- velocity *= 0.9;
1404
+ vec3 finalVelocity = currentVelocity * 0.9; // Dampening
1155
1405
 
1156
- // particle traction
1157
- vec3 direction = normalize(mixedPosition - position); // direction vector
1158
- float dist = length ( mixedPosition - position ); // distance from where it was supposed to be, and currently are.
1406
+ // Particle traction force towards target (influences velocity)
1407
+ vec3 direction = normalize(targetPosition - currentPosition);
1408
+ float dist = length(targetPosition - currentPosition);
1159
1409
  if (dist > 0.01) {
1160
- position += direction * 0.1 * uTractionForce; // uTractionForce defaults to 0.1
1410
+ // Add force proportional to distance and traction setting
1411
+ finalVelocity += direction * dist * 0.01 * uTractionForce; // Adjust multiplier as needed
1161
1412
  }
1162
1413
 
1163
- // mouse repel force
1164
- float pointerDistance = distance(position, uInteractionPosition.xyz);
1165
- float mouseRepelModifier = clamp(uMaxRepelDistance - pointerDistance, 0.0, 1.0);
1166
- float normalizedDistance = pointerDistance / uMaxRepelDistance;
1167
- float repulsionStrength = (1.0 - normalizedDistance) * uInteractionPosition.w;
1168
- direction = normalize(position - uInteractionPosition.xyz);
1169
- velocity += (direction * 0.01 * repulsionStrength) * mouseRepelModifier;
1414
+ // Mouse repel force
1415
+ if (uInteractionPosition.w > 0.0) { // Check if interaction is active (w component)
1416
+ float pointerDistance = distance(currentPosition, uInteractionPosition.xyz);
1417
+ if (pointerDistance < uMaxRepelDistance) {
1418
+ float mouseRepelModifier = smoothstep(uMaxRepelDistance, 0.0, pointerDistance); // Smoother falloff
1419
+ vec3 repelDirection = normalize(currentPosition - uInteractionPosition.xyz);
1420
+ // Apply force based on proximity and interaction strength (w)
1421
+ finalVelocity += repelDirection * mouseRepelModifier * uInteractionPosition.w * 0.01; // Adjust multiplier
1422
+ }
1423
+ }
1170
1424
 
1425
+ // Optional: Reset position if particle "dies" and respawns (lifespan logic)
1171
1426
  float lifespan = 20.0;
1172
- float age = mod(uTime + lifespan * offset, lifespan);
1173
-
1174
- if (age < 0.1) {
1175
- position.xyz = mixedPosition;
1427
+ float age = mod(uTime * 0.1 + lifespan * offset, lifespan); // Adjust time scale
1428
+ if (age < 0.05) { // Small window for reset
1429
+ finalVelocity = vec3(0.0); // Reset velocity on respawn
1430
+ // Note: Resetting position directly here might cause jumps.
1431
+ // It's often better handled in the position shader or by ensuring
1432
+ // strong attraction force when dist is large.
1176
1433
  }
1177
1434
 
1178
- gl_FragColor = vec4(velocity, 1.0);
1435
+
1436
+ gl_FragColor = vec4(finalVelocity, 1.0);
1179
1437
  }
1180
1438
  `;
1181
1439
  class SimulationRenderer {
1182
1440
  /**
1183
1441
  * Creates a new SimulationRenderer instance.
1184
- * @param size The size of the simulation textures.
1442
+ * @param size The size of the simulation textures (width/height).
1185
1443
  * @param webGLRenderer The WebGL renderer.
1186
- * @param initialPosition The initial position data texture. If not provided, a default sphere will be used.
1444
+ * @param initialPosition The initial position data texture (optional, defaults to sphere).
1187
1445
  */
1188
1446
  constructor(size, webGLRenderer, initialPosition) {
1189
1447
  __publicField(this, "gpuComputationRenderer");
1190
1448
  __publicField(this, "webGLRenderer");
1191
- // calculations
1192
- __publicField(this, "positionDataTexture");
1193
- __publicField(this, "velocityDataTexture");
1194
- // GPUComputationRenderer variables
1195
- __publicField(this, "mixPositionsVar");
1449
+ // GPGPU Variables
1196
1450
  __publicField(this, "velocityVar");
1197
1451
  __publicField(this, "positionVar");
1452
+ // Input Data Textures (References)
1453
+ __publicField(this, "initialPositionDataTexture");
1454
+ // Used only for first init
1455
+ __publicField(this, "initialVelocityDataTexture");
1456
+ // Blank texture for init
1457
+ __publicField(this, "positionAtlasTexture", null);
1458
+ // Holds the current mesh sequence atlas
1459
+ // Uniforms
1198
1460
  __publicField(this, "interactionPosition");
1461
+ // Cache last known output textures
1199
1462
  __publicField(this, "lastKnownPositionDataTexture");
1200
1463
  __publicField(this, "lastKnownVelocityDataTexture");
1201
- __publicField(this, "lastKnownMixProgress");
1202
- __publicField(this, "initialDataTexture");
1203
- this.initialDataTexture = initialPosition ?? createSpherePoints(size);
1204
- this.positionDataTexture = this.initialDataTexture;
1205
1464
  this.webGLRenderer = webGLRenderer;
1206
1465
  this.gpuComputationRenderer = new GPUComputationRenderer(size, size, this.webGLRenderer);
1207
- this.lastKnownMixProgress = 0;
1208
- if (!webGLRenderer.capabilities.isWebGL2) {
1466
+ if (!webGLRenderer.capabilities.isWebGL2 && webGLRenderer.extensions.get("OES_texture_float")) {
1467
+ this.gpuComputationRenderer.setDataType(THREE.FloatType);
1468
+ } else if (!webGLRenderer.capabilities.isWebGL2) {
1209
1469
  this.gpuComputationRenderer.setDataType(THREE.HalfFloatType);
1210
1470
  }
1211
- this.velocityDataTexture = createBlankDataTexture(size);
1471
+ this.initialPositionDataTexture = initialPosition ?? createSpherePoints(size);
1472
+ this.initialVelocityDataTexture = createBlankDataTexture(size);
1212
1473
  this.interactionPosition = new THREE.Vector4(0, 0, 0, 0);
1213
- this.mixPositionsVar = this.gpuComputationRenderer.addVariable("uMixedPosition", mixShader, this.positionDataTexture);
1214
- this.velocityVar = this.gpuComputationRenderer.addVariable("uCurrentVelocity", velocityShader, this.velocityDataTexture);
1215
- this.positionVar = this.gpuComputationRenderer.addVariable("uCurrentPosition", positionShader, this.positionDataTexture);
1216
- this.mixPositionsVar.material.uniforms.uProgress = { value: 0 };
1217
- this.mixPositionsVar.material.uniforms.uPositionA = { value: this.initialDataTexture };
1218
- this.mixPositionsVar.material.uniforms.uPositionB = { value: this.initialDataTexture };
1474
+ this.velocityVar = this.gpuComputationRenderer.addVariable("uCurrentVelocity", velocityShader, this.initialVelocityDataTexture);
1475
+ this.positionVar = this.gpuComputationRenderer.addVariable("uCurrentPosition", positionShader, this.initialPositionDataTexture);
1219
1476
  this.velocityVar.material.uniforms.uTime = { value: 0 };
1220
1477
  this.velocityVar.material.uniforms.uInteractionPosition = { value: this.interactionPosition };
1221
- this.velocityVar.material.uniforms.uCurrentPosition = { value: this.positionDataTexture };
1478
+ this.velocityVar.material.uniforms.uCurrentPosition = { value: null };
1222
1479
  this.velocityVar.material.uniforms.uTractionForce = { value: 0.1 };
1223
1480
  this.velocityVar.material.uniforms.uMaxRepelDistance = { value: 0.3 };
1481
+ this.velocityVar.material.uniforms.uPositionAtlas = { value: null };
1482
+ this.velocityVar.material.uniforms.uOverallProgress = { value: 0 };
1483
+ this.velocityVar.material.uniforms.uNumMeshes = { value: 1 };
1484
+ this.velocityVar.material.uniforms.uSingleTextureSize = { value: size };
1224
1485
  this.positionVar.material.uniforms.uTime = { value: 0 };
1225
- this.positionVar.material.uniforms.uProgress = { value: 0 };
1226
1486
  this.positionVar.material.uniforms.uTractionForce = { value: 0.1 };
1227
1487
  this.positionVar.material.uniforms.uInteractionPosition = { value: this.interactionPosition };
1228
- this.positionVar.material.uniforms.uCurrentPosition = { value: this.positionDataTexture };
1229
- this.gpuComputationRenderer.setVariableDependencies(this.positionVar, [this.velocityVar, this.positionVar, this.mixPositionsVar]);
1230
- this.gpuComputationRenderer.setVariableDependencies(this.velocityVar, [this.velocityVar, this.positionVar, this.mixPositionsVar]);
1231
- const err = this.gpuComputationRenderer.init();
1232
- if (err) {
1233
- throw new Error("failed to initialize SimulationRenderer: " + err);
1488
+ this.positionVar.material.uniforms.uCurrentPosition = { value: null };
1489
+ this.positionVar.material.uniforms.uCurrentVelocity = { value: null };
1490
+ this.positionVar.material.uniforms.uPositionAtlas = { value: null };
1491
+ this.positionVar.material.uniforms.uOverallProgress = { value: 0 };
1492
+ this.positionVar.material.uniforms.uNumMeshes = { value: 1 };
1493
+ this.positionVar.material.uniforms.uSingleTextureSize = { value: size };
1494
+ this.gpuComputationRenderer.setVariableDependencies(this.positionVar, [this.positionVar, this.velocityVar]);
1495
+ this.gpuComputationRenderer.setVariableDependencies(this.velocityVar, [this.velocityVar, this.positionVar]);
1496
+ const initError = this.gpuComputationRenderer.init();
1497
+ if (initError !== null) {
1498
+ throw new Error("Failed to initialize SimulationRenderer: " + initError);
1234
1499
  }
1235
- this.lastKnownVelocityDataTexture = this.getVelocityTexture();
1236
- this.lastKnownPositionDataTexture = this.getPositionTexture();
1500
+ this.positionVar.material.uniforms.uPositionAtlas.value = this.initialPositionDataTexture;
1501
+ this.positionVar.material.uniforms.uNumMeshes.value = 1;
1502
+ this.velocityVar.material.uniforms.uNumMeshes.value = 1;
1503
+ this.positionVar.material.uniforms.uSingleTextureSize.value = size;
1504
+ this.velocityVar.material.uniforms.uSingleTextureSize.value = size;
1505
+ this.positionVar.material.uniforms.uCurrentVelocity.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
1506
+ this.velocityVar.material.uniforms.uCurrentPosition.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
1507
+ this.lastKnownVelocityDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
1508
+ this.lastKnownPositionDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
1237
1509
  }
1238
1510
  /**
1239
- * Sets the source data texture for morphing.
1240
- * @param texture The source data texture.
1511
+ * Sets the mesh sequence position atlas texture and related uniforms.
1512
+ * @param entry Information about the atlas texture.
1241
1513
  */
1242
- setMorphSourceDataTexture(texture) {
1243
- this.mixPositionsVar.material.uniforms.uPositionA.value = texture;
1514
+ setPositionAtlas(entry) {
1515
+ const expectedAtlasWidth = entry.singleTextureSize * entry.numMeshes;
1516
+ if (entry.dataTexture.image.width !== expectedAtlasWidth || entry.dataTexture.image.height !== entry.singleTextureSize) {
1517
+ console.error(
1518
+ `SimulationRenderer: Atlas texture dimension mismatch! Expected ${expectedAtlasWidth}x${entry.singleTextureSize}, Got ${entry.dataTexture.image.width}x${entry.dataTexture.image.height}`
1519
+ );
1520
+ }
1521
+ this.positionAtlasTexture = entry.dataTexture;
1522
+ const numMeshes = entry.numMeshes > 0 ? entry.numMeshes : 1;
1523
+ this.positionVar.material.uniforms.uPositionAtlas.value = this.positionAtlasTexture;
1524
+ this.positionVar.material.uniforms.uNumMeshes.value = numMeshes;
1525
+ this.positionVar.material.uniforms.uSingleTextureSize.value = entry.singleTextureSize;
1526
+ this.velocityVar.material.uniforms.uPositionAtlas.value = this.positionAtlasTexture;
1527
+ this.velocityVar.material.uniforms.uNumMeshes.value = numMeshes;
1528
+ this.velocityVar.material.uniforms.uSingleTextureSize.value = entry.singleTextureSize;
1529
+ this.positionVar.material.uniforms.uCurrentVelocity.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
1530
+ this.velocityVar.material.uniforms.uCurrentPosition.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
1244
1531
  }
1245
1532
  /**
1246
- * Sets the destination data texture for morphing.
1247
- * @param texture The destination data texture.
1533
+ * Sets the overall progress for blending between meshes in the atlas.
1534
+ * @param progress Value between 0.0 and 1.0.
1248
1535
  */
1249
- setMorphDestinationDataTexture(texture) {
1250
- this.mixPositionsVar.material.uniforms.uPositionB.value = texture;
1536
+ setOverallProgress(progress) {
1537
+ const clampedProgress = clamp(progress, 0, 1);
1538
+ this.positionVar.material.uniforms.uOverallProgress.value = clampedProgress;
1539
+ this.velocityVar.material.uniforms.uOverallProgress.value = clampedProgress;
1251
1540
  }
1252
1541
  setMaxRepelDistance(distance) {
1253
1542
  this.velocityVar.material.uniforms.uMaxRepelDistance.value = distance;
1254
1543
  }
1255
- /**
1256
- * Sets the progress of the morphing animation.
1257
- * @param progress The progress value, between 0 and 1.
1258
- */
1259
- setProgress(progress) {
1260
- this.lastKnownMixProgress = clamp(progress, 0, 1);
1261
- this.mixPositionsVar.material.uniforms.uProgress.value = this.lastKnownMixProgress;
1262
- }
1263
1544
  setVelocityTractionForce(force) {
1264
1545
  this.velocityVar.material.uniforms.uTractionForce.value = force;
1265
1546
  }
@@ -1273,36 +1554,31 @@ class SimulationRenderer {
1273
1554
  * Disposes the resources used by the simulation renderer.
1274
1555
  */
1275
1556
  dispose() {
1276
- this.mixPositionsVar.renderTargets.forEach((rtt) => rtt.dispose());
1277
- this.positionVar.renderTargets.forEach((rtt) => rtt.dispose());
1278
- this.velocityVar.renderTargets.forEach((rtt) => rtt.dispose());
1279
- this.positionDataTexture.dispose();
1280
- this.velocityDataTexture.dispose();
1557
+ var _a, _b;
1281
1558
  this.gpuComputationRenderer.dispose();
1559
+ (_a = this.initialPositionDataTexture) == null ? void 0 : _a.dispose();
1560
+ (_b = this.initialVelocityDataTexture) == null ? void 0 : _b.dispose();
1561
+ this.positionAtlasTexture = null;
1282
1562
  }
1283
1563
  /**
1284
1564
  * Computes the next step of the simulation.
1285
- * @param elapsedTime The elapsed time since the simulation started.
1565
+ * @param deltaTime The time elapsed since the last frame, in seconds.
1286
1566
  */
1287
- compute(elapsedTime) {
1288
- this.velocityVar.material.uniforms.uTime.value = elapsedTime;
1289
- this.positionVar.material.uniforms.uTime.value = elapsedTime;
1567
+ compute(deltaTime) {
1568
+ this.velocityVar.material.uniforms.uTime.value += deltaTime;
1569
+ this.positionVar.material.uniforms.uTime.value += deltaTime;
1570
+ this.positionVar.material.uniforms.uCurrentVelocity.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
1571
+ this.velocityVar.material.uniforms.uCurrentPosition.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
1290
1572
  this.gpuComputationRenderer.compute();
1573
+ this.lastKnownVelocityDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
1574
+ this.lastKnownPositionDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
1291
1575
  }
1292
- /**
1293
- * Gets the current velocity texture.
1294
- * @returns The current velocity texture.
1295
- */
1576
+ /** Gets the current velocity texture (output from the last compute step). */
1296
1577
  getVelocityTexture() {
1297
- this.lastKnownVelocityDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
1298
1578
  return this.lastKnownVelocityDataTexture;
1299
1579
  }
1300
- /**
1301
- * Gets the current position texture.
1302
- * @returns The current position texture.
1303
- */
1580
+ /** Gets the current position texture (output from the last compute step). */
1304
1581
  getPositionTexture() {
1305
- this.lastKnownPositionDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
1306
1582
  return this.lastKnownPositionDataTexture;
1307
1583
  }
1308
1584
  }
@@ -1310,18 +1586,22 @@ class SimulationRendererService {
1310
1586
  constructor(eventEmitter, size, webGLRenderer) {
1311
1587
  __publicField(this, "state");
1312
1588
  __publicField(this, "textureSize");
1313
- __publicField(this, "dataTextureTransitionProgress");
1589
+ __publicField(this, "overallProgress");
1590
+ // ADDED: Store overall progress
1314
1591
  __publicField(this, "velocityTractionForce");
1315
1592
  __publicField(this, "positionalTractionForce");
1316
1593
  __publicField(this, "simulationRenderer");
1317
1594
  __publicField(this, "webGLRenderer");
1318
1595
  __publicField(this, "eventEmitter");
1596
+ // Store atlas info
1597
+ __publicField(this, "currentAtlasEntry", null);
1598
+ // ADDED
1319
1599
  __publicField(this, "lastKnownVelocityDataTexture");
1320
1600
  __publicField(this, "lastKnownPositionDataTexture");
1321
1601
  this.eventEmitter = eventEmitter;
1322
1602
  this.webGLRenderer = webGLRenderer;
1323
1603
  this.textureSize = size;
1324
- this.dataTextureTransitionProgress = 0;
1604
+ this.overallProgress = 0;
1325
1605
  this.velocityTractionForce = 0.1;
1326
1606
  this.positionalTractionForce = 0.1;
1327
1607
  this.updateServiceState("initializing");
@@ -1330,6 +1610,27 @@ class SimulationRendererService {
1330
1610
  this.lastKnownPositionDataTexture = this.simulationRenderer.getPositionTexture();
1331
1611
  this.updateServiceState("ready");
1332
1612
  }
1613
+ /**
1614
+ * Sets the position data texture atlas for the simulation.
1615
+ * @param entry An object containing the atlas texture and related parameters.
1616
+ */
1617
+ setPositionAtlas(entry) {
1618
+ const expectedWidth = entry.singleTextureSize * entry.numMeshes;
1619
+ if (entry.dataTexture.image.width !== expectedWidth) {
1620
+ this.eventEmitter.emit("invalidRequest", { message: `Atlas texture width mismatch.` });
1621
+ return;
1622
+ }
1623
+ this.currentAtlasEntry = entry;
1624
+ this.simulationRenderer.setPositionAtlas(entry);
1625
+ }
1626
+ /**
1627
+ * Sets the overall progress for the mesh sequence transition.
1628
+ * @param progress The progress value (0.0 to 1.0).
1629
+ */
1630
+ setOverallProgress(progress) {
1631
+ this.overallProgress = progress;
1632
+ this.simulationRenderer.setOverallProgress(this.overallProgress);
1633
+ }
1333
1634
  setTextureSize(size) {
1334
1635
  this.updateServiceState("initializing");
1335
1636
  this.simulationRenderer.dispose();
@@ -1337,24 +1638,6 @@ class SimulationRendererService {
1337
1638
  this.simulationRenderer = new SimulationRenderer(size, this.webGLRenderer);
1338
1639
  this.updateServiceState("ready");
1339
1640
  }
1340
- setOriginDataTexture(entry) {
1341
- if (this.textureSize !== entry.textureSize) {
1342
- this.eventEmitter.emit("invalidRequest", { message: `Texture size mismatch: ${entry.textureSize} vs ${this.textureSize}` });
1343
- } else {
1344
- this.simulationRenderer.setMorphSourceDataTexture(entry.dataTexture);
1345
- }
1346
- }
1347
- setDestinationDataTexture(entry) {
1348
- if (this.textureSize !== entry.textureSize) {
1349
- this.eventEmitter.emit("invalidRequest", { message: `Texture size mismatch: ${entry.textureSize} vs ${this.textureSize}` });
1350
- } else {
1351
- this.simulationRenderer.setMorphDestinationDataTexture(entry.dataTexture);
1352
- }
1353
- }
1354
- setDataTextureTransitionProgress(progress) {
1355
- this.dataTextureTransitionProgress = progress;
1356
- this.simulationRenderer.setProgress(this.dataTextureTransitionProgress);
1357
- }
1358
1641
  setVelocityTractionForce(force) {
1359
1642
  this.velocityTractionForce = force;
1360
1643
  this.simulationRenderer.setVelocityTractionForce(this.velocityTractionForce);
@@ -1364,21 +1647,21 @@ class SimulationRendererService {
1364
1647
  this.simulationRenderer.setPositionalTractionForce(this.positionalTractionForce);
1365
1648
  }
1366
1649
  compute(elapsedTime) {
1650
+ if (this.state !== "ready") return;
1367
1651
  this.simulationRenderer.compute(elapsedTime);
1652
+ this.lastKnownVelocityDataTexture = this.simulationRenderer.getVelocityTexture();
1653
+ this.lastKnownPositionDataTexture = this.simulationRenderer.getPositionTexture();
1368
1654
  }
1369
1655
  getVelocityTexture() {
1370
- if (this.state === "ready") this.lastKnownVelocityDataTexture = this.simulationRenderer.getVelocityTexture();
1371
1656
  return this.lastKnownVelocityDataTexture;
1372
1657
  }
1373
1658
  getPositionTexture() {
1374
- if (this.state === "ready") this.lastKnownPositionDataTexture = this.simulationRenderer.getPositionTexture();
1375
1659
  return this.lastKnownPositionDataTexture;
1376
1660
  }
1377
1661
  dispose() {
1378
1662
  this.updateServiceState("disposed");
1379
1663
  this.simulationRenderer.dispose();
1380
- this.lastKnownVelocityDataTexture.dispose();
1381
- this.lastKnownPositionDataTexture.dispose();
1664
+ this.currentAtlasEntry = null;
1382
1665
  }
1383
1666
  updateServiceState(serviceState) {
1384
1667
  this.state = serviceState;
@@ -1495,7 +1778,6 @@ class ParticlesEngine {
1495
1778
  */
1496
1779
  constructor(params) {
1497
1780
  __publicField(this, "simulationRendererService");
1498
- __publicField(this, "eventEmitter");
1499
1781
  __publicField(this, "renderer");
1500
1782
  __publicField(this, "scene");
1501
1783
  __publicField(this, "serviceStates");
@@ -1506,6 +1788,9 @@ class ParticlesEngine {
1506
1788
  __publicField(this, "transitionService");
1507
1789
  __publicField(this, "engineState");
1508
1790
  __publicField(this, "intersectionService");
1791
+ __publicField(this, "meshSequenceAtlasTexture", null);
1792
+ // ADDED: To store the generated atlas
1793
+ __publicField(this, "eventEmitter");
1509
1794
  const { scene, renderer, camera, textureSize, useIntersection = true } = params;
1510
1795
  this.eventEmitter = new DefaultEventEmitter();
1511
1796
  this.serviceStates = this.getInitialServiceStates();
@@ -1522,7 +1807,6 @@ class ParticlesEngine {
1522
1807
  this.scene.add(this.instancedMeshManager.getMesh());
1523
1808
  this.intersectionService = new IntersectionService(this.eventEmitter, camera);
1524
1809
  if (!useIntersection) this.intersectionService.setActive(false);
1525
- this.eventEmitter.on("transitionProgressed", this.handleTransitionProgress.bind(this));
1526
1810
  this.eventEmitter.on("interactionPositionUpdated", this.handleInteractionPositionUpdated.bind(this));
1527
1811
  }
1528
1812
  /**
@@ -1530,48 +1814,14 @@ class ParticlesEngine {
1530
1814
  * @param elapsedTime The elapsed time since the last frame.
1531
1815
  */
1532
1816
  render(elapsedTime) {
1817
+ const dt = elapsedTime / 1e3;
1818
+ this.transitionService.compute(dt);
1533
1819
  this.intersectionService.calculate(this.instancedMeshManager.getMesh());
1534
- this.transitionService.compute(elapsedTime);
1535
- this.simulationRendererService.compute(elapsedTime);
1536
- this.instancedMeshManager.update(elapsedTime);
1820
+ this.simulationRendererService.compute(dt);
1821
+ this.instancedMeshManager.update(dt);
1537
1822
  this.instancedMeshManager.updateVelocityTexture(this.simulationRendererService.getVelocityTexture());
1538
1823
  this.instancedMeshManager.updatePositionTexture(this.simulationRendererService.getPositionTexture());
1539
1824
  }
1540
- setOriginDataTexture(meshID, override = false) {
1541
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "data-texture" });
1542
- const mesh = this.assetService.getMesh(meshID);
1543
- if (!mesh) {
1544
- this.eventEmitter.emit("invalidRequest", { message: `Mesh with id "${meshID}" does not exist` });
1545
- return;
1546
- }
1547
- this.dataTextureManager.getDataTexture(mesh).then((dataTexture) => {
1548
- this.engineState.originMeshID = meshID;
1549
- this.simulationRendererService.setOriginDataTexture({ dataTexture, textureSize: this.engineState.textureSize });
1550
- this.intersectionService.setOriginGeometry(mesh);
1551
- });
1552
- }
1553
- setDestinationDataTexture(meshID, override = false) {
1554
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "data-texture" });
1555
- const mesh = this.assetService.getMesh(meshID);
1556
- if (!mesh) {
1557
- this.eventEmitter.emit("invalidRequest", { message: `Mesh with id "${meshID}" does not exist` });
1558
- return;
1559
- }
1560
- this.dataTextureManager.getDataTexture(mesh).then((texture) => {
1561
- this.engineState.destinationMeshID = meshID;
1562
- this.simulationRendererService.setDestinationDataTexture({
1563
- dataTexture: texture,
1564
- textureSize: this.engineState.textureSize
1565
- });
1566
- this.intersectionService.setDestinationGeometry(mesh);
1567
- });
1568
- }
1569
- setDataTextureTransitionProgress(progress, override = false) {
1570
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "data-texture" });
1571
- this.engineState.dataTextureTransitionProgress = progress;
1572
- this.simulationRendererService.setDataTextureTransitionProgress(progress);
1573
- this.intersectionService.setProgress(progress);
1574
- }
1575
1825
  setOriginMatcap(matcapID, override = false) {
1576
1826
  if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1577
1827
  this.engineState.originMatcapID = matcapID;
@@ -1607,40 +1857,31 @@ class ParticlesEngine {
1607
1857
  }
1608
1858
  }
1609
1859
  setMatcapProgress(progress, override = false) {
1860
+ const clampedProgress = clamp(progress, 0, 1);
1610
1861
  if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1611
- this.engineState.matcapTransitionProgress = progress;
1612
- this.instancedMeshManager.setProgress(progress);
1862
+ this.engineState.matcapTransitionProgress = clampedProgress;
1863
+ this.instancedMeshManager.setProgress(clampedProgress);
1613
1864
  }
1865
+ // --- Update setTextureSize ---
1614
1866
  async setTextureSize(size) {
1867
+ if (this.engineState.textureSize === size) {
1868
+ return;
1869
+ }
1615
1870
  this.engineState.textureSize = size;
1616
1871
  this.dataTextureManager.setTextureSize(size);
1617
1872
  this.simulationRendererService.setTextureSize(size);
1618
1873
  this.instancedMeshManager.resize(size);
1619
- const originMesh = this.assetService.getMesh(this.engineState.originMeshID);
1620
- if (!originMesh) {
1621
- this.eventEmitter.emit("invalidRequest", { message: `Mesh with id "${this.engineState.originMeshID}" does not exist` });
1622
- return;
1874
+ if (this.engineState.meshSequence.length > 0) {
1875
+ await this.setMeshSequence(this.engineState.meshSequence);
1623
1876
  }
1624
- const destinationMesh = this.assetService.getMesh(this.engineState.destinationMeshID);
1625
- if (!destinationMesh) {
1626
- this.eventEmitter.emit("invalidRequest", { message: `Mesh with id "${this.engineState.destinationMeshID}" does not exist` });
1627
- return;
1877
+ if (this.engineState.meshSequence.length > 0) {
1878
+ await this.setMeshSequence(this.engineState.meshSequence);
1628
1879
  }
1629
- this.dataTextureManager.getDataTexture(originMesh).then(
1630
- (texture) => this.simulationRendererService.setOriginDataTexture({
1631
- dataTexture: texture,
1632
- textureSize: size
1633
- })
1634
- );
1635
- this.dataTextureManager.getDataTexture(destinationMesh).then(
1636
- (texture) => this.simulationRendererService.setDestinationDataTexture({
1637
- dataTexture: texture,
1638
- textureSize: size
1639
- })
1640
- );
1641
- this.simulationRendererService.setDataTextureTransitionProgress(this.engineState.dataTextureTransitionProgress);
1642
1880
  this.simulationRendererService.setVelocityTractionForce(this.engineState.velocityTractionForce);
1643
1881
  this.simulationRendererService.setPositionalTractionForce(this.engineState.positionalTractionForce);
1882
+ this.simulationRendererService.setMaxRepelDistance(this.engineState.maxRepelDistance);
1883
+ this.simulationRendererService.setOverallProgress(this.engineState.overallProgress);
1884
+ this.intersectionService.setOverallProgress(this.engineState.overallProgress);
1644
1885
  this.instancedMeshManager.setOriginMatcap(this.assetService.getMatcap(this.engineState.originMatcapID));
1645
1886
  this.instancedMeshManager.setDestinationMatcap(this.assetService.getMatcap(this.engineState.destinationMatcapID));
1646
1887
  this.instancedMeshManager.setProgress(this.engineState.matcapTransitionProgress);
@@ -1663,7 +1904,7 @@ class ParticlesEngine {
1663
1904
  this.engineState.useIntersect = use;
1664
1905
  if (!use) {
1665
1906
  this.engineState.pointerPosition = { x: -99999999, y: -99999999 };
1666
- this.intersectionService.setPointerPosition(this.engineState.pointerPosition);
1907
+ this.simulationRendererService.setInteractionPosition({ x: 0, y: 0, z: 0, w: 0 });
1667
1908
  }
1668
1909
  }
1669
1910
  setPointerPosition(position) {
@@ -1687,50 +1928,170 @@ class ParticlesEngine {
1687
1928
  this.engineState.maxRepelDistance = distance;
1688
1929
  this.simulationRendererService.setMaxRepelDistance(distance);
1689
1930
  }
1690
- scheduleMeshTransition(originMeshID, destinationMeshID, easing = linear, duration = 1e3, override = false) {
1691
- this.transitionService.enqueue(
1692
- "data-texture",
1693
- { easing, duration },
1694
- {
1695
- onTransitionBegin: () => {
1696
- this.setOriginDataTexture(originMeshID, override);
1697
- this.setDestinationDataTexture(destinationMeshID, override);
1698
- this.setDataTextureTransitionProgress(0);
1699
- }
1931
+ /**
1932
+ * Sets the sequence of meshes for particle transitions.
1933
+ * This will generate a texture atlas containing position data for all meshes.
1934
+ * @param meshIDs An array of registered mesh IDs in the desired sequence order.
1935
+ */
1936
+ async setMeshSequence(meshIDs) {
1937
+ if (!meshIDs || meshIDs.length < 1) {
1938
+ this.eventEmitter.emit("invalidRequest", { message: "Mesh sequence must contain at least one mesh ID." });
1939
+ this.engineState.meshSequence = [];
1940
+ this.intersectionService.setMeshSequence([]);
1941
+ return;
1942
+ }
1943
+ this.engineState.meshSequence = meshIDs;
1944
+ this.engineState.overallProgress = 0;
1945
+ const meshes = meshIDs.map((id) => this.assetService.getMesh(id)).filter((mesh) => mesh !== null);
1946
+ if (meshes.length !== meshIDs.length) {
1947
+ const missing = meshIDs.filter((id) => !this.assetService.getMesh(id));
1948
+ console.warn(`Could not find meshes for IDs: ${missing.join(", ")}. Proceeding with ${meshes.length} found meshes.`);
1949
+ this.eventEmitter.emit("invalidRequest", { message: `Could not find meshes for IDs: ${missing.join(", ")}` });
1950
+ if (meshes.length < 1) {
1951
+ this.engineState.meshSequence = [];
1952
+ this.intersectionService.setMeshSequence([]);
1953
+ return;
1700
1954
  }
1701
- );
1955
+ this.engineState.meshSequence = meshes.map((m) => m.name);
1956
+ }
1957
+ try {
1958
+ this.meshSequenceAtlasTexture = await this.dataTextureManager.createSequenceDataTextureAtlas(meshes, this.engineState.textureSize);
1959
+ this.simulationRendererService.setPositionAtlas({
1960
+ dataTexture: this.meshSequenceAtlasTexture,
1961
+ textureSize: this.engineState.textureSize,
1962
+ // Pass the size of the *output* GPGPU texture
1963
+ numMeshes: this.engineState.meshSequence.length,
1964
+ // Use the potentially updated count
1965
+ singleTextureSize: this.engineState.textureSize
1966
+ // Size of one mesh's data within atlas
1967
+ });
1968
+ this.simulationRendererService.setOverallProgress(this.engineState.overallProgress);
1969
+ this.intersectionService.setMeshSequence(meshes);
1970
+ this.intersectionService.setOverallProgress(this.engineState.overallProgress);
1971
+ } catch (error) {
1972
+ console.error("Failed during mesh sequence setup:", error);
1973
+ this.meshSequenceAtlasTexture = null;
1974
+ }
1975
+ }
1976
+ /**
1977
+ * Sets the overall progress through the mesh sequence.
1978
+ * @param progress A value between 0.0 (first mesh) and 1.0 (last mesh).
1979
+ * @param override If true, cancels any ongoing mesh sequence transition before setting the value. Defaults to true.
1980
+ */
1981
+ setOverallProgress(progress, override = true) {
1982
+ if (override) {
1983
+ this.eventEmitter.emit("transitionCancelled", { type: "mesh-sequence" });
1984
+ }
1985
+ const clampedProgress = clamp(progress, 0, 1);
1986
+ this.engineState.overallProgress = clampedProgress;
1987
+ this.simulationRendererService.setOverallProgress(clampedProgress);
1988
+ this.intersectionService.setOverallProgress(clampedProgress);
1702
1989
  }
1703
- scheduleMatcapTransition(originMatcapID, destinationMatcapID, easing = linear, duration = 1e3, override = false) {
1990
+ // --- Transition scheduling methods remain the same ---
1991
+ scheduleMatcapTransition(originMatcapID, destinationMatcapID, easing = linear, duration = 1e3, override = false, options = {}) {
1992
+ if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1993
+ const handleProgressUpdate = (transitionProgress) => {
1994
+ var _a;
1995
+ this.setMatcapProgress(transitionProgress, false);
1996
+ (_a = options.onTransitionProgress) == null ? void 0 : _a.call(options, transitionProgress);
1997
+ };
1704
1998
  this.transitionService.enqueue(
1705
1999
  "matcap",
1706
2000
  { easing, duration },
1707
2001
  {
2002
+ ...options,
2003
+ onTransitionProgress: handleProgressUpdate,
1708
2004
  onTransitionBegin: () => {
1709
- this.setOriginMatcap(originMatcapID, override);
1710
- this.setDestinationMatcap(destinationMatcapID, override);
1711
- this.setMatcapProgress(0);
1712
- }
2005
+ var _a;
2006
+ this.setOriginMatcap(originMatcapID, false);
2007
+ this.setDestinationMatcap(destinationMatcapID, false);
2008
+ this.setMatcapProgress(0, false);
2009
+ (_a = options.onTransitionBegin) == null ? void 0 : _a.call(options);
2010
+ },
2011
+ onTransitionFinished: () => {
2012
+ var _a;
2013
+ this.setMatcapProgress(1, false);
2014
+ (_a = options.onTransitionFinished) == null ? void 0 : _a.call(options);
2015
+ },
2016
+ onTransitionCancelled: options.onTransitionCancelled
1713
2017
  }
1714
2018
  );
1715
2019
  }
1716
- scheduleTextureTransition(origin, destination, options) {
2020
+ scheduleTextureTransition(origin, destination, options = {}) {
1717
2021
  const easing = (options == null ? void 0 : options.easing) ?? linear;
1718
2022
  const duration = (options == null ? void 0 : options.duration) ?? 1e3;
1719
- if (options == null ? void 0 : options.override) {
1720
- this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1721
- }
2023
+ const userCallbacks = {
2024
+ // Extract user callbacks
2025
+ onTransitionBegin: options == null ? void 0 : options.onTransitionBegin,
2026
+ onTransitionProgress: options == null ? void 0 : options.onTransitionProgress,
2027
+ onTransitionFinished: options == null ? void 0 : options.onTransitionFinished,
2028
+ onTransitionCancelled: options == null ? void 0 : options.onTransitionCancelled
2029
+ };
2030
+ if (options == null ? void 0 : options.override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
2031
+ const handleProgressUpdate = (transitionProgress) => {
2032
+ var _a;
2033
+ this.setMatcapProgress(transitionProgress, false);
2034
+ (_a = userCallbacks.onTransitionProgress) == null ? void 0 : _a.call(userCallbacks, transitionProgress);
2035
+ };
1722
2036
  this.transitionService.enqueue(
1723
2037
  "matcap",
2038
+ // Still uses matcap type internally
1724
2039
  { easing, duration },
1725
2040
  {
2041
+ ...userCallbacks,
2042
+ // Pass user callbacks
2043
+ onTransitionProgress: handleProgressUpdate,
1726
2044
  onTransitionBegin: () => {
2045
+ var _a;
1727
2046
  this.setOriginTexture(origin);
1728
2047
  this.setDestinationTexture(destination);
1729
2048
  this.setMatcapProgress(0);
2049
+ (_a = userCallbacks.onTransitionBegin) == null ? void 0 : _a.call(userCallbacks);
2050
+ },
2051
+ onTransitionFinished: () => {
2052
+ var _a;
2053
+ this.setMatcapProgress(1);
2054
+ (_a = userCallbacks.onTransitionFinished) == null ? void 0 : _a.call(userCallbacks);
2055
+ },
2056
+ onTransitionCancelled: () => {
2057
+ var _a;
2058
+ (_a = userCallbacks.onTransitionCancelled) == null ? void 0 : _a.call(userCallbacks);
1730
2059
  }
1731
2060
  }
1732
2061
  );
1733
2062
  }
2063
+ /**
2064
+ * Schedules a smooth transition for the overall mesh sequence progress.
2065
+ * @param targetProgress The final progress value (0.0 to 1.0) to transition to.
2066
+ * @param duration Duration of the transition in milliseconds.
2067
+ * @param easing Easing function to use.
2068
+ * @param options Transition options (onBegin, onProgress, onFinished, onCancelled).
2069
+ * @param override If true, cancels any ongoing mesh sequence transitions.
2070
+ */
2071
+ scheduleMeshSequenceTransition(targetProgress, duration = 1e3, easing = linear, options = {}, override = true) {
2072
+ if (override) this.eventEmitter.emit("transitionCancelled", { type: "mesh-sequence" });
2073
+ const startProgress = this.engineState.overallProgress;
2074
+ const progressDiff = targetProgress - startProgress;
2075
+ const handleProgressUpdate = (transitionProgress) => {
2076
+ var _a;
2077
+ const currentOverallProgress = startProgress + progressDiff * transitionProgress;
2078
+ this.setOverallProgress(currentOverallProgress, false);
2079
+ (_a = options.onTransitionProgress) == null ? void 0 : _a.call(options, currentOverallProgress);
2080
+ };
2081
+ const transitionDetail = { duration, easing };
2082
+ const transitionOptions = {
2083
+ ...options,
2084
+ onTransitionProgress: handleProgressUpdate,
2085
+ onTransitionBegin: options.onTransitionBegin,
2086
+ onTransitionFinished: () => {
2087
+ var _a;
2088
+ this.setOverallProgress(targetProgress, false);
2089
+ (_a = options.onTransitionFinished) == null ? void 0 : _a.call(options);
2090
+ },
2091
+ onTransitionCancelled: options.onTransitionCancelled
2092
+ };
2093
+ this.transitionService.enqueue("mesh-sequence", transitionDetail, transitionOptions);
2094
+ }
1734
2095
  handleServiceStateUpdated({ type, state }) {
1735
2096
  this.serviceStates[type] = state;
1736
2097
  }
@@ -1749,23 +2110,37 @@ class ParticlesEngine {
1749
2110
  getTextures() {
1750
2111
  return this.assetService.getTextures();
1751
2112
  }
2113
+ getTextureSize() {
2114
+ return this.engineState.textureSize;
2115
+ }
2116
+ getUseIntersect() {
2117
+ return this.engineState.useIntersect;
2118
+ }
2119
+ getEngineStateSnapshot() {
2120
+ return { ...this.engineState };
2121
+ }
1752
2122
  /**
1753
2123
  * Disposes the resources used by the engine.
1754
2124
  */
1755
2125
  dispose() {
1756
- this.scene.remove(this.instancedMeshManager.getMesh());
1757
- this.simulationRendererService.dispose();
1758
- this.instancedMeshManager.dispose();
1759
- this.intersectionService.dispose();
1760
- this.assetService.dispose();
1761
- this.dataTextureManager.dispose();
2126
+ var _a, _b, _c, _d, _e, _f;
2127
+ if (this.scene && this.instancedMeshManager) {
2128
+ this.scene.remove(this.instancedMeshManager.getMesh());
2129
+ }
2130
+ (_a = this.simulationRendererService) == null ? void 0 : _a.dispose();
2131
+ (_b = this.instancedMeshManager) == null ? void 0 : _b.dispose();
2132
+ (_c = this.intersectionService) == null ? void 0 : _c.dispose();
2133
+ (_d = this.assetService) == null ? void 0 : _d.dispose();
2134
+ (_e = this.dataTextureManager) == null ? void 0 : _e.dispose();
2135
+ (_f = this.eventEmitter) == null ? void 0 : _f.dispose();
1762
2136
  }
1763
2137
  initialEngineState(params) {
1764
2138
  return {
1765
2139
  textureSize: params.textureSize,
1766
- originMeshID: "",
1767
- destinationMeshID: "",
1768
- dataTextureTransitionProgress: 0,
2140
+ meshSequence: [],
2141
+ // ADDED
2142
+ overallProgress: 0,
2143
+ // ADDED
1769
2144
  originMatcapID: "",
1770
2145
  destinationMatcapID: "",
1771
2146
  matcapTransitionProgress: 0,
@@ -1786,16 +2161,6 @@ class ParticlesEngine {
1786
2161
  asset: "created"
1787
2162
  };
1788
2163
  }
1789
- handleTransitionProgress({ type, progress }) {
1790
- switch (type) {
1791
- case "data-texture":
1792
- this.setDataTextureTransitionProgress(progress);
1793
- break;
1794
- case "matcap":
1795
- this.setMatcapProgress(progress);
1796
- break;
1797
- }
1798
- }
1799
2164
  handleInteractionPositionUpdated({ position }) {
1800
2165
  this.simulationRendererService.setInteractionPosition(position);
1801
2166
  }