@polarfront-lab/ionian 1.5.0 → 1.7.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/index.d.ts +118 -60
- package/dist/ionian.iife.js +1 -1
- package/dist/ionian.iife.js.map +1 -1
- package/dist/ionian.js +620 -310
- package/dist/ionian.js.map +1 -1
- package/package.json +3 -3
package/dist/ionian.js
CHANGED
|
@@ -334,6 +334,7 @@ class MeshSurfaceSampler {
|
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
336
|
class DataTextureService {
|
|
337
|
+
// Cache the current atlas
|
|
337
338
|
/**
|
|
338
339
|
* Creates a new DataTextureManager instance.
|
|
339
340
|
* @param eventEmitter
|
|
@@ -343,6 +344,7 @@ class DataTextureService {
|
|
|
343
344
|
__publicField(this, "textureSize");
|
|
344
345
|
__publicField(this, "dataTextures");
|
|
345
346
|
__publicField(this, "eventEmitter");
|
|
347
|
+
__publicField(this, "currentAtlas", null);
|
|
346
348
|
this.eventEmitter = eventEmitter;
|
|
347
349
|
this.textureSize = textureSize;
|
|
348
350
|
this.dataTextures = /* @__PURE__ */ new Map();
|
|
@@ -353,6 +355,10 @@ class DataTextureService {
|
|
|
353
355
|
this.textureSize = textureSize;
|
|
354
356
|
this.dataTextures.forEach((texture) => texture.dispose());
|
|
355
357
|
this.dataTextures.clear();
|
|
358
|
+
if (this.currentAtlas) {
|
|
359
|
+
this.currentAtlas.dispose();
|
|
360
|
+
this.currentAtlas = null;
|
|
361
|
+
}
|
|
356
362
|
}
|
|
357
363
|
/**
|
|
358
364
|
* Prepares a mesh for sampling.
|
|
@@ -360,23 +366,76 @@ class DataTextureService {
|
|
|
360
366
|
* @param asset The asset to prepare.
|
|
361
367
|
*/
|
|
362
368
|
async getDataTexture(asset) {
|
|
363
|
-
const
|
|
364
|
-
if (
|
|
365
|
-
return
|
|
369
|
+
const cachedTexture = this.dataTextures.get(asset.uuid);
|
|
370
|
+
if (cachedTexture) {
|
|
371
|
+
return cachedTexture;
|
|
366
372
|
}
|
|
367
373
|
const meshData = parseMeshData(asset);
|
|
368
374
|
const array = sampleMesh(meshData, this.textureSize);
|
|
369
375
|
const dataTexture = createDataTexture(array, this.textureSize);
|
|
370
376
|
dataTexture.name = asset.name;
|
|
377
|
+
this.dataTextures.set(asset.uuid, dataTexture);
|
|
371
378
|
return dataTexture;
|
|
372
379
|
}
|
|
373
380
|
async dispose() {
|
|
381
|
+
this.dataTextures.forEach((texture) => texture.dispose());
|
|
374
382
|
this.dataTextures.clear();
|
|
383
|
+
if (this.currentAtlas) {
|
|
384
|
+
this.currentAtlas.dispose();
|
|
385
|
+
this.currentAtlas = null;
|
|
386
|
+
}
|
|
375
387
|
this.updateServiceState("disposed");
|
|
376
388
|
}
|
|
377
389
|
updateServiceState(serviceState) {
|
|
378
390
|
this.eventEmitter.emit("serviceStateUpdated", { type: "data-texture", state: serviceState });
|
|
379
391
|
}
|
|
392
|
+
/**
|
|
393
|
+
* Creates a Texture Atlas containing position data for a sequence of meshes.
|
|
394
|
+
* @param meshes An array of THREE.Mesh objects in the desired sequence.
|
|
395
|
+
* @param singleTextureSize The desired resolution (width/height) for each mesh's data within the atlas.
|
|
396
|
+
* @returns A Promise resolving to the generated DataTexture atlas.
|
|
397
|
+
*/
|
|
398
|
+
async createSequenceDataTextureAtlas(meshes, singleTextureSize) {
|
|
399
|
+
this.updateServiceState("loading");
|
|
400
|
+
if (this.currentAtlas) {
|
|
401
|
+
this.currentAtlas.dispose();
|
|
402
|
+
this.currentAtlas = null;
|
|
403
|
+
}
|
|
404
|
+
const numMeshes = meshes.length;
|
|
405
|
+
if (numMeshes === 0) {
|
|
406
|
+
throw new Error("Mesh array cannot be empty.");
|
|
407
|
+
}
|
|
408
|
+
const atlasWidth = singleTextureSize * numMeshes;
|
|
409
|
+
const atlasHeight = singleTextureSize;
|
|
410
|
+
const atlasData = new Float32Array(atlasWidth * atlasHeight * 4);
|
|
411
|
+
try {
|
|
412
|
+
for (let i = 0; i < numMeshes; i++) {
|
|
413
|
+
const mesh = meshes[i];
|
|
414
|
+
const meshDataTexture = await this.getDataTexture(mesh);
|
|
415
|
+
const meshTextureData = meshDataTexture.image.data;
|
|
416
|
+
for (let y = 0; y < singleTextureSize; y++) {
|
|
417
|
+
for (let x = 0; x < singleTextureSize; x++) {
|
|
418
|
+
const sourceIndex = (y * singleTextureSize + x) * 4;
|
|
419
|
+
const targetX = x + i * singleTextureSize;
|
|
420
|
+
const targetIndex = (y * atlasWidth + targetX) * 4;
|
|
421
|
+
atlasData[targetIndex] = meshTextureData[sourceIndex];
|
|
422
|
+
atlasData[targetIndex + 1] = meshTextureData[sourceIndex + 1];
|
|
423
|
+
atlasData[targetIndex + 2] = meshTextureData[sourceIndex + 2];
|
|
424
|
+
atlasData[targetIndex + 3] = meshTextureData[sourceIndex + 3];
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const atlasTexture = new THREE.DataTexture(atlasData, atlasWidth, atlasHeight, THREE.RGBAFormat, THREE.FloatType);
|
|
429
|
+
atlasTexture.needsUpdate = true;
|
|
430
|
+
atlasTexture.name = `atlas-${meshes.map((m) => m.name).join("-")}`;
|
|
431
|
+
this.currentAtlas = atlasTexture;
|
|
432
|
+
this.updateServiceState("ready");
|
|
433
|
+
return atlasTexture;
|
|
434
|
+
} catch (error) {
|
|
435
|
+
this.updateServiceState("error");
|
|
436
|
+
throw error;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
380
439
|
}
|
|
381
440
|
function parseMeshData(mesh) {
|
|
382
441
|
var _a;
|
|
@@ -722,32 +781,35 @@ class IntersectionService {
|
|
|
722
781
|
* Creates a new IntersectionService instance.
|
|
723
782
|
* @param eventEmitter The event emitter used for emitting events.
|
|
724
783
|
* @param camera The camera used for raycasting.
|
|
725
|
-
* @param originGeometry The origin geometry.
|
|
726
|
-
* @param destinationGeometry The destination geometry.
|
|
727
784
|
*/
|
|
728
|
-
constructor(eventEmitter, camera
|
|
785
|
+
constructor(eventEmitter, camera) {
|
|
729
786
|
__publicField(this, "active", true);
|
|
730
787
|
__publicField(this, "raycaster", new THREE.Raycaster());
|
|
731
788
|
__publicField(this, "mousePosition", new THREE.Vector2());
|
|
732
789
|
__publicField(this, "camera");
|
|
733
|
-
__publicField(this, "
|
|
734
|
-
|
|
735
|
-
__publicField(this, "
|
|
790
|
+
__publicField(this, "meshSequenceGeometries", []);
|
|
791
|
+
// ADDED: Store cloned geometries
|
|
792
|
+
__publicField(this, "meshSequenceUUIDs", []);
|
|
793
|
+
// ADDED: Track UUIDs to avoid redundant cloning
|
|
794
|
+
__publicField(this, "overallProgress", 0);
|
|
795
|
+
// ADDED: Store overall progress (0-1)
|
|
736
796
|
__publicField(this, "intersectionMesh", new THREE.Mesh());
|
|
797
|
+
// Use a single mesh for intersection target
|
|
737
798
|
__publicField(this, "geometryNeedsUpdate");
|
|
738
799
|
__publicField(this, "eventEmitter");
|
|
739
800
|
__publicField(this, "blendedGeometry");
|
|
801
|
+
// Keep for the final blended result
|
|
740
802
|
__publicField(this, "intersection");
|
|
741
|
-
__publicField(this, "lastKnownOriginMeshID");
|
|
742
|
-
__publicField(this, "lastKnownDestinationMeshID");
|
|
743
803
|
this.camera = camera;
|
|
744
|
-
this.originGeometry = originGeometry;
|
|
745
804
|
this.eventEmitter = eventEmitter;
|
|
746
|
-
this.destinationGeometry = destinationGeometry;
|
|
747
805
|
this.geometryNeedsUpdate = true;
|
|
748
806
|
}
|
|
749
807
|
setActive(active) {
|
|
750
808
|
this.active = active;
|
|
809
|
+
if (!active) {
|
|
810
|
+
this.intersection = void 0;
|
|
811
|
+
this.eventEmitter.emit("interactionPositionUpdated", { position: { x: 0, y: 0, z: 0, w: 0 } });
|
|
812
|
+
}
|
|
751
813
|
}
|
|
752
814
|
getIntersectionMesh() {
|
|
753
815
|
return this.intersectionMesh;
|
|
@@ -760,36 +822,40 @@ class IntersectionService {
|
|
|
760
822
|
this.camera = camera;
|
|
761
823
|
}
|
|
762
824
|
/**
|
|
763
|
-
*
|
|
764
|
-
*
|
|
825
|
+
* Sets the sequence of meshes used for intersection calculations.
|
|
826
|
+
* Clones the geometries to avoid modifying originals.
|
|
827
|
+
* @param meshes An array of THREE.Mesh objects in sequence.
|
|
765
828
|
*/
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
this.
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
829
|
+
setMeshSequence(meshes) {
|
|
830
|
+
this.meshSequenceGeometries.forEach((geom) => geom.dispose());
|
|
831
|
+
this.meshSequenceGeometries = [];
|
|
832
|
+
this.meshSequenceUUIDs = [];
|
|
833
|
+
if (!meshes || meshes.length === 0) {
|
|
834
|
+
this.geometryNeedsUpdate = true;
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
meshes.forEach((mesh) => {
|
|
838
|
+
if (mesh && mesh.geometry) {
|
|
839
|
+
const clonedGeometry = mesh.geometry.clone();
|
|
840
|
+
clonedGeometry.applyMatrix4(mesh.matrixWorld);
|
|
841
|
+
this.meshSequenceGeometries.push(clonedGeometry);
|
|
842
|
+
this.meshSequenceUUIDs.push(mesh.uuid);
|
|
843
|
+
} else {
|
|
844
|
+
console.warn("Invalid mesh provided to IntersectionService sequence.");
|
|
845
|
+
}
|
|
846
|
+
});
|
|
784
847
|
this.geometryNeedsUpdate = true;
|
|
785
848
|
}
|
|
786
849
|
/**
|
|
787
|
-
* Set the progress
|
|
788
|
-
* @param progress
|
|
850
|
+
* Set the overall progress through the mesh sequence.
|
|
851
|
+
* @param progress Value between 0.0 (first mesh) and 1.0 (last mesh).
|
|
789
852
|
*/
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
this.
|
|
853
|
+
setOverallProgress(progress) {
|
|
854
|
+
const newProgress = THREE.MathUtils.clamp(progress, 0, 1);
|
|
855
|
+
if (this.overallProgress !== newProgress) {
|
|
856
|
+
this.overallProgress = newProgress;
|
|
857
|
+
this.geometryNeedsUpdate = true;
|
|
858
|
+
}
|
|
793
859
|
}
|
|
794
860
|
/**
|
|
795
861
|
* Set the mouse position.
|
|
@@ -803,22 +869,46 @@ class IntersectionService {
|
|
|
803
869
|
* @returns The intersection point or undefined if no intersection was found.
|
|
804
870
|
*/
|
|
805
871
|
calculate(instancedMesh) {
|
|
806
|
-
|
|
807
|
-
this.
|
|
808
|
-
|
|
872
|
+
var _a, _b, _c;
|
|
873
|
+
if (!this.active || !this.camera || this.meshSequenceGeometries.length === 0) {
|
|
874
|
+
if (this.intersection) {
|
|
875
|
+
this.intersection = void 0;
|
|
876
|
+
this.eventEmitter.emit("interactionPositionUpdated", { position: { x: 0, y: 0, z: 0, w: 0 } });
|
|
877
|
+
}
|
|
878
|
+
return void 0;
|
|
879
|
+
}
|
|
809
880
|
if (this.geometryNeedsUpdate) {
|
|
810
|
-
this.
|
|
881
|
+
if (this.blendedGeometry && this.blendedGeometry !== this.intersectionMesh.geometry) {
|
|
882
|
+
this.blendedGeometry.dispose();
|
|
883
|
+
}
|
|
811
884
|
this.blendedGeometry = this.getBlendedGeometry();
|
|
885
|
+
this.geometryNeedsUpdate = false;
|
|
886
|
+
if (this.blendedGeometry) {
|
|
887
|
+
if (this.intersectionMesh.geometry !== this.blendedGeometry) {
|
|
888
|
+
if (this.intersectionMesh.geometry) this.intersectionMesh.geometry.dispose();
|
|
889
|
+
this.intersectionMesh.geometry = this.blendedGeometry;
|
|
890
|
+
}
|
|
891
|
+
} else {
|
|
892
|
+
if (this.intersectionMesh.geometry) this.intersectionMesh.geometry.dispose();
|
|
893
|
+
this.intersectionMesh.geometry = new THREE.BufferGeometry();
|
|
894
|
+
}
|
|
812
895
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
896
|
+
this.intersectionMesh.matrixWorld.copy(instancedMesh.matrixWorld);
|
|
897
|
+
let newIntersection = void 0;
|
|
898
|
+
if (this.blendedGeometry && this.blendedGeometry.attributes.position) {
|
|
899
|
+
newIntersection = this.getFirstIntersection(this.camera, this.intersectionMesh);
|
|
817
900
|
}
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
this.
|
|
901
|
+
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;
|
|
902
|
+
if (hasChanged) {
|
|
903
|
+
this.intersection = newIntersection;
|
|
904
|
+
if (this.intersection) {
|
|
905
|
+
const worldPoint = new THREE.Vector3(this.intersection.x, this.intersection.y, this.intersection.z);
|
|
906
|
+
const localPoint = instancedMesh.worldToLocal(worldPoint.clone());
|
|
907
|
+
this.intersection.set(localPoint.x, localPoint.y, localPoint.z, 1);
|
|
908
|
+
this.eventEmitter.emit("interactionPositionUpdated", { position: this.intersection });
|
|
909
|
+
} else {
|
|
910
|
+
this.eventEmitter.emit("interactionPositionUpdated", { position: { x: 0, y: 0, z: 0, w: 0 } });
|
|
911
|
+
}
|
|
822
912
|
}
|
|
823
913
|
return this.intersection;
|
|
824
914
|
}
|
|
@@ -827,8 +917,13 @@ class IntersectionService {
|
|
|
827
917
|
*/
|
|
828
918
|
dispose() {
|
|
829
919
|
var _a;
|
|
830
|
-
|
|
831
|
-
this.
|
|
920
|
+
this.meshSequenceGeometries.forEach((geom) => geom.dispose());
|
|
921
|
+
this.meshSequenceGeometries = [];
|
|
922
|
+
this.meshSequenceUUIDs = [];
|
|
923
|
+
if (this.blendedGeometry && this.blendedGeometry !== this.intersectionMesh.geometry) {
|
|
924
|
+
this.blendedGeometry.dispose();
|
|
925
|
+
}
|
|
926
|
+
(_a = this.intersectionMesh.geometry) == null ? void 0 : _a.dispose();
|
|
832
927
|
}
|
|
833
928
|
updateIntersectionMesh(instancedMesh) {
|
|
834
929
|
if (this.blendedGeometry) {
|
|
@@ -842,29 +937,50 @@ class IntersectionService {
|
|
|
842
937
|
this.intersectionMesh.matrixAutoUpdate = false;
|
|
843
938
|
this.intersectionMesh.updateMatrixWorld(true);
|
|
844
939
|
}
|
|
845
|
-
getFirstIntersection(camera,
|
|
940
|
+
getFirstIntersection(camera, targetMesh) {
|
|
846
941
|
this.raycaster.setFromCamera(this.mousePosition, camera);
|
|
847
|
-
const
|
|
848
|
-
if (
|
|
849
|
-
const worldPoint =
|
|
850
|
-
|
|
851
|
-
return new THREE.Vector4(localPoint.x, localPoint.y, localPoint.z, 1);
|
|
942
|
+
const intersects = this.raycaster.intersectObject(targetMesh, false);
|
|
943
|
+
if (intersects.length > 0 && intersects[0].point) {
|
|
944
|
+
const worldPoint = intersects[0].point;
|
|
945
|
+
return new THREE.Vector4(worldPoint.x, worldPoint.y, worldPoint.z, 1);
|
|
852
946
|
}
|
|
947
|
+
return void 0;
|
|
853
948
|
}
|
|
854
949
|
getBlendedGeometry() {
|
|
855
|
-
|
|
856
|
-
|
|
950
|
+
const numGeometries = this.meshSequenceGeometries.length;
|
|
951
|
+
if (numGeometries === 0) {
|
|
952
|
+
return void 0;
|
|
857
953
|
}
|
|
858
|
-
if (
|
|
859
|
-
return this.
|
|
954
|
+
if (numGeometries === 1) {
|
|
955
|
+
return this.meshSequenceGeometries[0];
|
|
860
956
|
}
|
|
861
|
-
|
|
862
|
-
|
|
957
|
+
const totalSegments = numGeometries - 1;
|
|
958
|
+
const progressPerSegment = 1 / totalSegments;
|
|
959
|
+
const scaledProgress = this.overallProgress * totalSegments;
|
|
960
|
+
let indexA = Math.floor(scaledProgress);
|
|
961
|
+
let indexB = indexA + 1;
|
|
962
|
+
indexA = THREE.MathUtils.clamp(indexA, 0, totalSegments);
|
|
963
|
+
indexB = THREE.MathUtils.clamp(indexB, 0, totalSegments);
|
|
964
|
+
let localProgress = 0;
|
|
965
|
+
if (progressPerSegment > 0) {
|
|
966
|
+
localProgress = scaledProgress - indexA;
|
|
967
|
+
}
|
|
968
|
+
if (this.overallProgress >= 1) {
|
|
969
|
+
indexA = totalSegments;
|
|
970
|
+
indexB = totalSegments;
|
|
971
|
+
localProgress = 1;
|
|
972
|
+
}
|
|
973
|
+
localProgress = THREE.MathUtils.clamp(localProgress, 0, 1);
|
|
974
|
+
const geomA = this.meshSequenceGeometries[indexA];
|
|
975
|
+
const geomB = this.meshSequenceGeometries[indexB];
|
|
976
|
+
if (!geomA || !geomB) {
|
|
977
|
+
console.error("IntersectionService: Invalid geometries found for blending at indices", indexA, indexB);
|
|
978
|
+
return this.meshSequenceGeometries[0];
|
|
863
979
|
}
|
|
864
|
-
if (
|
|
865
|
-
return
|
|
980
|
+
if (indexA === indexB) {
|
|
981
|
+
return geomA;
|
|
866
982
|
}
|
|
867
|
-
return this.blendGeometry(
|
|
983
|
+
return this.blendGeometry(geomA, geomB, localProgress);
|
|
868
984
|
}
|
|
869
985
|
blendGeometry(from, to, progress) {
|
|
870
986
|
const blended = new THREE.BufferGeometry();
|
|
@@ -914,8 +1030,8 @@ class FullScreenQuad {
|
|
|
914
1030
|
}
|
|
915
1031
|
class GPUComputationRenderer {
|
|
916
1032
|
/**
|
|
917
|
-
* @param {
|
|
918
|
-
* @param {
|
|
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.
|
|
919
1035
|
* @param {WebGLRenderer} renderer The renderer
|
|
920
1036
|
*/
|
|
921
1037
|
constructor(sizeX, sizeY, renderer) {
|
|
@@ -1087,179 +1203,283 @@ class GPUComputationRenderer {
|
|
|
1087
1203
|
}
|
|
1088
1204
|
}
|
|
1089
1205
|
}
|
|
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
1206
|
const positionShader = `
|
|
1104
|
-
uniform float uProgress;
|
|
1105
1207
|
uniform vec4 uInteractionPosition;
|
|
1106
1208
|
uniform float uTime;
|
|
1107
1209
|
uniform float uTractionForce;
|
|
1210
|
+
uniform sampler2D uPositionAtlas;
|
|
1211
|
+
uniform float uOverallProgress; // (0.0 to 1.0)
|
|
1212
|
+
uniform int uNumMeshes;
|
|
1213
|
+
uniform float uSingleTextureSize;
|
|
1108
1214
|
|
|
1109
1215
|
float rand(vec2 co) {
|
|
1110
1216
|
return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
|
|
1111
1217
|
}
|
|
1112
1218
|
|
|
1219
|
+
// Helper function to get position from atlas
|
|
1220
|
+
vec3 getAtlasPosition(vec2 uv, int meshIndex) {
|
|
1221
|
+
float atlasWidth = uSingleTextureSize * float(uNumMeshes);
|
|
1222
|
+
float atlasHeight = uSingleTextureSize; // Assuming height is single texture size
|
|
1223
|
+
|
|
1224
|
+
// Calculate UV within the specific mesh's section of the atlas
|
|
1225
|
+
float segmentWidthRatio = uSingleTextureSize / atlasWidth;
|
|
1226
|
+
vec2 atlasUV = vec2(
|
|
1227
|
+
uv.x * segmentWidthRatio + segmentWidthRatio * float(meshIndex),
|
|
1228
|
+
uv.y // Assuming vertical layout doesn't change y
|
|
1229
|
+
);
|
|
1230
|
+
|
|
1231
|
+
return texture2D(uPositionAtlas, atlasUV).xyz;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1113
1234
|
void main() {
|
|
1235
|
+
// GPGPU UV calculation
|
|
1236
|
+
vec2 uv = gl_FragCoord.xy / resolution.xy; // resolution is the size of the *output* texture (e.g., 256x256)
|
|
1114
1237
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
float offset = rand(uv);
|
|
1238
|
+
vec3 currentPosition = texture2D(uCurrentPosition, uv).xyz;
|
|
1239
|
+
vec3 currentVelocity = texture2D(uCurrentVelocity, uv).xyz;
|
|
1118
1240
|
|
|
1119
|
-
|
|
1120
|
-
vec3
|
|
1121
|
-
|
|
1241
|
+
// --- Calculate Target Position from Atlas ---
|
|
1242
|
+
vec3 targetPosition;
|
|
1243
|
+
if (uNumMeshes <= 1) {
|
|
1244
|
+
targetPosition = getAtlasPosition(uv, 0);
|
|
1245
|
+
} else {
|
|
1246
|
+
float totalSegments = float(uNumMeshes - 1);
|
|
1247
|
+
float progressPerSegment = 1.0 / totalSegments;
|
|
1248
|
+
float scaledProgress = uOverallProgress * totalSegments;
|
|
1122
1249
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1250
|
+
int indexA = int(floor(scaledProgress));
|
|
1251
|
+
// Clamp indexB to avoid going out of bounds
|
|
1252
|
+
int indexB = min(indexA + 1, uNumMeshes - 1);
|
|
1126
1253
|
|
|
1127
|
-
|
|
1128
|
-
|
|
1254
|
+
// Ensure indexA is also within bounds (important if uOverallProgress is exactly 1.0)
|
|
1255
|
+
indexA = min(indexA, uNumMeshes - 1);
|
|
1256
|
+
|
|
1257
|
+
|
|
1258
|
+
float localProgress = fract(scaledProgress);
|
|
1259
|
+
|
|
1260
|
+
// Handle edge case where progress is exactly 1.0
|
|
1261
|
+
if (uOverallProgress == 1.0) {
|
|
1262
|
+
indexA = uNumMeshes - 1;
|
|
1263
|
+
indexB = uNumMeshes - 1;
|
|
1264
|
+
localProgress = 1.0; // or 0.0 depending on how you want to handle it
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
|
|
1268
|
+
vec3 positionA = getAtlasPosition(uv, indexA);
|
|
1269
|
+
vec3 positionB = getAtlasPosition(uv, indexB);
|
|
1270
|
+
|
|
1271
|
+
targetPosition = mix(positionA, positionB, localProgress);
|
|
1272
|
+
}
|
|
1273
|
+
// --- End Target Position Calculation ---
|
|
1274
|
+
|
|
1275
|
+
// Particle attraction to target position
|
|
1276
|
+
vec3 direction = normalize(targetPosition - currentPosition);
|
|
1277
|
+
float dist = length(targetPosition - currentPosition);
|
|
1278
|
+
|
|
1279
|
+
vec3 finalPosition = currentPosition;
|
|
1280
|
+
|
|
1281
|
+
// Apply attraction force (simplified mix)
|
|
1282
|
+
if (dist > 0.01) { // Only apply if significantly far
|
|
1283
|
+
finalPosition = mix(currentPosition, targetPosition, 0.1 * uTractionForce);
|
|
1129
1284
|
}
|
|
1130
1285
|
|
|
1131
|
-
|
|
1132
|
-
gl_FragColor = vec4(
|
|
1286
|
+
finalPosition += currentVelocity;
|
|
1287
|
+
gl_FragColor = vec4(finalPosition, 1.0);
|
|
1133
1288
|
}
|
|
1134
1289
|
`;
|
|
1135
1290
|
const velocityShader = `
|
|
1136
|
-
uniform float uProgress;
|
|
1137
1291
|
uniform vec4 uInteractionPosition;
|
|
1138
1292
|
uniform float uTime;
|
|
1139
1293
|
uniform float uTractionForce;
|
|
1140
1294
|
uniform float uMaxRepelDistance;
|
|
1295
|
+
uniform sampler2D uPositionAtlas;
|
|
1296
|
+
uniform float uOverallProgress;
|
|
1297
|
+
uniform int uNumMeshes;
|
|
1298
|
+
uniform float uSingleTextureSize;
|
|
1141
1299
|
|
|
1142
1300
|
float rand(vec2 co) {
|
|
1143
1301
|
return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
|
|
1144
1302
|
}
|
|
1145
1303
|
|
|
1304
|
+
// Helper function (same as in position shader)
|
|
1305
|
+
vec3 getAtlasPosition(vec2 uv, int meshIndex) {
|
|
1306
|
+
float atlasWidth = uSingleTextureSize * float(uNumMeshes);
|
|
1307
|
+
float atlasHeight = uSingleTextureSize;
|
|
1308
|
+
float segmentWidthRatio = uSingleTextureSize / atlasWidth;
|
|
1309
|
+
vec2 atlasUV = vec2(uv.x * segmentWidthRatio + segmentWidthRatio * float(meshIndex), uv.y);
|
|
1310
|
+
return texture2D(uPositionAtlas, atlasUV).xyz;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1146
1313
|
void main() {
|
|
1147
|
-
|
|
1314
|
+
vec2 uv = gl_FragCoord.xy / resolution.xy;
|
|
1148
1315
|
float offset = rand(uv);
|
|
1149
1316
|
|
|
1150
|
-
vec3
|
|
1151
|
-
vec3
|
|
1152
|
-
|
|
1317
|
+
vec3 currentPosition = texture2D(uCurrentPosition, uv).xyz;
|
|
1318
|
+
vec3 currentVelocity = texture2D(uCurrentVelocity, uv).xyz;
|
|
1319
|
+
|
|
1320
|
+
// --- Calculate Target Position from Atlas (same logic as position shader) ---
|
|
1321
|
+
vec3 targetPosition;
|
|
1322
|
+
if (uNumMeshes <= 1) {
|
|
1323
|
+
targetPosition = getAtlasPosition(uv, 0);
|
|
1324
|
+
} else {
|
|
1325
|
+
float totalSegments = float(uNumMeshes - 1);
|
|
1326
|
+
float progressPerSegment = 1.0 / totalSegments;
|
|
1327
|
+
float scaledProgress = uOverallProgress * totalSegments;
|
|
1328
|
+
int indexA = int(floor(scaledProgress));
|
|
1329
|
+
int indexB = min(indexA + 1, uNumMeshes - 1);
|
|
1330
|
+
indexA = min(indexA, uNumMeshes - 1);
|
|
1331
|
+
float localProgress = fract(scaledProgress);
|
|
1332
|
+
if (uOverallProgress == 1.0) {
|
|
1333
|
+
indexA = uNumMeshes - 1;
|
|
1334
|
+
indexB = uNumMeshes - 1;
|
|
1335
|
+
localProgress = 1.0;
|
|
1336
|
+
}
|
|
1337
|
+
vec3 positionA = getAtlasPosition(uv, indexA);
|
|
1338
|
+
vec3 positionB = getAtlasPosition(uv, indexB);
|
|
1339
|
+
targetPosition = mix(positionA, positionB, localProgress);
|
|
1340
|
+
}
|
|
1341
|
+
// --- End Target Position Calculation ---
|
|
1153
1342
|
|
|
1154
|
-
|
|
1343
|
+
vec3 finalVelocity = currentVelocity * 0.9; // Dampening
|
|
1155
1344
|
|
|
1156
|
-
//
|
|
1157
|
-
vec3 direction = normalize(
|
|
1158
|
-
float dist = length
|
|
1345
|
+
// Particle traction force towards target (influences velocity)
|
|
1346
|
+
vec3 direction = normalize(targetPosition - currentPosition);
|
|
1347
|
+
float dist = length(targetPosition - currentPosition);
|
|
1159
1348
|
if (dist > 0.01) {
|
|
1160
|
-
|
|
1349
|
+
// Add force proportional to distance and traction setting
|
|
1350
|
+
finalVelocity += direction * dist * 0.01 * uTractionForce; // Adjust multiplier as needed
|
|
1161
1351
|
}
|
|
1162
1352
|
|
|
1163
|
-
//
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1353
|
+
// Mouse repel force
|
|
1354
|
+
if (uInteractionPosition.w > 0.0) { // Check if interaction is active (w component)
|
|
1355
|
+
float pointerDistance = distance(currentPosition, uInteractionPosition.xyz);
|
|
1356
|
+
if (pointerDistance < uMaxRepelDistance) {
|
|
1357
|
+
float mouseRepelModifier = smoothstep(uMaxRepelDistance, 0.0, pointerDistance); // Smoother falloff
|
|
1358
|
+
vec3 repelDirection = normalize(currentPosition - uInteractionPosition.xyz);
|
|
1359
|
+
// Apply force based on proximity and interaction strength (w)
|
|
1360
|
+
finalVelocity += repelDirection * mouseRepelModifier * uInteractionPosition.w * 0.01; // Adjust multiplier
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1170
1363
|
|
|
1364
|
+
// Optional: Reset position if particle "dies" and respawns (lifespan logic)
|
|
1171
1365
|
float lifespan = 20.0;
|
|
1172
|
-
float age = mod(uTime + lifespan * offset, lifespan);
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
position
|
|
1366
|
+
float age = mod(uTime * 0.1 + lifespan * offset, lifespan); // Adjust time scale
|
|
1367
|
+
if (age < 0.05) { // Small window for reset
|
|
1368
|
+
finalVelocity = vec3(0.0); // Reset velocity on respawn
|
|
1369
|
+
// Note: Resetting position directly here might cause jumps.
|
|
1370
|
+
// It's often better handled in the position shader or by ensuring
|
|
1371
|
+
// strong attraction force when dist is large.
|
|
1176
1372
|
}
|
|
1177
1373
|
|
|
1178
|
-
|
|
1374
|
+
|
|
1375
|
+
gl_FragColor = vec4(finalVelocity, 1.0);
|
|
1179
1376
|
}
|
|
1180
1377
|
`;
|
|
1181
1378
|
class SimulationRenderer {
|
|
1182
1379
|
/**
|
|
1183
1380
|
* Creates a new SimulationRenderer instance.
|
|
1184
|
-
* @param size The size of the simulation textures.
|
|
1381
|
+
* @param size The size of the simulation textures (width/height).
|
|
1185
1382
|
* @param webGLRenderer The WebGL renderer.
|
|
1186
|
-
* @param initialPosition The initial position data texture
|
|
1383
|
+
* @param initialPosition The initial position data texture (optional, defaults to sphere).
|
|
1187
1384
|
*/
|
|
1188
1385
|
constructor(size, webGLRenderer, initialPosition) {
|
|
1189
1386
|
__publicField(this, "gpuComputationRenderer");
|
|
1190
1387
|
__publicField(this, "webGLRenderer");
|
|
1191
|
-
//
|
|
1192
|
-
__publicField(this, "positionDataTexture");
|
|
1193
|
-
__publicField(this, "velocityDataTexture");
|
|
1194
|
-
// GPUComputationRenderer variables
|
|
1195
|
-
__publicField(this, "mixPositionsVar");
|
|
1388
|
+
// GPGPU Variables
|
|
1196
1389
|
__publicField(this, "velocityVar");
|
|
1197
1390
|
__publicField(this, "positionVar");
|
|
1391
|
+
// Input Data Textures (References)
|
|
1392
|
+
__publicField(this, "initialPositionDataTexture");
|
|
1393
|
+
// Used only for first init
|
|
1394
|
+
__publicField(this, "initialVelocityDataTexture");
|
|
1395
|
+
// Blank texture for init
|
|
1396
|
+
__publicField(this, "positionAtlasTexture", null);
|
|
1397
|
+
// Holds the current mesh sequence atlas
|
|
1398
|
+
// Uniforms
|
|
1198
1399
|
__publicField(this, "interactionPosition");
|
|
1400
|
+
// Cache last known output textures
|
|
1199
1401
|
__publicField(this, "lastKnownPositionDataTexture");
|
|
1200
1402
|
__publicField(this, "lastKnownVelocityDataTexture");
|
|
1201
|
-
__publicField(this, "lastKnownMixProgress");
|
|
1202
|
-
__publicField(this, "initialDataTexture");
|
|
1203
|
-
this.initialDataTexture = initialPosition ?? createSpherePoints(size);
|
|
1204
|
-
this.positionDataTexture = this.initialDataTexture;
|
|
1205
1403
|
this.webGLRenderer = webGLRenderer;
|
|
1206
1404
|
this.gpuComputationRenderer = new GPUComputationRenderer(size, size, this.webGLRenderer);
|
|
1207
|
-
|
|
1208
|
-
|
|
1405
|
+
if (!webGLRenderer.capabilities.isWebGL2 && webGLRenderer.extensions.get("OES_texture_float")) {
|
|
1406
|
+
this.gpuComputationRenderer.setDataType(THREE.FloatType);
|
|
1407
|
+
} else if (!webGLRenderer.capabilities.isWebGL2) {
|
|
1209
1408
|
this.gpuComputationRenderer.setDataType(THREE.HalfFloatType);
|
|
1210
1409
|
}
|
|
1211
|
-
this.
|
|
1410
|
+
this.initialPositionDataTexture = initialPosition ?? createSpherePoints(size);
|
|
1411
|
+
this.initialVelocityDataTexture = createBlankDataTexture(size);
|
|
1212
1412
|
this.interactionPosition = new THREE.Vector4(0, 0, 0, 0);
|
|
1213
|
-
this.
|
|
1214
|
-
this.
|
|
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 };
|
|
1413
|
+
this.velocityVar = this.gpuComputationRenderer.addVariable("uCurrentVelocity", velocityShader, this.initialVelocityDataTexture);
|
|
1414
|
+
this.positionVar = this.gpuComputationRenderer.addVariable("uCurrentPosition", positionShader, this.initialPositionDataTexture);
|
|
1219
1415
|
this.velocityVar.material.uniforms.uTime = { value: 0 };
|
|
1220
1416
|
this.velocityVar.material.uniforms.uInteractionPosition = { value: this.interactionPosition };
|
|
1221
|
-
this.velocityVar.material.uniforms.uCurrentPosition = { value:
|
|
1417
|
+
this.velocityVar.material.uniforms.uCurrentPosition = { value: null };
|
|
1222
1418
|
this.velocityVar.material.uniforms.uTractionForce = { value: 0.1 };
|
|
1223
1419
|
this.velocityVar.material.uniforms.uMaxRepelDistance = { value: 0.3 };
|
|
1420
|
+
this.velocityVar.material.uniforms.uPositionAtlas = { value: null };
|
|
1421
|
+
this.velocityVar.material.uniforms.uOverallProgress = { value: 0 };
|
|
1422
|
+
this.velocityVar.material.uniforms.uNumMeshes = { value: 1 };
|
|
1423
|
+
this.velocityVar.material.uniforms.uSingleTextureSize = { value: size };
|
|
1224
1424
|
this.positionVar.material.uniforms.uTime = { value: 0 };
|
|
1225
|
-
this.positionVar.material.uniforms.uProgress = { value: 0 };
|
|
1226
1425
|
this.positionVar.material.uniforms.uTractionForce = { value: 0.1 };
|
|
1227
1426
|
this.positionVar.material.uniforms.uInteractionPosition = { value: this.interactionPosition };
|
|
1228
|
-
this.positionVar.material.uniforms.uCurrentPosition = { value:
|
|
1229
|
-
this.
|
|
1230
|
-
this.
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1427
|
+
this.positionVar.material.uniforms.uCurrentPosition = { value: null };
|
|
1428
|
+
this.positionVar.material.uniforms.uCurrentVelocity = { value: null };
|
|
1429
|
+
this.positionVar.material.uniforms.uPositionAtlas = { value: null };
|
|
1430
|
+
this.positionVar.material.uniforms.uOverallProgress = { value: 0 };
|
|
1431
|
+
this.positionVar.material.uniforms.uNumMeshes = { value: 1 };
|
|
1432
|
+
this.positionVar.material.uniforms.uSingleTextureSize = { value: size };
|
|
1433
|
+
this.gpuComputationRenderer.setVariableDependencies(this.positionVar, [this.positionVar, this.velocityVar]);
|
|
1434
|
+
this.gpuComputationRenderer.setVariableDependencies(this.velocityVar, [this.velocityVar, this.positionVar]);
|
|
1435
|
+
const initError = this.gpuComputationRenderer.init();
|
|
1436
|
+
if (initError !== null) {
|
|
1437
|
+
throw new Error("Failed to initialize SimulationRenderer: " + initError);
|
|
1234
1438
|
}
|
|
1235
|
-
this.
|
|
1236
|
-
this.
|
|
1439
|
+
this.positionVar.material.uniforms.uPositionAtlas.value = this.initialPositionDataTexture;
|
|
1440
|
+
this.positionVar.material.uniforms.uNumMeshes.value = 1;
|
|
1441
|
+
this.velocityVar.material.uniforms.uNumMeshes.value = 1;
|
|
1442
|
+
this.positionVar.material.uniforms.uSingleTextureSize.value = size;
|
|
1443
|
+
this.velocityVar.material.uniforms.uSingleTextureSize.value = size;
|
|
1444
|
+
this.positionVar.material.uniforms.uCurrentVelocity.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
|
|
1445
|
+
this.velocityVar.material.uniforms.uCurrentPosition.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
|
|
1446
|
+
this.lastKnownVelocityDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
|
|
1447
|
+
this.lastKnownPositionDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
|
|
1237
1448
|
}
|
|
1238
1449
|
/**
|
|
1239
|
-
* Sets the
|
|
1240
|
-
* @param
|
|
1450
|
+
* Sets the mesh sequence position atlas texture and related uniforms.
|
|
1451
|
+
* @param entry Information about the atlas texture.
|
|
1241
1452
|
*/
|
|
1242
|
-
|
|
1243
|
-
|
|
1453
|
+
setPositionAtlas(entry) {
|
|
1454
|
+
const expectedAtlasWidth = entry.singleTextureSize * entry.numMeshes;
|
|
1455
|
+
if (entry.dataTexture.image.width !== expectedAtlasWidth || entry.dataTexture.image.height !== entry.singleTextureSize) {
|
|
1456
|
+
console.error(
|
|
1457
|
+
`SimulationRenderer: Atlas texture dimension mismatch! Expected ${expectedAtlasWidth}x${entry.singleTextureSize}, Got ${entry.dataTexture.image.width}x${entry.dataTexture.image.height}`
|
|
1458
|
+
);
|
|
1459
|
+
}
|
|
1460
|
+
this.positionAtlasTexture = entry.dataTexture;
|
|
1461
|
+
const numMeshes = entry.numMeshes > 0 ? entry.numMeshes : 1;
|
|
1462
|
+
this.positionVar.material.uniforms.uPositionAtlas.value = this.positionAtlasTexture;
|
|
1463
|
+
this.positionVar.material.uniforms.uNumMeshes.value = numMeshes;
|
|
1464
|
+
this.positionVar.material.uniforms.uSingleTextureSize.value = entry.singleTextureSize;
|
|
1465
|
+
this.velocityVar.material.uniforms.uPositionAtlas.value = this.positionAtlasTexture;
|
|
1466
|
+
this.velocityVar.material.uniforms.uNumMeshes.value = numMeshes;
|
|
1467
|
+
this.velocityVar.material.uniforms.uSingleTextureSize.value = entry.singleTextureSize;
|
|
1468
|
+
this.positionVar.material.uniforms.uCurrentVelocity.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
|
|
1469
|
+
this.velocityVar.material.uniforms.uCurrentPosition.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
|
|
1244
1470
|
}
|
|
1245
1471
|
/**
|
|
1246
|
-
* Sets the
|
|
1247
|
-
* @param
|
|
1472
|
+
* Sets the overall progress for blending between meshes in the atlas.
|
|
1473
|
+
* @param progress Value between 0.0 and 1.0.
|
|
1248
1474
|
*/
|
|
1249
|
-
|
|
1250
|
-
|
|
1475
|
+
setOverallProgress(progress) {
|
|
1476
|
+
const clampedProgress = clamp(progress, 0, 1);
|
|
1477
|
+
this.positionVar.material.uniforms.uOverallProgress.value = clampedProgress;
|
|
1478
|
+
this.velocityVar.material.uniforms.uOverallProgress.value = clampedProgress;
|
|
1251
1479
|
}
|
|
1252
1480
|
setMaxRepelDistance(distance) {
|
|
1253
1481
|
this.velocityVar.material.uniforms.uMaxRepelDistance.value = distance;
|
|
1254
1482
|
}
|
|
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
1483
|
setVelocityTractionForce(force) {
|
|
1264
1484
|
this.velocityVar.material.uniforms.uTractionForce.value = force;
|
|
1265
1485
|
}
|
|
@@ -1273,36 +1493,31 @@ class SimulationRenderer {
|
|
|
1273
1493
|
* Disposes the resources used by the simulation renderer.
|
|
1274
1494
|
*/
|
|
1275
1495
|
dispose() {
|
|
1276
|
-
|
|
1277
|
-
this.positionVar.renderTargets.forEach((rtt) => rtt.dispose());
|
|
1278
|
-
this.velocityVar.renderTargets.forEach((rtt) => rtt.dispose());
|
|
1279
|
-
this.positionDataTexture.dispose();
|
|
1280
|
-
this.velocityDataTexture.dispose();
|
|
1496
|
+
var _a, _b;
|
|
1281
1497
|
this.gpuComputationRenderer.dispose();
|
|
1498
|
+
(_a = this.initialPositionDataTexture) == null ? void 0 : _a.dispose();
|
|
1499
|
+
(_b = this.initialVelocityDataTexture) == null ? void 0 : _b.dispose();
|
|
1500
|
+
this.positionAtlasTexture = null;
|
|
1282
1501
|
}
|
|
1283
1502
|
/**
|
|
1284
1503
|
* Computes the next step of the simulation.
|
|
1285
|
-
* @param
|
|
1504
|
+
* @param deltaTime The time elapsed since the last frame, in seconds.
|
|
1286
1505
|
*/
|
|
1287
|
-
compute(
|
|
1288
|
-
this.velocityVar.material.uniforms.uTime.value
|
|
1289
|
-
this.positionVar.material.uniforms.uTime.value
|
|
1506
|
+
compute(deltaTime) {
|
|
1507
|
+
this.velocityVar.material.uniforms.uTime.value += deltaTime;
|
|
1508
|
+
this.positionVar.material.uniforms.uTime.value += deltaTime;
|
|
1509
|
+
this.positionVar.material.uniforms.uCurrentVelocity.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
|
|
1510
|
+
this.velocityVar.material.uniforms.uCurrentPosition.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
|
|
1290
1511
|
this.gpuComputationRenderer.compute();
|
|
1512
|
+
this.lastKnownVelocityDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
|
|
1513
|
+
this.lastKnownPositionDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
|
|
1291
1514
|
}
|
|
1292
|
-
/**
|
|
1293
|
-
* Gets the current velocity texture.
|
|
1294
|
-
* @returns The current velocity texture.
|
|
1295
|
-
*/
|
|
1515
|
+
/** Gets the current velocity texture (output from the last compute step). */
|
|
1296
1516
|
getVelocityTexture() {
|
|
1297
|
-
this.lastKnownVelocityDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
|
|
1298
1517
|
return this.lastKnownVelocityDataTexture;
|
|
1299
1518
|
}
|
|
1300
|
-
/**
|
|
1301
|
-
* Gets the current position texture.
|
|
1302
|
-
* @returns The current position texture.
|
|
1303
|
-
*/
|
|
1519
|
+
/** Gets the current position texture (output from the last compute step). */
|
|
1304
1520
|
getPositionTexture() {
|
|
1305
|
-
this.lastKnownPositionDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
|
|
1306
1521
|
return this.lastKnownPositionDataTexture;
|
|
1307
1522
|
}
|
|
1308
1523
|
}
|
|
@@ -1310,18 +1525,22 @@ class SimulationRendererService {
|
|
|
1310
1525
|
constructor(eventEmitter, size, webGLRenderer) {
|
|
1311
1526
|
__publicField(this, "state");
|
|
1312
1527
|
__publicField(this, "textureSize");
|
|
1313
|
-
__publicField(this, "
|
|
1528
|
+
__publicField(this, "overallProgress");
|
|
1529
|
+
// ADDED: Store overall progress
|
|
1314
1530
|
__publicField(this, "velocityTractionForce");
|
|
1315
1531
|
__publicField(this, "positionalTractionForce");
|
|
1316
1532
|
__publicField(this, "simulationRenderer");
|
|
1317
1533
|
__publicField(this, "webGLRenderer");
|
|
1318
1534
|
__publicField(this, "eventEmitter");
|
|
1535
|
+
// Store atlas info
|
|
1536
|
+
__publicField(this, "currentAtlasEntry", null);
|
|
1537
|
+
// ADDED
|
|
1319
1538
|
__publicField(this, "lastKnownVelocityDataTexture");
|
|
1320
1539
|
__publicField(this, "lastKnownPositionDataTexture");
|
|
1321
1540
|
this.eventEmitter = eventEmitter;
|
|
1322
1541
|
this.webGLRenderer = webGLRenderer;
|
|
1323
1542
|
this.textureSize = size;
|
|
1324
|
-
this.
|
|
1543
|
+
this.overallProgress = 0;
|
|
1325
1544
|
this.velocityTractionForce = 0.1;
|
|
1326
1545
|
this.positionalTractionForce = 0.1;
|
|
1327
1546
|
this.updateServiceState("initializing");
|
|
@@ -1330,6 +1549,27 @@ class SimulationRendererService {
|
|
|
1330
1549
|
this.lastKnownPositionDataTexture = this.simulationRenderer.getPositionTexture();
|
|
1331
1550
|
this.updateServiceState("ready");
|
|
1332
1551
|
}
|
|
1552
|
+
/**
|
|
1553
|
+
* Sets the position data texture atlas for the simulation.
|
|
1554
|
+
* @param entry An object containing the atlas texture and related parameters.
|
|
1555
|
+
*/
|
|
1556
|
+
setPositionAtlas(entry) {
|
|
1557
|
+
const expectedWidth = entry.singleTextureSize * entry.numMeshes;
|
|
1558
|
+
if (entry.dataTexture.image.width !== expectedWidth) {
|
|
1559
|
+
this.eventEmitter.emit("invalidRequest", { message: `Atlas texture width mismatch.` });
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
this.currentAtlasEntry = entry;
|
|
1563
|
+
this.simulationRenderer.setPositionAtlas(entry);
|
|
1564
|
+
}
|
|
1565
|
+
/**
|
|
1566
|
+
* Sets the overall progress for the mesh sequence transition.
|
|
1567
|
+
* @param progress The progress value (0.0 to 1.0).
|
|
1568
|
+
*/
|
|
1569
|
+
setOverallProgress(progress) {
|
|
1570
|
+
this.overallProgress = progress;
|
|
1571
|
+
this.simulationRenderer.setOverallProgress(this.overallProgress);
|
|
1572
|
+
}
|
|
1333
1573
|
setTextureSize(size) {
|
|
1334
1574
|
this.updateServiceState("initializing");
|
|
1335
1575
|
this.simulationRenderer.dispose();
|
|
@@ -1337,24 +1577,6 @@ class SimulationRendererService {
|
|
|
1337
1577
|
this.simulationRenderer = new SimulationRenderer(size, this.webGLRenderer);
|
|
1338
1578
|
this.updateServiceState("ready");
|
|
1339
1579
|
}
|
|
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
1580
|
setVelocityTractionForce(force) {
|
|
1359
1581
|
this.velocityTractionForce = force;
|
|
1360
1582
|
this.simulationRenderer.setVelocityTractionForce(this.velocityTractionForce);
|
|
@@ -1364,21 +1586,21 @@ class SimulationRendererService {
|
|
|
1364
1586
|
this.simulationRenderer.setPositionalTractionForce(this.positionalTractionForce);
|
|
1365
1587
|
}
|
|
1366
1588
|
compute(elapsedTime) {
|
|
1589
|
+
if (this.state !== "ready") return;
|
|
1367
1590
|
this.simulationRenderer.compute(elapsedTime);
|
|
1591
|
+
this.lastKnownVelocityDataTexture = this.simulationRenderer.getVelocityTexture();
|
|
1592
|
+
this.lastKnownPositionDataTexture = this.simulationRenderer.getPositionTexture();
|
|
1368
1593
|
}
|
|
1369
1594
|
getVelocityTexture() {
|
|
1370
|
-
if (this.state === "ready") this.lastKnownVelocityDataTexture = this.simulationRenderer.getVelocityTexture();
|
|
1371
1595
|
return this.lastKnownVelocityDataTexture;
|
|
1372
1596
|
}
|
|
1373
1597
|
getPositionTexture() {
|
|
1374
|
-
if (this.state === "ready") this.lastKnownPositionDataTexture = this.simulationRenderer.getPositionTexture();
|
|
1375
1598
|
return this.lastKnownPositionDataTexture;
|
|
1376
1599
|
}
|
|
1377
1600
|
dispose() {
|
|
1378
1601
|
this.updateServiceState("disposed");
|
|
1379
1602
|
this.simulationRenderer.dispose();
|
|
1380
|
-
this.
|
|
1381
|
-
this.lastKnownPositionDataTexture.dispose();
|
|
1603
|
+
this.currentAtlasEntry = null;
|
|
1382
1604
|
}
|
|
1383
1605
|
updateServiceState(serviceState) {
|
|
1384
1606
|
this.state = serviceState;
|
|
@@ -1495,7 +1717,6 @@ class ParticlesEngine {
|
|
|
1495
1717
|
*/
|
|
1496
1718
|
constructor(params) {
|
|
1497
1719
|
__publicField(this, "simulationRendererService");
|
|
1498
|
-
__publicField(this, "eventEmitter");
|
|
1499
1720
|
__publicField(this, "renderer");
|
|
1500
1721
|
__publicField(this, "scene");
|
|
1501
1722
|
__publicField(this, "serviceStates");
|
|
@@ -1506,6 +1727,9 @@ class ParticlesEngine {
|
|
|
1506
1727
|
__publicField(this, "transitionService");
|
|
1507
1728
|
__publicField(this, "engineState");
|
|
1508
1729
|
__publicField(this, "intersectionService");
|
|
1730
|
+
__publicField(this, "meshSequenceAtlasTexture", null);
|
|
1731
|
+
// ADDED: To store the generated atlas
|
|
1732
|
+
__publicField(this, "eventEmitter");
|
|
1509
1733
|
const { scene, renderer, camera, textureSize, useIntersection = true } = params;
|
|
1510
1734
|
this.eventEmitter = new DefaultEventEmitter();
|
|
1511
1735
|
this.serviceStates = this.getInitialServiceStates();
|
|
@@ -1522,7 +1746,6 @@ class ParticlesEngine {
|
|
|
1522
1746
|
this.scene.add(this.instancedMeshManager.getMesh());
|
|
1523
1747
|
this.intersectionService = new IntersectionService(this.eventEmitter, camera);
|
|
1524
1748
|
if (!useIntersection) this.intersectionService.setActive(false);
|
|
1525
|
-
this.eventEmitter.on("transitionProgressed", this.handleTransitionProgress.bind(this));
|
|
1526
1749
|
this.eventEmitter.on("interactionPositionUpdated", this.handleInteractionPositionUpdated.bind(this));
|
|
1527
1750
|
}
|
|
1528
1751
|
/**
|
|
@@ -1530,48 +1753,14 @@ class ParticlesEngine {
|
|
|
1530
1753
|
* @param elapsedTime The elapsed time since the last frame.
|
|
1531
1754
|
*/
|
|
1532
1755
|
render(elapsedTime) {
|
|
1756
|
+
const dt = elapsedTime / 1e3;
|
|
1757
|
+
this.transitionService.compute(dt);
|
|
1533
1758
|
this.intersectionService.calculate(this.instancedMeshManager.getMesh());
|
|
1534
|
-
this.
|
|
1535
|
-
this.
|
|
1536
|
-
this.instancedMeshManager.update(elapsedTime);
|
|
1759
|
+
this.simulationRendererService.compute(dt);
|
|
1760
|
+
this.instancedMeshManager.update(dt);
|
|
1537
1761
|
this.instancedMeshManager.updateVelocityTexture(this.simulationRendererService.getVelocityTexture());
|
|
1538
1762
|
this.instancedMeshManager.updatePositionTexture(this.simulationRendererService.getPositionTexture());
|
|
1539
1763
|
}
|
|
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
1764
|
setOriginMatcap(matcapID, override = false) {
|
|
1576
1765
|
if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
|
|
1577
1766
|
this.engineState.originMatcapID = matcapID;
|
|
@@ -1607,40 +1796,31 @@ class ParticlesEngine {
|
|
|
1607
1796
|
}
|
|
1608
1797
|
}
|
|
1609
1798
|
setMatcapProgress(progress, override = false) {
|
|
1799
|
+
const clampedProgress = clamp(progress, 0, 1);
|
|
1610
1800
|
if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
|
|
1611
|
-
this.engineState.matcapTransitionProgress =
|
|
1612
|
-
this.instancedMeshManager.setProgress(
|
|
1801
|
+
this.engineState.matcapTransitionProgress = clampedProgress;
|
|
1802
|
+
this.instancedMeshManager.setProgress(clampedProgress);
|
|
1613
1803
|
}
|
|
1804
|
+
// --- Update setTextureSize ---
|
|
1614
1805
|
async setTextureSize(size) {
|
|
1806
|
+
if (this.engineState.textureSize === size) {
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1615
1809
|
this.engineState.textureSize = size;
|
|
1616
1810
|
this.dataTextureManager.setTextureSize(size);
|
|
1617
1811
|
this.simulationRendererService.setTextureSize(size);
|
|
1618
1812
|
this.instancedMeshManager.resize(size);
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
this.eventEmitter.emit("invalidRequest", { message: `Mesh with id "${this.engineState.originMeshID}" does not exist` });
|
|
1622
|
-
return;
|
|
1813
|
+
if (this.engineState.meshSequence.length > 0) {
|
|
1814
|
+
await this.setMeshSequence(this.engineState.meshSequence);
|
|
1623
1815
|
}
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
this.eventEmitter.emit("invalidRequest", { message: `Mesh with id "${this.engineState.destinationMeshID}" does not exist` });
|
|
1627
|
-
return;
|
|
1816
|
+
if (this.engineState.meshSequence.length > 0) {
|
|
1817
|
+
await this.setMeshSequence(this.engineState.meshSequence);
|
|
1628
1818
|
}
|
|
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
1819
|
this.simulationRendererService.setVelocityTractionForce(this.engineState.velocityTractionForce);
|
|
1643
1820
|
this.simulationRendererService.setPositionalTractionForce(this.engineState.positionalTractionForce);
|
|
1821
|
+
this.simulationRendererService.setMaxRepelDistance(this.engineState.maxRepelDistance);
|
|
1822
|
+
this.simulationRendererService.setOverallProgress(this.engineState.overallProgress);
|
|
1823
|
+
this.intersectionService.setOverallProgress(this.engineState.overallProgress);
|
|
1644
1824
|
this.instancedMeshManager.setOriginMatcap(this.assetService.getMatcap(this.engineState.originMatcapID));
|
|
1645
1825
|
this.instancedMeshManager.setDestinationMatcap(this.assetService.getMatcap(this.engineState.destinationMatcapID));
|
|
1646
1826
|
this.instancedMeshManager.setProgress(this.engineState.matcapTransitionProgress);
|
|
@@ -1663,7 +1843,7 @@ class ParticlesEngine {
|
|
|
1663
1843
|
this.engineState.useIntersect = use;
|
|
1664
1844
|
if (!use) {
|
|
1665
1845
|
this.engineState.pointerPosition = { x: -99999999, y: -99999999 };
|
|
1666
|
-
this.
|
|
1846
|
+
this.simulationRendererService.setInteractionPosition({ x: 0, y: 0, z: 0, w: 0 });
|
|
1667
1847
|
}
|
|
1668
1848
|
}
|
|
1669
1849
|
setPointerPosition(position) {
|
|
@@ -1687,50 +1867,170 @@ class ParticlesEngine {
|
|
|
1687
1867
|
this.engineState.maxRepelDistance = distance;
|
|
1688
1868
|
this.simulationRendererService.setMaxRepelDistance(distance);
|
|
1689
1869
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1870
|
+
/**
|
|
1871
|
+
* Sets the sequence of meshes for particle transitions.
|
|
1872
|
+
* This will generate a texture atlas containing position data for all meshes.
|
|
1873
|
+
* @param meshIDs An array of registered mesh IDs in the desired sequence order.
|
|
1874
|
+
*/
|
|
1875
|
+
async setMeshSequence(meshIDs) {
|
|
1876
|
+
if (!meshIDs || meshIDs.length < 1) {
|
|
1877
|
+
this.eventEmitter.emit("invalidRequest", { message: "Mesh sequence must contain at least one mesh ID." });
|
|
1878
|
+
this.engineState.meshSequence = [];
|
|
1879
|
+
this.intersectionService.setMeshSequence([]);
|
|
1880
|
+
return;
|
|
1881
|
+
}
|
|
1882
|
+
this.engineState.meshSequence = meshIDs;
|
|
1883
|
+
this.engineState.overallProgress = 0;
|
|
1884
|
+
const meshes = meshIDs.map((id) => this.assetService.getMesh(id)).filter((mesh) => mesh !== null);
|
|
1885
|
+
if (meshes.length !== meshIDs.length) {
|
|
1886
|
+
const missing = meshIDs.filter((id) => !this.assetService.getMesh(id));
|
|
1887
|
+
console.warn(`Could not find meshes for IDs: ${missing.join(", ")}. Proceeding with ${meshes.length} found meshes.`);
|
|
1888
|
+
this.eventEmitter.emit("invalidRequest", { message: `Could not find meshes for IDs: ${missing.join(", ")}` });
|
|
1889
|
+
if (meshes.length < 1) {
|
|
1890
|
+
this.engineState.meshSequence = [];
|
|
1891
|
+
this.intersectionService.setMeshSequence([]);
|
|
1892
|
+
return;
|
|
1700
1893
|
}
|
|
1701
|
-
|
|
1894
|
+
this.engineState.meshSequence = meshes.map((m) => m.name);
|
|
1895
|
+
}
|
|
1896
|
+
try {
|
|
1897
|
+
this.meshSequenceAtlasTexture = await this.dataTextureManager.createSequenceDataTextureAtlas(meshes, this.engineState.textureSize);
|
|
1898
|
+
this.simulationRendererService.setPositionAtlas({
|
|
1899
|
+
dataTexture: this.meshSequenceAtlasTexture,
|
|
1900
|
+
textureSize: this.engineState.textureSize,
|
|
1901
|
+
// Pass the size of the *output* GPGPU texture
|
|
1902
|
+
numMeshes: this.engineState.meshSequence.length,
|
|
1903
|
+
// Use the potentially updated count
|
|
1904
|
+
singleTextureSize: this.engineState.textureSize
|
|
1905
|
+
// Size of one mesh's data within atlas
|
|
1906
|
+
});
|
|
1907
|
+
this.simulationRendererService.setOverallProgress(this.engineState.overallProgress);
|
|
1908
|
+
this.intersectionService.setMeshSequence(meshes);
|
|
1909
|
+
this.intersectionService.setOverallProgress(this.engineState.overallProgress);
|
|
1910
|
+
} catch (error) {
|
|
1911
|
+
console.error("Failed during mesh sequence setup:", error);
|
|
1912
|
+
this.meshSequenceAtlasTexture = null;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
/**
|
|
1916
|
+
* Sets the overall progress through the mesh sequence.
|
|
1917
|
+
* @param progress A value between 0.0 (first mesh) and 1.0 (last mesh).
|
|
1918
|
+
* @param override If true, cancels any ongoing mesh sequence transition before setting the value. Defaults to true.
|
|
1919
|
+
*/
|
|
1920
|
+
setOverallProgress(progress, override = true) {
|
|
1921
|
+
if (override) {
|
|
1922
|
+
this.eventEmitter.emit("transitionCancelled", { type: "mesh-sequence" });
|
|
1923
|
+
}
|
|
1924
|
+
const clampedProgress = clamp(progress, 0, 1);
|
|
1925
|
+
this.engineState.overallProgress = clampedProgress;
|
|
1926
|
+
this.simulationRendererService.setOverallProgress(clampedProgress);
|
|
1927
|
+
this.intersectionService.setOverallProgress(clampedProgress);
|
|
1702
1928
|
}
|
|
1703
|
-
|
|
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
|
+
};
|
|
1704
1937
|
this.transitionService.enqueue(
|
|
1705
1938
|
"matcap",
|
|
1706
1939
|
{ easing, duration },
|
|
1707
1940
|
{
|
|
1941
|
+
...options,
|
|
1942
|
+
onTransitionProgress: handleProgressUpdate,
|
|
1708
1943
|
onTransitionBegin: () => {
|
|
1709
|
-
|
|
1710
|
-
this.
|
|
1711
|
-
this.
|
|
1712
|
-
|
|
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
|
|
1713
1956
|
}
|
|
1714
1957
|
);
|
|
1715
1958
|
}
|
|
1716
|
-
scheduleTextureTransition(origin, destination, options) {
|
|
1959
|
+
scheduleTextureTransition(origin, destination, options = {}) {
|
|
1717
1960
|
const easing = (options == null ? void 0 : options.easing) ?? linear;
|
|
1718
1961
|
const duration = (options == null ? void 0 : options.duration) ?? 1e3;
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
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
|
+
};
|
|
1722
1975
|
this.transitionService.enqueue(
|
|
1723
1976
|
"matcap",
|
|
1977
|
+
// Still uses matcap type internally
|
|
1724
1978
|
{ easing, duration },
|
|
1725
1979
|
{
|
|
1980
|
+
...userCallbacks,
|
|
1981
|
+
// Pass user callbacks
|
|
1982
|
+
onTransitionProgress: handleProgressUpdate,
|
|
1726
1983
|
onTransitionBegin: () => {
|
|
1984
|
+
var _a;
|
|
1727
1985
|
this.setOriginTexture(origin);
|
|
1728
1986
|
this.setDestinationTexture(destination);
|
|
1729
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);
|
|
1730
1998
|
}
|
|
1731
1999
|
}
|
|
1732
2000
|
);
|
|
1733
2001
|
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Schedules a smooth transition for the overall mesh sequence progress.
|
|
2004
|
+
* @param targetProgress The final progress value (0.0 to 1.0) to transition to.
|
|
2005
|
+
* @param duration Duration of the transition in milliseconds.
|
|
2006
|
+
* @param easing Easing function to use.
|
|
2007
|
+
* @param options Transition options (onBegin, onProgress, onFinished, onCancelled).
|
|
2008
|
+
* @param override If true, cancels any ongoing mesh sequence transitions.
|
|
2009
|
+
*/
|
|
2010
|
+
scheduleMeshSequenceTransition(targetProgress, duration = 1e3, easing = linear, options = {}, override = true) {
|
|
2011
|
+
if (override) this.eventEmitter.emit("transitionCancelled", { type: "mesh-sequence" });
|
|
2012
|
+
const startProgress = this.engineState.overallProgress;
|
|
2013
|
+
const progressDiff = targetProgress - startProgress;
|
|
2014
|
+
const handleProgressUpdate = (transitionProgress) => {
|
|
2015
|
+
var _a;
|
|
2016
|
+
const currentOverallProgress = startProgress + progressDiff * transitionProgress;
|
|
2017
|
+
this.setOverallProgress(currentOverallProgress, false);
|
|
2018
|
+
(_a = options.onTransitionProgress) == null ? void 0 : _a.call(options, currentOverallProgress);
|
|
2019
|
+
};
|
|
2020
|
+
const transitionDetail = { duration, easing };
|
|
2021
|
+
const transitionOptions = {
|
|
2022
|
+
...options,
|
|
2023
|
+
onTransitionProgress: handleProgressUpdate,
|
|
2024
|
+
onTransitionBegin: options.onTransitionBegin,
|
|
2025
|
+
onTransitionFinished: () => {
|
|
2026
|
+
var _a;
|
|
2027
|
+
this.setOverallProgress(targetProgress, false);
|
|
2028
|
+
(_a = options.onTransitionFinished) == null ? void 0 : _a.call(options);
|
|
2029
|
+
},
|
|
2030
|
+
onTransitionCancelled: options.onTransitionCancelled
|
|
2031
|
+
};
|
|
2032
|
+
this.transitionService.enqueue("mesh-sequence", transitionDetail, transitionOptions);
|
|
2033
|
+
}
|
|
1734
2034
|
handleServiceStateUpdated({ type, state }) {
|
|
1735
2035
|
this.serviceStates[type] = state;
|
|
1736
2036
|
}
|
|
@@ -1743,23 +2043,43 @@ class ParticlesEngine {
|
|
|
1743
2043
|
getMatcapIDs() {
|
|
1744
2044
|
return this.assetService.getTextureIDs();
|
|
1745
2045
|
}
|
|
2046
|
+
getMeshes() {
|
|
2047
|
+
return this.assetService.getMeshes();
|
|
2048
|
+
}
|
|
2049
|
+
getTextures() {
|
|
2050
|
+
return this.assetService.getTextures();
|
|
2051
|
+
}
|
|
2052
|
+
getTextureSize() {
|
|
2053
|
+
return this.engineState.textureSize;
|
|
2054
|
+
}
|
|
2055
|
+
getUseIntersect() {
|
|
2056
|
+
return this.engineState.useIntersect;
|
|
2057
|
+
}
|
|
2058
|
+
getEngineStateSnapshot() {
|
|
2059
|
+
return { ...this.engineState };
|
|
2060
|
+
}
|
|
1746
2061
|
/**
|
|
1747
2062
|
* Disposes the resources used by the engine.
|
|
1748
2063
|
*/
|
|
1749
2064
|
dispose() {
|
|
1750
|
-
|
|
1751
|
-
this.
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
this.
|
|
1755
|
-
this.
|
|
2065
|
+
var _a, _b, _c, _d, _e, _f;
|
|
2066
|
+
if (this.scene && this.instancedMeshManager) {
|
|
2067
|
+
this.scene.remove(this.instancedMeshManager.getMesh());
|
|
2068
|
+
}
|
|
2069
|
+
(_a = this.simulationRendererService) == null ? void 0 : _a.dispose();
|
|
2070
|
+
(_b = this.instancedMeshManager) == null ? void 0 : _b.dispose();
|
|
2071
|
+
(_c = this.intersectionService) == null ? void 0 : _c.dispose();
|
|
2072
|
+
(_d = this.assetService) == null ? void 0 : _d.dispose();
|
|
2073
|
+
(_e = this.dataTextureManager) == null ? void 0 : _e.dispose();
|
|
2074
|
+
(_f = this.eventEmitter) == null ? void 0 : _f.dispose();
|
|
1756
2075
|
}
|
|
1757
2076
|
initialEngineState(params) {
|
|
1758
2077
|
return {
|
|
1759
2078
|
textureSize: params.textureSize,
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
2079
|
+
meshSequence: [],
|
|
2080
|
+
// ADDED
|
|
2081
|
+
overallProgress: 0,
|
|
2082
|
+
// ADDED
|
|
1763
2083
|
originMatcapID: "",
|
|
1764
2084
|
destinationMatcapID: "",
|
|
1765
2085
|
matcapTransitionProgress: 0,
|
|
@@ -1780,16 +2100,6 @@ class ParticlesEngine {
|
|
|
1780
2100
|
asset: "created"
|
|
1781
2101
|
};
|
|
1782
2102
|
}
|
|
1783
|
-
handleTransitionProgress({ type, progress }) {
|
|
1784
|
-
switch (type) {
|
|
1785
|
-
case "data-texture":
|
|
1786
|
-
this.setDataTextureTransitionProgress(progress);
|
|
1787
|
-
break;
|
|
1788
|
-
case "matcap":
|
|
1789
|
-
this.setMatcapProgress(progress);
|
|
1790
|
-
break;
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
2103
|
handleInteractionPositionUpdated({ position }) {
|
|
1794
2104
|
this.simulationRendererService.setInteractionPosition(position);
|
|
1795
2105
|
}
|