@polarfront-lab/ionian 1.0.6 → 1.1.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
@@ -3,6 +3,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
4
  import * as THREE from "three";
5
5
  import { Mesh, OrthographicCamera, BufferGeometry, Float32BufferAttribute, NearestFilter, ShaderMaterial, WebGLRenderTarget, FloatType, RGBAFormat, DataTexture, ClampToEdgeWrapping } from "three";
6
+ import { GLTFLoader, DRACOLoader } from "three-stdlib";
6
7
  const linear = (n) => n;
7
8
  function mitt(n) {
8
9
  return { all: n = n || /* @__PURE__ */ new Map(), on: function(t, e) {
@@ -2033,7 +2034,7 @@ var genericPoolExports = requireGenericPool();
2033
2034
  const genericPool = /* @__PURE__ */ getDefaultExportFromCjs(genericPoolExports);
2034
2035
  const workerFactory = {
2035
2036
  create: async () => {
2036
- const workerPath = new URL("data:video/mp2t;base64,aW1wb3J0IHsgTWVzaERhdGEgfSBmcm9tICdAL3R5cGVzJzsKaW1wb3J0ICogYXMgQ29tbGluayBmcm9tICdjb21saW5rJzsKaW1wb3J0ICogYXMgVEhSRUUgZnJvbSAndGhyZWUnOwppbXBvcnQgeyBNZXNoU3VyZmFjZVNhbXBsZXIgfSBmcm9tICd0aHJlZS9leGFtcGxlcy9qc20vbWF0aC9NZXNoU3VyZmFjZVNhbXBsZXIuanMnOwoKZXhwb3J0IGludGVyZmFjZSBNZXNoU2FtcGxlckFQSSB7CiAgc2FtcGxlTWVzaDogKG1lc2hEYXRhOiBNZXNoRGF0YSwgc2l6ZTogbnVtYmVyKSA9PiBQcm9taXNlPEZsb2F0MzJBcnJheT47Cn0KCmNvbnN0IGFwaSA9IHsKICBzYW1wbGVNZXNoOiAobWVzaERhdGE6IE1lc2hEYXRhLCBzaXplOiBudW1iZXIpOiBGbG9hdDMyQXJyYXkgPT4gewogICAgY29uc3QgZ2VvbWV0cnkgPSBuZXcgVEhSRUUuQnVmZmVyR2VvbWV0cnkoKTsKICAgIGdlb21ldHJ5LnNldEF0dHJpYnV0ZSgncG9zaXRpb24nLCBuZXcgVEhSRUUuQnVmZmVyQXR0cmlidXRlKG5ldyBGbG9hdDMyQXJyYXkobWVzaERhdGEucG9zaXRpb24pLCAzKSk7CiAgICBpZiAobWVzaERhdGEubm9ybWFsKSB7CiAgICAgIGdlb21ldHJ5LnNldEF0dHJpYnV0ZSgnbm9ybWFsJywgbmV3IFRIUkVFLkJ1ZmZlckF0dHJpYnV0ZShuZXcgRmxvYXQzMkFycmF5KG1lc2hEYXRhLm5vcm1hbCksIDMpKTsKICAgIH0KICAgIGNvbnN0IG1hdGVyaWFsID0gbmV3IFRIUkVFLk1lc2hCYXNpY01hdGVyaWFsKCk7CiAgICBjb25zdCBtZXNoID0gbmV3IFRIUkVFLk1lc2goZ2VvbWV0cnksIG1hdGVyaWFsKTsKICAgIG1lc2guc2NhbGUuc2V0KG1lc2hEYXRhLnNjYWxlLngsIG1lc2hEYXRhLnNjYWxlLnksIG1lc2hEYXRhLnNjYWxlLnopOwoKICAgIGNvbnN0IHNhbXBsZXIgPSBuZXcgTWVzaFN1cmZhY2VTYW1wbGVyKG1lc2gpLmJ1aWxkKCk7CiAgICBjb25zdCBkYXRhID0gbmV3IEZsb2F0MzJBcnJheShzaXplICogc2l6ZSAqIDQpOwogICAgY29uc3QgcG9zaXRpb24gPSBuZXcgVEhSRUUuVmVjdG9yMygpOwoKICAgIGZvciAobGV0IGkgPSAwOyBpIDwgc2l6ZTsgaSsrKSB7CiAgICAgIGZvciAobGV0IGogPSAwOyBqIDwgc2l6ZTsgaisrKSB7CiAgICAgICAgY29uc3QgaW5kZXggPSBpICogc2l6ZSArIGo7CiAgICAgICAgc2FtcGxlci5zYW1wbGUocG9zaXRpb24pOwogICAgICAgIGRhdGFbNCAqIGluZGV4XSA9IHBvc2l0aW9uLnggKiBtZXNoRGF0YS5zY2FsZS54OwogICAgICAgIGRhdGFbNCAqIGluZGV4ICsgMV0gPSBwb3NpdGlvbi55ICogbWVzaERhdGEuc2NhbGUueTsKICAgICAgICBkYXRhWzQgKiBpbmRleCArIDJdID0gcG9zaXRpb24ueiAqIG1lc2hEYXRhLnNjYWxlLno7CiAgICAgICAgZGF0YVs0ICogaW5kZXggKyAzXSA9IChNYXRoLnJhbmRvbSgpIC0gMC41KSAqIDAuMDE7CiAgICAgIH0KICAgIH0KCiAgICByZXR1cm4gZGF0YTsKICB9LAp9OwoKQ29tbGluay5leHBvc2UoYXBpKTsK", import.meta.url);
2037
+ const workerPath = new URL("data:video/mp2t;base64,aW1wb3J0IHsgTWVzaERhdGEgfSBmcm9tICdAL2xpYi90eXBlcyc7CmltcG9ydCAqIGFzIENvbWxpbmsgZnJvbSAnY29tbGluayc7CmltcG9ydCAqIGFzIFRIUkVFIGZyb20gJ3RocmVlJzsKaW1wb3J0IHsgTWVzaFN1cmZhY2VTYW1wbGVyIH0gZnJvbSAndGhyZWUvZXhhbXBsZXMvanNtL21hdGgvTWVzaFN1cmZhY2VTYW1wbGVyLmpzJzsKCmV4cG9ydCBpbnRlcmZhY2UgTWVzaFNhbXBsZXJBUEkgewogIHNhbXBsZU1lc2g6IChtZXNoRGF0YTogTWVzaERhdGEsIHNpemU6IG51bWJlcikgPT4gUHJvbWlzZTxGbG9hdDMyQXJyYXk+Owp9Cgpjb25zdCBhcGkgPSB7CiAgc2FtcGxlTWVzaDogKG1lc2hEYXRhOiBNZXNoRGF0YSwgc2l6ZTogbnVtYmVyKTogRmxvYXQzMkFycmF5ID0+IHsKICAgIGNvbnN0IGdlb21ldHJ5ID0gbmV3IFRIUkVFLkJ1ZmZlckdlb21ldHJ5KCk7CiAgICBnZW9tZXRyeS5zZXRBdHRyaWJ1dGUoJ3Bvc2l0aW9uJywgbmV3IFRIUkVFLkJ1ZmZlckF0dHJpYnV0ZShuZXcgRmxvYXQzMkFycmF5KG1lc2hEYXRhLnBvc2l0aW9uKSwgMykpOwogICAgaWYgKG1lc2hEYXRhLm5vcm1hbCkgewogICAgICBnZW9tZXRyeS5zZXRBdHRyaWJ1dGUoJ25vcm1hbCcsIG5ldyBUSFJFRS5CdWZmZXJBdHRyaWJ1dGUobmV3IEZsb2F0MzJBcnJheShtZXNoRGF0YS5ub3JtYWwpLCAzKSk7CiAgICB9CiAgICBjb25zdCBtYXRlcmlhbCA9IG5ldyBUSFJFRS5NZXNoQmFzaWNNYXRlcmlhbCgpOwogICAgY29uc3QgbWVzaCA9IG5ldyBUSFJFRS5NZXNoKGdlb21ldHJ5LCBtYXRlcmlhbCk7CiAgICBtZXNoLnNjYWxlLnNldChtZXNoRGF0YS5zY2FsZS54LCBtZXNoRGF0YS5zY2FsZS55LCBtZXNoRGF0YS5zY2FsZS56KTsKCiAgICBjb25zdCBzYW1wbGVyID0gbmV3IE1lc2hTdXJmYWNlU2FtcGxlcihtZXNoKS5idWlsZCgpOwogICAgY29uc3QgZGF0YSA9IG5ldyBGbG9hdDMyQXJyYXkoc2l6ZSAqIHNpemUgKiA0KTsKICAgIGNvbnN0IHBvc2l0aW9uID0gbmV3IFRIUkVFLlZlY3RvcjMoKTsKCiAgICBmb3IgKGxldCBpID0gMDsgaSA8IHNpemU7IGkrKykgewogICAgICBmb3IgKGxldCBqID0gMDsgaiA8IHNpemU7IGorKykgewogICAgICAgIGNvbnN0IGluZGV4ID0gaSAqIHNpemUgKyBqOwogICAgICAgIHNhbXBsZXIuc2FtcGxlKHBvc2l0aW9uKTsKICAgICAgICBkYXRhWzQgKiBpbmRleF0gPSBwb3NpdGlvbi54ICogbWVzaERhdGEuc2NhbGUueDsKICAgICAgICBkYXRhWzQgKiBpbmRleCArIDFdID0gcG9zaXRpb24ueSAqIG1lc2hEYXRhLnNjYWxlLnk7CiAgICAgICAgZGF0YVs0ICogaW5kZXggKyAyXSA9IHBvc2l0aW9uLnogKiBtZXNoRGF0YS5zY2FsZS56OwogICAgICAgIGRhdGFbNCAqIGluZGV4ICsgM10gPSAoTWF0aC5yYW5kb20oKSAtIDAuNSkgKiAwLjAxOwogICAgICB9CiAgICB9CgogICAgcmV0dXJuIGRhdGE7CiAgfSwKfTsKCkNvbWxpbmsuZXhwb3NlKGFwaSk7Cg==", import.meta.url);
2037
2038
  const worker = new Worker(workerPath, { type: "module" });
2038
2039
  return wrap(worker);
2039
2040
  },
@@ -2071,16 +2072,13 @@ function createSpherePoints(size) {
2071
2072
  }
2072
2073
  return createDataTexture(data, size);
2073
2074
  }
2074
- function copyOf(source) {
2075
- const map = /* @__PURE__ */ new Map();
2076
- if (source) {
2077
- if (Array.isArray(source)) {
2078
- source.forEach(({ id, item }) => map.set(id, item));
2079
- } else {
2080
- map.set(source.id, source.item);
2081
- }
2075
+ function disposeMesh(mesh) {
2076
+ mesh.geometry.dispose();
2077
+ if (mesh.material instanceof THREE.Material) {
2078
+ mesh.material.dispose();
2079
+ } else {
2080
+ mesh.material.forEach((material) => material.dispose());
2082
2081
  }
2083
- return map;
2084
2082
  }
2085
2083
  function clamp(value, min, max) {
2086
2084
  value = Math.min(value, max);
@@ -2092,71 +2090,44 @@ class DataTextureService {
2092
2090
  * Creates a new DataTextureManager instance.
2093
2091
  * @param eventEmitter
2094
2092
  * @param textureSize
2095
- * @param meshes Optional initial meshes.
2096
2093
  */
2097
- constructor(eventEmitter, textureSize, meshes) {
2094
+ constructor(eventEmitter, textureSize) {
2098
2095
  __publicField(this, "textureSize");
2099
- __publicField(this, "meshes");
2100
2096
  __publicField(this, "dataTextures");
2101
2097
  __publicField(this, "eventEmitter");
2102
2098
  this.eventEmitter = eventEmitter;
2103
2099
  this.textureSize = textureSize;
2104
- this.meshes = copyOf(meshes);
2105
2100
  this.dataTextures = /* @__PURE__ */ new Map();
2106
2101
  this.updateServiceState("ready");
2107
2102
  }
2108
- /**
2109
- * Registers a mesh.
2110
- * @param id The ID of the mesh.
2111
- * @param mesh The mesh to register.
2112
- */
2113
- async register(id, mesh) {
2114
- this.meshes.set(id, mesh);
2115
- }
2116
2103
  setTextureSize(textureSize) {
2117
2104
  if (this.textureSize === textureSize) return;
2118
2105
  this.textureSize = textureSize;
2119
2106
  this.dataTextures.forEach((texture) => texture.dispose());
2120
2107
  this.dataTextures.clear();
2121
2108
  }
2122
- getMesh(id) {
2123
- return this.meshes.get(id);
2124
- }
2125
- /**
2126
- * Gets the data texture for the specified mesh ID and current texture size.
2127
- * Returns the fallback data texture if the specified mesh ID is not found.
2128
- * @param id The ID of the mesh.
2129
- * @returns The data texture, or undefined if not found and no fallback is available.
2130
- */
2131
- async getDataTexture(id) {
2132
- return await this.prepareMesh(id);
2133
- }
2134
2109
  /**
2135
2110
  * Prepares a mesh for sampling.
2136
- * @param id The ID of the mesh to prepare.
2111
+ * @returns The prepared data texture.
2112
+ * @param asset The asset to prepare.
2137
2113
  */
2138
- async prepareMesh(id) {
2139
- if (!this.meshes.has(id)) {
2140
- throw new Error(`Mesh with id "${id}" does not exist.`);
2141
- }
2142
- const texture = this.dataTextures.get(id);
2114
+ async getDataTexture(asset) {
2115
+ const texture = this.dataTextures.get(asset.name);
2143
2116
  if (texture) {
2144
2117
  return texture;
2145
2118
  }
2146
- const mesh = this.meshes.get(id);
2147
- const meshData = parseMeshData(mesh);
2119
+ const meshData = parseMeshData(asset);
2148
2120
  const worker = await pool.acquire();
2149
2121
  try {
2150
- const data = await worker.sampleMesh(meshData, this.textureSize);
2151
- const texture2 = createDataTexture(data, this.textureSize);
2152
- texture2.name = id;
2153
- return texture2;
2122
+ const array = await worker.sampleMesh(meshData, this.textureSize);
2123
+ const dataTexture = createDataTexture(array, this.textureSize);
2124
+ dataTexture.name = asset.name;
2125
+ return dataTexture;
2154
2126
  } finally {
2155
2127
  await pool.release(worker);
2156
2128
  }
2157
2129
  }
2158
2130
  async dispose() {
2159
- this.meshes.clear();
2160
2131
  this.dataTextures.clear();
2161
2132
  this.updateServiceState("disposed");
2162
2133
  }
@@ -2298,11 +2269,9 @@ class InstancedMeshManager {
2298
2269
  * @param matcap The matcap texture to set.
2299
2270
  */
2300
2271
  setOriginMatcap(matcap) {
2301
- console.log("set source matcap", matcap);
2302
2272
  this.matcapMaterial.uniforms.uSourceMatcap.value = matcap;
2303
2273
  }
2304
2274
  setDestinationMatcap(matcap) {
2305
- console.log("set target matcap", matcap);
2306
2275
  this.matcapMaterial.uniforms.uTargetMatcap.value = matcap;
2307
2276
  }
2308
2277
  setProgress(float) {
@@ -2319,7 +2288,6 @@ class InstancedMeshManager {
2319
2288
  * Use the matcap material for the instanced mesh.
2320
2289
  */
2321
2290
  useMatcapMaterial() {
2322
- console.log("Using matcap material");
2323
2291
  this.mesh.material = this.matcapMaterial;
2324
2292
  }
2325
2293
  /**
@@ -2330,8 +2298,6 @@ class InstancedMeshManager {
2330
2298
  const material = this.materials.get(id);
2331
2299
  if (material) {
2332
2300
  this.mesh.material = material;
2333
- } else {
2334
- console.warn(`material with id "${id}" not found`);
2335
2301
  }
2336
2302
  }
2337
2303
  /**
@@ -2342,8 +2308,6 @@ class InstancedMeshManager {
2342
2308
  const geometry = this.geometries.get(id);
2343
2309
  if (geometry) {
2344
2310
  this.mesh.geometry = geometry;
2345
- } else {
2346
- console.warn(`geometry with id "${id}" not found`);
2347
2311
  }
2348
2312
  }
2349
2313
  /**
@@ -2394,7 +2358,6 @@ class InstancedMeshManager {
2394
2358
  if (previous === geometry) {
2395
2359
  return;
2396
2360
  }
2397
- console.log(`geometry with id "${id}" already exists. replacing...`);
2398
2361
  }
2399
2362
  const uvRefs = this.getUVRefs(this.size);
2400
2363
  geometry.setAttribute("uvRef", uvRefs);
@@ -2415,7 +2378,6 @@ class InstancedMeshManager {
2415
2378
  if (previous === material) {
2416
2379
  return;
2417
2380
  }
2418
- console.log(`material with id "${id}" already exists. replacing...`);
2419
2381
  }
2420
2382
  if (this.mesh.material === previous) {
2421
2383
  this.mesh.material = material;
@@ -2470,8 +2432,6 @@ class IntersectionService {
2470
2432
  constructor(eventEmitter, camera, originGeometry, destinationGeometry) {
2471
2433
  __publicField(this, "raycaster", new THREE.Raycaster());
2472
2434
  __publicField(this, "mousePosition", new THREE.Vector2());
2473
- __publicField(this, "mouseEntered", false);
2474
- __publicField(this, "mousePositionChanged", false);
2475
2435
  __publicField(this, "camera");
2476
2436
  __publicField(this, "originGeometry");
2477
2437
  __publicField(this, "destinationGeometry");
@@ -2489,6 +2449,9 @@ class IntersectionService {
2489
2449
  this.destinationGeometry = destinationGeometry;
2490
2450
  this.geometryNeedsUpdate = true;
2491
2451
  }
2452
+ getIntersectionMesh() {
2453
+ return this.intersectionMesh;
2454
+ }
2492
2455
  /**
2493
2456
  * Set the camera used for raycasting.
2494
2457
  * @param camera
@@ -2533,36 +2496,23 @@ class IntersectionService {
2533
2496
  * @param mousePosition
2534
2497
  */
2535
2498
  setMousePosition(mousePosition) {
2536
- if (mousePosition) {
2537
- if (!this.mousePosition.equals(mousePosition)) {
2538
- this.mousePosition.copy(mousePosition);
2539
- this.mousePositionChanged = true;
2540
- }
2541
- this.mouseEntered = true;
2542
- } else {
2543
- this.mouseEntered = false;
2544
- this.mousePositionChanged = false;
2545
- }
2499
+ if (mousePosition) this.mousePosition.copy(mousePosition);
2546
2500
  }
2547
2501
  /**
2548
2502
  * Calculate the intersection.
2549
2503
  * @returns The intersection point or undefined if no intersection was found.
2550
2504
  */
2551
- calculate() {
2505
+ calculate(instancedMesh) {
2506
+ this.updateIntersectionMesh(instancedMesh);
2552
2507
  if (!this.camera) return;
2553
- if (!this.mouseEntered) return;
2554
2508
  if (this.geometryNeedsUpdate) {
2555
2509
  this.geometryNeedsUpdate = false;
2556
2510
  this.blendedGeometry = this.getBlendedGeometry();
2557
- this.mousePositionChanged = true;
2558
2511
  }
2559
- if (this.mousePositionChanged) {
2560
- this.mousePositionChanged = false;
2561
- if (this.blendedGeometry) {
2562
- this.intersection = this.getFirstIntersection(this.blendedGeometry, this.camera);
2563
- } else {
2564
- this.intersection = void 0;
2565
- }
2512
+ if (this.blendedGeometry) {
2513
+ this.intersection = this.getFirstIntersection(this.camera, instancedMesh);
2514
+ } else {
2515
+ this.intersection = void 0;
2566
2516
  }
2567
2517
  if (this.intersection) {
2568
2518
  this.eventEmitter.emit("interactionPositionUpdated", { position: this.intersection });
@@ -2579,12 +2529,25 @@ class IntersectionService {
2579
2529
  (_a = this.blendedGeometry) == null ? void 0 : _a.dispose();
2580
2530
  this.intersectionMesh.geometry.dispose();
2581
2531
  }
2582
- getFirstIntersection(geometry, camera) {
2532
+ updateIntersectionMesh(instancedMesh) {
2533
+ if (this.blendedGeometry) {
2534
+ if (this.blendedGeometry.uuid !== this.intersectionMesh.geometry.uuid) {
2535
+ this.intersectionMesh.geometry.dispose();
2536
+ this.intersectionMesh.geometry = this.blendedGeometry;
2537
+ }
2538
+ }
2539
+ this.intersectionMesh.matrix.copy(instancedMesh.matrixWorld);
2540
+ this.intersectionMesh.matrixWorld.copy(instancedMesh.matrixWorld);
2541
+ this.intersectionMesh.matrixAutoUpdate = false;
2542
+ this.intersectionMesh.updateMatrixWorld(true);
2543
+ }
2544
+ getFirstIntersection(camera, instancedMesh) {
2583
2545
  this.raycaster.setFromCamera(this.mousePosition, camera);
2584
- this.intersectionMesh.geometry = geometry;
2585
2546
  const intersection = this.raycaster.intersectObject(this.intersectionMesh, false)[0];
2586
2547
  if (intersection) {
2587
- return new THREE.Vector4(intersection.point.x, intersection.point.y, intersection.point.z, 1);
2548
+ const worldPoint = intersection.point.clone();
2549
+ const localPoint = instancedMesh.worldToLocal(worldPoint);
2550
+ return new THREE.Vector4(localPoint.x, localPoint.y, localPoint.z, 1);
2588
2551
  }
2589
2552
  }
2590
2553
  getBlendedGeometry() {
@@ -2622,46 +2585,6 @@ class IntersectionService {
2622
2585
  return blended;
2623
2586
  }
2624
2587
  }
2625
- class MatcapService {
2626
- constructor(eventEmitter, matcaps) {
2627
- __publicField(this, "matcaps", /* @__PURE__ */ new Map());
2628
- __publicField(this, "eventEmitter");
2629
- __publicField(this, "fallbackMatcap", new THREE.DataTexture(new Uint8Array([127, 127, 127, 255]), 1, 1, THREE.RGBAFormat));
2630
- this.eventEmitter = eventEmitter;
2631
- if (matcaps) {
2632
- matcaps.forEach(({ id, item }) => this.setMatcap(id, item));
2633
- }
2634
- this.updateServiceState("ready");
2635
- }
2636
- getMatcap(id) {
2637
- const texture = this.matcaps.get(id);
2638
- if (!texture) {
2639
- this.eventEmitter.emit("invalidRequest", { message: `invalid matcap request: ${id}` });
2640
- return this.fallbackMatcap;
2641
- } else {
2642
- return texture;
2643
- }
2644
- }
2645
- setMatcap(id, texture) {
2646
- const previous = this.matcaps.get(id);
2647
- if (previous === texture) return;
2648
- this.matcaps.set(id, texture);
2649
- if (previous) {
2650
- this.eventEmitter.emit("matcapReplaced", { id });
2651
- previous.dispose();
2652
- } else {
2653
- this.eventEmitter.emit("matcapRegistered", { id });
2654
- }
2655
- }
2656
- dispose() {
2657
- this.updateServiceState("disposed");
2658
- this.matcaps.forEach((texture) => texture.dispose());
2659
- this.matcaps.clear();
2660
- }
2661
- updateServiceState(serviceState) {
2662
- this.eventEmitter.emit("serviceStateUpdated", { type: "matcap", state: serviceState });
2663
- }
2664
- }
2665
2588
  const _camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
2666
2589
  class FullscreenTriangleGeometry extends BufferGeometry {
2667
2590
  constructor() {
@@ -3264,6 +3187,122 @@ class TransitionService {
3264
3187
  this.eventEmitter.emit("transitionFinished", { type });
3265
3188
  }
3266
3189
  }
3190
+ class AssetService {
3191
+ constructor(eventEmitter) {
3192
+ __publicField(this, "serviceState", "created");
3193
+ __publicField(this, "eventEmitter");
3194
+ __publicField(this, "meshes", /* @__PURE__ */ new Map());
3195
+ __publicField(this, "textures", /* @__PURE__ */ new Map());
3196
+ __publicField(this, "gltfLoader", new GLTFLoader());
3197
+ __publicField(this, "textureLoader", new THREE.TextureLoader());
3198
+ __publicField(this, "dracoLoader", new DRACOLoader());
3199
+ __publicField(this, "solidColorTexture", new THREE.DataTexture(new Uint8Array([127, 127, 127, 255]), 1, 1, THREE.RGBAFormat));
3200
+ this.eventEmitter = eventEmitter;
3201
+ this.dracoLoader.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.5.7/");
3202
+ this.gltfLoader.setDRACOLoader(this.dracoLoader);
3203
+ this.updateServiceState("ready");
3204
+ }
3205
+ /**
3206
+ * Registers an asset.
3207
+ * @param id - The ID of the asset.
3208
+ * @param item - The asset to set.
3209
+ */
3210
+ register(id, item) {
3211
+ item.name = id;
3212
+ if (item instanceof THREE.Mesh) {
3213
+ const prev = this.meshes.get(id);
3214
+ if (prev) disposeMesh(prev);
3215
+ this.meshes.set(id, item);
3216
+ } else {
3217
+ const prev = this.textures.get(id);
3218
+ if (prev) prev.dispose();
3219
+ this.textures.set(id, item);
3220
+ }
3221
+ this.eventEmitter.emit("assetRegistered", { id });
3222
+ }
3223
+ setSolidColor(color) {
3224
+ this.changeColor(color);
3225
+ }
3226
+ getSolidColorTexture() {
3227
+ return this.solidColorTexture;
3228
+ }
3229
+ getMesh(id) {
3230
+ return this.meshes.get(id) ?? null;
3231
+ }
3232
+ getMatcap(id) {
3233
+ const texture = this.textures.get(id);
3234
+ if (!texture) this.eventEmitter.emit("invalidRequest", { message: `texture with id "${id}" not found. using solid color texture instead...` });
3235
+ return texture ?? this.solidColorTexture;
3236
+ }
3237
+ getMeshIDs() {
3238
+ return Array.from(this.meshes.keys());
3239
+ }
3240
+ getTextureIDs() {
3241
+ return Array.from(this.textures.keys());
3242
+ }
3243
+ getMeshes() {
3244
+ return Array.from(this.meshes.values());
3245
+ }
3246
+ getTextures() {
3247
+ return Array.from(this.textures.values());
3248
+ }
3249
+ /**
3250
+ * Loads a mesh asynchronously.
3251
+ * @param id - The ID of the mesh.
3252
+ * @param url - The URL of the mesh.
3253
+ * @param options - Optional parameters.
3254
+ * @returns The loaded mesh or null.
3255
+ */
3256
+ async loadMeshAsync(id, url, options = {}) {
3257
+ const gltf = await this.gltfLoader.loadAsync(url);
3258
+ try {
3259
+ if (options.meshName) {
3260
+ const mesh = gltf.scene.getObjectByName(options.meshName);
3261
+ this.register(id, mesh);
3262
+ return mesh;
3263
+ } else {
3264
+ const mesh = gltf.scene.children[0];
3265
+ this.register(id, mesh);
3266
+ return mesh;
3267
+ }
3268
+ } catch (error) {
3269
+ this.eventEmitter.emit("invalidRequest", { message: `failed to load mesh: ${id}. ${error}` });
3270
+ return null;
3271
+ }
3272
+ }
3273
+ /**
3274
+ * Loads a texture asynchronously.
3275
+ * @param id - The ID of the texture.
3276
+ * @param url - The URL of the texture.
3277
+ * @returns The loaded texture or null.
3278
+ */
3279
+ async loadTextureAsync(id, url) {
3280
+ try {
3281
+ const texture = await this.textureLoader.loadAsync(url);
3282
+ this.register(id, texture);
3283
+ return texture;
3284
+ } catch (error) {
3285
+ this.eventEmitter.emit("invalidRequest", { message: `failed to load texture: ${id}. ${error}` });
3286
+ return null;
3287
+ }
3288
+ }
3289
+ dispose() {
3290
+ this.updateServiceState("disposed");
3291
+ this.meshes.forEach((mesh) => disposeMesh(mesh));
3292
+ this.meshes.clear();
3293
+ this.textures.forEach((texture) => texture.dispose());
3294
+ this.textures.clear();
3295
+ }
3296
+ changeColor(color) {
3297
+ const actual = new THREE.Color(color);
3298
+ this.solidColorTexture = new THREE.DataTexture(new Uint8Array([actual.r, actual.g, actual.b, 255]), 1, 1, THREE.RGBAFormat);
3299
+ this.solidColorTexture.needsUpdate = true;
3300
+ }
3301
+ updateServiceState(serviceState) {
3302
+ this.serviceState = serviceState;
3303
+ this.eventEmitter.emit("serviceStateUpdated", { type: "asset", state: serviceState });
3304
+ }
3305
+ }
3267
3306
  class ParticlesEngine {
3268
3307
  /**
3269
3308
  * Creates a new ParticlesEngine instance.
@@ -3276,8 +3315,8 @@ class ParticlesEngine {
3276
3315
  __publicField(this, "scene");
3277
3316
  __publicField(this, "serviceStates");
3278
3317
  // assets
3318
+ __publicField(this, "assetService");
3279
3319
  __publicField(this, "dataTextureManager");
3280
- __publicField(this, "matcapService");
3281
3320
  __publicField(this, "instancedMeshManager");
3282
3321
  __publicField(this, "transitionService");
3283
3322
  __publicField(this, "engineState");
@@ -3288,9 +3327,9 @@ class ParticlesEngine {
3288
3327
  this.scene = params.scene;
3289
3328
  this.renderer = params.renderer;
3290
3329
  this.engineState = this.initialEngineState(params.textureSize);
3330
+ this.assetService = new AssetService(this.eventEmitter);
3291
3331
  this.transitionService = new TransitionService(this.eventEmitter);
3292
- this.dataTextureManager = new DataTextureService(this.eventEmitter, params.textureSize, params.meshes);
3293
- this.matcapService = new MatcapService(this.eventEmitter, params.matcaps);
3332
+ this.dataTextureManager = new DataTextureService(this.eventEmitter, params.textureSize);
3294
3333
  this.simulationRendererService = new SimulationRendererService(this.eventEmitter, params.textureSize, this.renderer);
3295
3334
  this.instancedMeshManager = new InstancedMeshManager(params.textureSize);
3296
3335
  this.instancedMeshManager.useMatcapMaterial();
@@ -3304,7 +3343,7 @@ class ParticlesEngine {
3304
3343
  * @param elapsedTime The elapsed time since the last frame.
3305
3344
  */
3306
3345
  render(elapsedTime) {
3307
- this.intersectionService.calculate();
3346
+ this.intersectionService.calculate(this.instancedMeshManager.getMesh());
3308
3347
  this.transitionService.compute(elapsedTime);
3309
3348
  this.simulationRendererService.compute(elapsedTime);
3310
3349
  this.instancedMeshManager.update(elapsedTime);
@@ -3313,24 +3352,31 @@ class ParticlesEngine {
3313
3352
  }
3314
3353
  setOriginDataTexture(meshID, override = false) {
3315
3354
  if (override) this.eventEmitter.emit("transitionCancelled", { type: "data-texture" });
3316
- this.dataTextureManager.getDataTexture(meshID).then((texture) => {
3355
+ const mesh = this.assetService.getMesh(meshID);
3356
+ if (!mesh) {
3357
+ this.eventEmitter.emit("invalidRequest", { message: `Mesh with id "${meshID}" does not exist` });
3358
+ return;
3359
+ }
3360
+ this.dataTextureManager.getDataTexture(mesh).then((dataTexture) => {
3317
3361
  this.engineState.originMeshID = meshID;
3318
- this.simulationRendererService.setOriginDataTexture({
3319
- dataTexture: texture,
3320
- textureSize: this.engineState.textureSize
3321
- });
3322
- this.intersectionService.setOriginGeometry(this.dataTextureManager.getMesh(meshID));
3362
+ this.simulationRendererService.setOriginDataTexture({ dataTexture, textureSize: this.engineState.textureSize });
3363
+ this.intersectionService.setOriginGeometry(mesh);
3323
3364
  });
3324
3365
  }
3325
3366
  setDestinationDataTexture(meshID, override = false) {
3326
3367
  if (override) this.eventEmitter.emit("transitionCancelled", { type: "data-texture" });
3327
- this.dataTextureManager.getDataTexture(meshID).then((texture) => {
3368
+ const mesh = this.assetService.getMesh(meshID);
3369
+ if (!mesh) {
3370
+ this.eventEmitter.emit("invalidRequest", { message: `Mesh with id "${meshID}" does not exist` });
3371
+ return;
3372
+ }
3373
+ this.dataTextureManager.getDataTexture(mesh).then((texture) => {
3328
3374
  this.engineState.destinationMeshID = meshID;
3329
3375
  this.simulationRendererService.setDestinationDataTexture({
3330
3376
  dataTexture: texture,
3331
3377
  textureSize: this.engineState.textureSize
3332
3378
  });
3333
- this.intersectionService.setDestinationGeometry(this.dataTextureManager.getMesh(meshID));
3379
+ this.intersectionService.setDestinationGeometry(mesh);
3334
3380
  });
3335
3381
  }
3336
3382
  setDataTextureTransitionProgress(progress, override = false) {
@@ -3342,12 +3388,12 @@ class ParticlesEngine {
3342
3388
  setOriginMatcap(matcapID, override = false) {
3343
3389
  if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
3344
3390
  this.engineState.originMatcapID = matcapID;
3345
- this.instancedMeshManager.setOriginMatcap(this.matcapService.getMatcap(matcapID));
3391
+ this.instancedMeshManager.setOriginMatcap(this.assetService.getMatcap(matcapID));
3346
3392
  }
3347
3393
  setDestinationMatcap(matcapID, override = false) {
3348
3394
  if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
3349
3395
  this.engineState.destinationMatcapID = matcapID;
3350
- this.instancedMeshManager.setDestinationMatcap(this.matcapService.getMatcap(matcapID));
3396
+ this.instancedMeshManager.setDestinationMatcap(this.assetService.getMatcap(matcapID));
3351
3397
  }
3352
3398
  setMatcapProgress(progress, override = false) {
3353
3399
  if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
@@ -3359,8 +3405,18 @@ class ParticlesEngine {
3359
3405
  this.dataTextureManager.setTextureSize(size);
3360
3406
  this.simulationRendererService.setTextureSize(size);
3361
3407
  this.instancedMeshManager.resize(size);
3362
- this.dataTextureManager.getDataTexture(this.engineState.originMeshID).then((texture) => this.simulationRendererService.setOriginDataTexture({ dataTexture: texture, textureSize: size }));
3363
- this.dataTextureManager.getDataTexture(this.engineState.destinationMeshID).then(
3408
+ const originMesh = this.assetService.getMesh(this.engineState.originMeshID);
3409
+ if (!originMesh) {
3410
+ this.eventEmitter.emit("invalidRequest", { message: `Mesh with id "${this.engineState.originMeshID}" does not exist` });
3411
+ return;
3412
+ }
3413
+ const destinationMesh = this.assetService.getMesh(this.engineState.destinationMeshID);
3414
+ if (!destinationMesh) {
3415
+ this.eventEmitter.emit("invalidRequest", { message: `Mesh with id "${this.engineState.destinationMeshID}" does not exist` });
3416
+ return;
3417
+ }
3418
+ this.dataTextureManager.getDataTexture(originMesh).then((texture) => this.simulationRendererService.setOriginDataTexture({ dataTexture: texture, textureSize: size }));
3419
+ this.dataTextureManager.getDataTexture(destinationMesh).then(
3364
3420
  (texture) => this.simulationRendererService.setDestinationDataTexture({
3365
3421
  dataTexture: texture,
3366
3422
  textureSize: size
@@ -3369,11 +3425,23 @@ class ParticlesEngine {
3369
3425
  this.simulationRendererService.setDataTextureTransitionProgress(this.engineState.dataTextureTransitionProgress);
3370
3426
  this.simulationRendererService.setVelocityTractionForce(this.engineState.velocityTractionForce);
3371
3427
  this.simulationRendererService.setPositionalTractionForce(this.engineState.positionalTractionForce);
3372
- this.instancedMeshManager.setOriginMatcap(this.matcapService.getMatcap(this.engineState.originMatcapID));
3373
- this.instancedMeshManager.setDestinationMatcap(this.matcapService.getMatcap(this.engineState.destinationMatcapID));
3428
+ this.instancedMeshManager.setOriginMatcap(this.assetService.getMatcap(this.engineState.originMatcapID));
3429
+ this.instancedMeshManager.setDestinationMatcap(this.assetService.getMatcap(this.engineState.destinationMatcapID));
3374
3430
  this.instancedMeshManager.setProgress(this.engineState.matcapTransitionProgress);
3375
3431
  this.instancedMeshManager.setGeometrySize(this.engineState.instanceGeometryScale);
3376
3432
  }
3433
+ registerMesh(id, mesh) {
3434
+ this.assetService.register(id, mesh);
3435
+ }
3436
+ registerMatcap(id, matcap) {
3437
+ this.assetService.register(id, matcap);
3438
+ }
3439
+ async fetchAndRegisterMesh(id, url) {
3440
+ return await this.assetService.loadMeshAsync(id, url);
3441
+ }
3442
+ async fetchAndRegisterMatcap(id, url) {
3443
+ return await this.assetService.loadTextureAsync(id, url);
3444
+ }
3377
3445
  setPointerPosition(position) {
3378
3446
  this.engineState.pointerPosition = position;
3379
3447
  this.intersectionService.setMousePosition(position);
@@ -3421,19 +3489,27 @@ class ParticlesEngine {
3421
3489
  );
3422
3490
  }
3423
3491
  handleServiceStateUpdated({ type, state }) {
3424
- console.log("service state updated", type, state);
3425
3492
  this.serviceStates[type] = state;
3426
3493
  }
3494
+ getObject() {
3495
+ return this.instancedMeshManager.getMesh();
3496
+ }
3497
+ getMeshIDs() {
3498
+ return this.assetService.getMeshIDs();
3499
+ }
3500
+ getMatcapIDs() {
3501
+ return this.assetService.getTextureIDs();
3502
+ }
3427
3503
  /**
3428
3504
  * Disposes the resources used by the engine.
3429
3505
  */
3430
3506
  dispose() {
3431
3507
  this.scene.remove(this.instancedMeshManager.getMesh());
3432
- this.matcapService.dispose();
3433
3508
  this.simulationRendererService.dispose();
3434
3509
  this.instancedMeshManager.dispose();
3435
3510
  this.intersectionService.dispose();
3436
- this.dataTextureManager.dispose().then(() => console.log("engine disposed"));
3511
+ this.assetService.dispose();
3512
+ this.dataTextureManager.dispose();
3437
3513
  }
3438
3514
  initialEngineState(textureSize) {
3439
3515
  return {
@@ -3456,7 +3532,8 @@ class ParticlesEngine {
3456
3532
  "data-texture": "created",
3457
3533
  "instanced-mesh": "created",
3458
3534
  matcap: "created",
3459
- simulation: "created"
3535
+ simulation: "created",
3536
+ asset: "created"
3460
3537
  };
3461
3538
  }
3462
3539
  handleTransitionProgress({ type, progress }) {