@polarfront-lab/ionian 1.2.0 → 1.4.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
@@ -83,6 +83,125 @@ function clamp(value, min, max) {
83
83
  value = Math.max(value, min);
84
84
  return value;
85
85
  }
86
+ class AssetService {
87
+ constructor(eventEmitter) {
88
+ __publicField(this, "serviceState", "created");
89
+ __publicField(this, "eventEmitter");
90
+ __publicField(this, "meshes", /* @__PURE__ */ new Map());
91
+ __publicField(this, "textures", /* @__PURE__ */ new Map());
92
+ __publicField(this, "gltfLoader", new GLTFLoader());
93
+ __publicField(this, "textureLoader", new THREE.TextureLoader());
94
+ __publicField(this, "dracoLoader", new DRACOLoader());
95
+ __publicField(this, "solidColorTexture", new THREE.DataTexture(new Uint8Array([127, 127, 127, 255]), 1, 1, THREE.RGBAFormat));
96
+ this.eventEmitter = eventEmitter;
97
+ this.dracoLoader.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.5.7/");
98
+ this.gltfLoader.setDRACOLoader(this.dracoLoader);
99
+ this.updateServiceState("ready");
100
+ }
101
+ /**
102
+ * Registers an asset.
103
+ * @param id - The ID of the asset.
104
+ * @param item - The asset to set.
105
+ */
106
+ register(id, item) {
107
+ item.name = id;
108
+ if (item instanceof THREE.Mesh) {
109
+ const prev = this.meshes.get(id);
110
+ if (prev) disposeMesh(prev);
111
+ this.meshes.set(id, item);
112
+ } else {
113
+ const prev = this.textures.get(id);
114
+ if (prev) prev.dispose();
115
+ this.textures.set(id, item);
116
+ }
117
+ this.eventEmitter.emit("assetRegistered", { id });
118
+ }
119
+ setSolidColor(color) {
120
+ this.changeColor(color);
121
+ }
122
+ getSolidColorTexture() {
123
+ return this.solidColorTexture;
124
+ }
125
+ getMesh(id) {
126
+ return this.meshes.get(id) ?? null;
127
+ }
128
+ getMatcap(id) {
129
+ const texture = this.textures.get(id);
130
+ if (!texture) this.eventEmitter.emit("invalidRequest", { message: `texture with id "${id}" not found. using solid color texture instead...` });
131
+ return texture ?? this.solidColorTexture;
132
+ }
133
+ getMeshIDs() {
134
+ return Array.from(this.meshes.keys());
135
+ }
136
+ getTextureIDs() {
137
+ return Array.from(this.textures.keys());
138
+ }
139
+ getMeshes() {
140
+ return Array.from(this.meshes.values());
141
+ }
142
+ getTextures() {
143
+ return Array.from(this.textures.values());
144
+ }
145
+ hasMatcap(id) {
146
+ return this.textures.has(id);
147
+ }
148
+ /**
149
+ * Loads a mesh asynchronously.
150
+ * @param id - The ID of the mesh.
151
+ * @param url - The URL of the mesh.
152
+ * @param options - Optional parameters.
153
+ * @returns The loaded mesh or null.
154
+ */
155
+ async loadMeshAsync(id, url, options = {}) {
156
+ const gltf = await this.gltfLoader.loadAsync(url);
157
+ try {
158
+ if (options.meshName) {
159
+ const mesh = gltf.scene.getObjectByName(options.meshName);
160
+ this.register(id, mesh);
161
+ return mesh;
162
+ } else {
163
+ const mesh = gltf.scene.children[0];
164
+ this.register(id, mesh);
165
+ return mesh;
166
+ }
167
+ } catch (error) {
168
+ this.eventEmitter.emit("invalidRequest", { message: `failed to load mesh: ${id}. ${error}` });
169
+ return null;
170
+ }
171
+ }
172
+ /**
173
+ * Loads a texture asynchronously.
174
+ * @param id - The ID of the texture.
175
+ * @param url - The URL of the texture.
176
+ * @returns The loaded texture or null.
177
+ */
178
+ async loadTextureAsync(id, url) {
179
+ try {
180
+ const texture = await this.textureLoader.loadAsync(url);
181
+ this.register(id, texture);
182
+ return texture;
183
+ } catch (error) {
184
+ this.eventEmitter.emit("invalidRequest", { message: `failed to load texture: ${id}. ${error}` });
185
+ return null;
186
+ }
187
+ }
188
+ dispose() {
189
+ this.updateServiceState("disposed");
190
+ this.meshes.forEach((mesh) => disposeMesh(mesh));
191
+ this.meshes.clear();
192
+ this.textures.forEach((texture) => texture.dispose());
193
+ this.textures.clear();
194
+ }
195
+ changeColor(color) {
196
+ const actual = new THREE.Color(color);
197
+ this.solidColorTexture = new THREE.DataTexture(new Uint8Array([actual.r, actual.g, actual.b, 255]), 1, 1, THREE.RGBAFormat);
198
+ this.solidColorTexture.needsUpdate = true;
199
+ }
200
+ updateServiceState(serviceState) {
201
+ this.serviceState = serviceState;
202
+ this.eventEmitter.emit("serviceStateUpdated", { type: "asset", state: serviceState });
203
+ }
204
+ }
86
205
  const _face = new Triangle();
87
206
  const _color = new Vector3();
88
207
  const _uva = new Vector2(), _uvb = new Vector2(), _uvc = new Vector2();
@@ -295,8 +414,8 @@ const instanceFragmentShader = `
295
414
  varying vec2 vUv;
296
415
  uniform sampler2D uTexture;
297
416
 
298
- uniform sampler2D uSourceMatcap;
299
- uniform sampler2D uTargetMatcap;
417
+ uniform sampler2D uOriginTexture;
418
+ uniform sampler2D uDestinationTexture;
300
419
 
301
420
  uniform float uProgress;
302
421
  varying vec3 vNormal;
@@ -307,8 +426,8 @@ void main() {
307
426
  vec3 y = cross( viewDir, x );
308
427
  vec2 uv = vec2( dot( x, vNormal ), dot( y, vNormal ) ) * 0.495 + 0.5; // 0.495 to remove artifacts caused by undersized matcap disks
309
428
 
310
- vec4 sourceMatcap = texture2D( uSourceMatcap, uv );
311
- vec4 targetMatcap = texture2D( uTargetMatcap, uv );
429
+ vec4 sourceMatcap = texture2D( uOriginTexture, uv );
430
+ vec4 targetMatcap = texture2D( uDestinationTexture, uv );
312
431
 
313
432
  vec4 matcap = mix(sourceMatcap, targetMatcap, uProgress);
314
433
  gl_FragColor = matcap;
@@ -368,32 +487,36 @@ class InstancedMeshManager {
368
487
  __publicField(this, "size");
369
488
  __publicField(this, "mesh");
370
489
  __publicField(this, "matcapMaterial");
371
- __publicField(this, "fallbackMaterial");
372
490
  __publicField(this, "fallbackGeometry");
373
- __publicField(this, "materials");
491
+ __publicField(this, "uniforms");
492
+ __publicField(this, "originColor");
493
+ __publicField(this, "destinationColor");
374
494
  __publicField(this, "geometries");
375
495
  __publicField(this, "uvRefsCache");
376
496
  __publicField(this, "previousScale");
377
497
  this.size = initialSize;
378
- this.materials = /* @__PURE__ */ new Map();
379
498
  this.geometries = /* @__PURE__ */ new Map();
380
499
  this.uvRefsCache = /* @__PURE__ */ new Map();
381
500
  this.previousScale = { x: 1, y: 1, z: 1 };
501
+ this.originColor = "grey";
502
+ this.destinationColor = "grey";
503
+ this.uniforms = {
504
+ uTime: { value: 0 },
505
+ uProgress: { value: 0 },
506
+ uTexture: { value: null },
507
+ uVelocity: { value: null },
508
+ uOriginTexture: { value: null },
509
+ uDestinationTexture: { value: null }
510
+ };
382
511
  this.matcapMaterial = new THREE.ShaderMaterial({
383
- uniforms: {
384
- uTime: { value: 0 },
385
- uProgress: { value: 0 },
386
- uTexture: { value: null },
387
- uVelocity: { value: null },
388
- uSourceMatcap: { value: null },
389
- uTargetMatcap: { value: null }
390
- },
512
+ uniforms: this.uniforms,
391
513
  vertexShader: instanceVertexShader,
392
514
  fragmentShader: instanceFragmentShader
393
515
  });
394
- this.fallbackMaterial = new THREE.MeshBasicMaterial({ color: 16777215 });
516
+ this.setOriginColor(this.originColor);
517
+ this.setDestinationColor(this.destinationColor);
395
518
  this.fallbackGeometry = new THREE.BoxGeometry(1e-3, 1e-3, 1e-3);
396
- this.mesh = this.createInstancedMesh(initialSize, this.fallbackGeometry, this.fallbackMaterial);
519
+ this.mesh = this.createInstancedMesh(initialSize, this.fallbackGeometry, this.matcapMaterial);
397
520
  }
398
521
  /**
399
522
  * Gets the instanced mesh.
@@ -417,10 +540,12 @@ class InstancedMeshManager {
417
540
  * @param matcap The matcap texture to set.
418
541
  */
419
542
  setOriginMatcap(matcap) {
420
- this.matcapMaterial.uniforms.uSourceMatcap.value = matcap;
543
+ this.disposeSolidColorOriginTexture();
544
+ this.matcapMaterial.uniforms.uOriginTexture.value = matcap;
421
545
  }
422
546
  setDestinationMatcap(matcap) {
423
- this.matcapMaterial.uniforms.uTargetMatcap.value = matcap;
547
+ this.disposeSolidColorDestinationTexture();
548
+ this.matcapMaterial.uniforms.uDestinationTexture.value = matcap;
424
549
  }
425
550
  setProgress(float) {
426
551
  float = Math.max(0, float);
@@ -438,16 +563,6 @@ class InstancedMeshManager {
438
563
  useMatcapMaterial() {
439
564
  this.mesh.material = this.matcapMaterial;
440
565
  }
441
- /**
442
- * Use the specified material for the instanced mesh.
443
- * @param id The ID of the material to use.
444
- */
445
- useMaterial(id) {
446
- const material = this.materials.get(id);
447
- if (material) {
448
- this.mesh.material = material;
449
- }
450
- }
451
566
  /**
452
567
  * Use the specified geometry for the instanced mesh.
453
568
  * @param id The ID of the geometry to use.
@@ -490,10 +605,11 @@ class InstancedMeshManager {
490
605
  dispose() {
491
606
  this.mesh.dispose();
492
607
  this.geometries.forEach((geometry) => geometry.dispose());
493
- this.materials.forEach((material) => material.dispose());
608
+ this.disposeSolidColorOriginTexture();
609
+ this.disposeSolidColorDestinationTexture();
610
+ this.matcapMaterial.dispose();
494
611
  this.uvRefsCache.clear();
495
612
  this.geometries.clear();
496
- this.materials.clear();
497
613
  }
498
614
  /**
499
615
  * Registers a geometry.
@@ -507,7 +623,7 @@ class InstancedMeshManager {
507
623
  return;
508
624
  }
509
625
  }
510
- const uvRefs = this.getUVRefs(this.size);
626
+ const uvRefs = this.createUVRefs(this.size);
511
627
  geometry.setAttribute("uvRef", uvRefs);
512
628
  this.geometries.set(id, geometry);
513
629
  if (this.mesh.geometry === previous) {
@@ -515,30 +631,22 @@ class InstancedMeshManager {
515
631
  }
516
632
  previous == null ? void 0 : previous.dispose();
517
633
  }
518
- /**
519
- * Registers a material.
520
- * @param id The ID of the material to register.
521
- * @param material The material to register.
522
- */
523
- registerMaterial(id, material) {
524
- const previous = this.materials.get(id);
525
- if (previous) {
526
- if (previous === material) {
527
- return;
528
- }
529
- }
530
- if (this.mesh.material === previous) {
531
- this.mesh.material = material;
532
- }
533
- this.materials.set(id, material);
534
- previous == null ? void 0 : previous.dispose();
634
+ setOriginColor(color) {
635
+ this.disposeSolidColorOriginTexture();
636
+ this.originColor = color;
637
+ this.uniforms.uOriginTexture.value = this.createSolidColorDataTexture(color);
638
+ }
639
+ setDestinationColor(color) {
640
+ this.disposeSolidColorDestinationTexture();
641
+ this.destinationColor = color;
642
+ this.uniforms.uDestinationTexture.value = this.createSolidColorDataTexture(color);
535
643
  }
536
644
  /**
537
645
  * Gets the UV references for the specified size.
538
646
  * @param size The size for which to generate UV references.
539
647
  * @returns The UV references.
540
648
  */
541
- getUVRefs(size) {
649
+ createUVRefs(size) {
542
650
  const cached = this.uvRefsCache.get(size);
543
651
  if (cached) {
544
652
  return cached;
@@ -564,10 +672,50 @@ class InstancedMeshManager {
564
672
  */
565
673
  createInstancedMesh(size, geometry, material) {
566
674
  geometry = geometry || this.fallbackGeometry;
567
- geometry.setAttribute("uvRef", this.getUVRefs(size));
675
+ geometry.setAttribute("uvRef", this.createUVRefs(size));
568
676
  const count = size * size;
569
677
  return new THREE.InstancedMesh(geometry, material, count);
570
678
  }
679
+ createSolidColorDataTexture(color, size = 16) {
680
+ const col = new THREE.Color(color);
681
+ const width = size;
682
+ const height = size;
683
+ const data = new Uint8Array(width * height * 4);
684
+ const r = Math.floor(col.r * 255);
685
+ const g = Math.floor(col.g * 255);
686
+ const b = Math.floor(col.b * 255);
687
+ for (let i = 0; i < width * height; i++) {
688
+ const index = i * 4;
689
+ data[index] = r;
690
+ data[index + 1] = g;
691
+ data[index + 2] = b;
692
+ data[index + 3] = 255;
693
+ }
694
+ const texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
695
+ texture.type = THREE.UnsignedByteType;
696
+ texture.wrapS = THREE.RepeatWrapping;
697
+ texture.wrapT = THREE.RepeatWrapping;
698
+ texture.minFilter = THREE.NearestFilter;
699
+ texture.magFilter = THREE.NearestFilter;
700
+ texture.needsUpdate = true;
701
+ return texture;
702
+ }
703
+ disposeSolidColorOriginTexture() {
704
+ if (this.originColor) {
705
+ this.originColor = null;
706
+ if (this.uniforms.uOriginTexture.value) {
707
+ this.uniforms.uOriginTexture.value.dispose();
708
+ }
709
+ }
710
+ }
711
+ disposeSolidColorDestinationTexture() {
712
+ if (this.destinationColor) {
713
+ this.destinationColor = null;
714
+ if (this.uniforms.uDestinationTexture.value) {
715
+ this.uniforms.uDestinationTexture.value.dispose();
716
+ }
717
+ }
718
+ }
571
719
  }
572
720
  class IntersectionService {
573
721
  /**
@@ -1335,122 +1483,6 @@ class TransitionService {
1335
1483
  this.eventEmitter.emit("transitionFinished", { type });
1336
1484
  }
1337
1485
  }
1338
- class AssetService {
1339
- constructor(eventEmitter) {
1340
- __publicField(this, "serviceState", "created");
1341
- __publicField(this, "eventEmitter");
1342
- __publicField(this, "meshes", /* @__PURE__ */ new Map());
1343
- __publicField(this, "textures", /* @__PURE__ */ new Map());
1344
- __publicField(this, "gltfLoader", new GLTFLoader());
1345
- __publicField(this, "textureLoader", new THREE.TextureLoader());
1346
- __publicField(this, "dracoLoader", new DRACOLoader());
1347
- __publicField(this, "solidColorTexture", new THREE.DataTexture(new Uint8Array([127, 127, 127, 255]), 1, 1, THREE.RGBAFormat));
1348
- this.eventEmitter = eventEmitter;
1349
- this.dracoLoader.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.5.7/");
1350
- this.gltfLoader.setDRACOLoader(this.dracoLoader);
1351
- this.updateServiceState("ready");
1352
- }
1353
- /**
1354
- * Registers an asset.
1355
- * @param id - The ID of the asset.
1356
- * @param item - The asset to set.
1357
- */
1358
- register(id, item) {
1359
- item.name = id;
1360
- if (item instanceof THREE.Mesh) {
1361
- const prev = this.meshes.get(id);
1362
- if (prev) disposeMesh(prev);
1363
- this.meshes.set(id, item);
1364
- } else {
1365
- const prev = this.textures.get(id);
1366
- if (prev) prev.dispose();
1367
- this.textures.set(id, item);
1368
- }
1369
- this.eventEmitter.emit("assetRegistered", { id });
1370
- }
1371
- setSolidColor(color) {
1372
- this.changeColor(color);
1373
- }
1374
- getSolidColorTexture() {
1375
- return this.solidColorTexture;
1376
- }
1377
- getMesh(id) {
1378
- return this.meshes.get(id) ?? null;
1379
- }
1380
- getMatcap(id) {
1381
- const texture = this.textures.get(id);
1382
- if (!texture) this.eventEmitter.emit("invalidRequest", { message: `texture with id "${id}" not found. using solid color texture instead...` });
1383
- return texture ?? this.solidColorTexture;
1384
- }
1385
- getMeshIDs() {
1386
- return Array.from(this.meshes.keys());
1387
- }
1388
- getTextureIDs() {
1389
- return Array.from(this.textures.keys());
1390
- }
1391
- getMeshes() {
1392
- return Array.from(this.meshes.values());
1393
- }
1394
- getTextures() {
1395
- return Array.from(this.textures.values());
1396
- }
1397
- /**
1398
- * Loads a mesh asynchronously.
1399
- * @param id - The ID of the mesh.
1400
- * @param url - The URL of the mesh.
1401
- * @param options - Optional parameters.
1402
- * @returns The loaded mesh or null.
1403
- */
1404
- async loadMeshAsync(id, url, options = {}) {
1405
- const gltf = await this.gltfLoader.loadAsync(url);
1406
- try {
1407
- if (options.meshName) {
1408
- const mesh = gltf.scene.getObjectByName(options.meshName);
1409
- this.register(id, mesh);
1410
- return mesh;
1411
- } else {
1412
- const mesh = gltf.scene.children[0];
1413
- this.register(id, mesh);
1414
- return mesh;
1415
- }
1416
- } catch (error) {
1417
- this.eventEmitter.emit("invalidRequest", { message: `failed to load mesh: ${id}. ${error}` });
1418
- return null;
1419
- }
1420
- }
1421
- /**
1422
- * Loads a texture asynchronously.
1423
- * @param id - The ID of the texture.
1424
- * @param url - The URL of the texture.
1425
- * @returns The loaded texture or null.
1426
- */
1427
- async loadTextureAsync(id, url) {
1428
- try {
1429
- const texture = await this.textureLoader.loadAsync(url);
1430
- this.register(id, texture);
1431
- return texture;
1432
- } catch (error) {
1433
- this.eventEmitter.emit("invalidRequest", { message: `failed to load texture: ${id}. ${error}` });
1434
- return null;
1435
- }
1436
- }
1437
- dispose() {
1438
- this.updateServiceState("disposed");
1439
- this.meshes.forEach((mesh) => disposeMesh(mesh));
1440
- this.meshes.clear();
1441
- this.textures.forEach((texture) => texture.dispose());
1442
- this.textures.clear();
1443
- }
1444
- changeColor(color) {
1445
- const actual = new THREE.Color(color);
1446
- this.solidColorTexture = new THREE.DataTexture(new Uint8Array([actual.r, actual.g, actual.b, 255]), 1, 1, THREE.RGBAFormat);
1447
- this.solidColorTexture.needsUpdate = true;
1448
- }
1449
- updateServiceState(serviceState) {
1450
- this.serviceState = serviceState;
1451
- this.eventEmitter.emit("serviceStateUpdated", { type: "asset", state: serviceState });
1452
- }
1453
- }
1454
1486
  class ParticlesEngine {
1455
1487
  /**
1456
1488
  * Creates a new ParticlesEngine instance.
@@ -1543,6 +1575,30 @@ class ParticlesEngine {
1543
1575
  this.engineState.destinationMatcapID = matcapID;
1544
1576
  this.instancedMeshManager.setDestinationMatcap(this.assetService.getMatcap(matcapID));
1545
1577
  }
1578
+ setOriginColor(color, override = false) {
1579
+ if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1580
+ this.instancedMeshManager.setOriginColor(color);
1581
+ }
1582
+ setDestinationColor(color, override = false) {
1583
+ if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1584
+ this.instancedMeshManager.setDestinationColor(color);
1585
+ }
1586
+ setOriginTexture(id, override = false) {
1587
+ if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1588
+ if (typeof id === "string" && this.assetService.hasMatcap(id)) {
1589
+ this.setOriginMatcap(id);
1590
+ } else {
1591
+ this.setOriginColor(id);
1592
+ }
1593
+ }
1594
+ setDestinationTexture(id, override = false) {
1595
+ if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1596
+ if (typeof id === "string" && this.assetService.hasMatcap(id)) {
1597
+ this.setDestinationMatcap(id);
1598
+ } else {
1599
+ this.setDestinationColor(id);
1600
+ }
1601
+ }
1546
1602
  setMatcapProgress(progress, override = false) {
1547
1603
  if (override) this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1548
1604
  this.engineState.matcapTransitionProgress = progress;
@@ -1563,7 +1619,12 @@ class ParticlesEngine {
1563
1619
  this.eventEmitter.emit("invalidRequest", { message: `Mesh with id "${this.engineState.destinationMeshID}" does not exist` });
1564
1620
  return;
1565
1621
  }
1566
- this.dataTextureManager.getDataTexture(originMesh).then((texture) => this.simulationRendererService.setOriginDataTexture({ dataTexture: texture, textureSize: size }));
1622
+ this.dataTextureManager.getDataTexture(originMesh).then(
1623
+ (texture) => this.simulationRendererService.setOriginDataTexture({
1624
+ dataTexture: texture,
1625
+ textureSize: size
1626
+ })
1627
+ );
1567
1628
  this.dataTextureManager.getDataTexture(destinationMesh).then(
1568
1629
  (texture) => this.simulationRendererService.setDestinationDataTexture({
1569
1630
  dataTexture: texture,
@@ -1636,6 +1697,24 @@ class ParticlesEngine {
1636
1697
  }
1637
1698
  );
1638
1699
  }
1700
+ scheduleTextureTransition(origin, destination, options) {
1701
+ const easing = (options == null ? void 0 : options.easing) ?? linear;
1702
+ const duration = (options == null ? void 0 : options.duration) ?? 1e3;
1703
+ if (options == null ? void 0 : options.override) {
1704
+ this.eventEmitter.emit("transitionCancelled", { type: "matcap" });
1705
+ }
1706
+ this.transitionService.enqueue(
1707
+ "matcap",
1708
+ { easing, duration },
1709
+ {
1710
+ onTransitionBegin: () => {
1711
+ this.setOriginTexture(origin);
1712
+ this.setDestinationTexture(destination);
1713
+ this.setMatcapProgress(0);
1714
+ }
1715
+ }
1716
+ );
1717
+ }
1639
1718
  handleServiceStateUpdated({ type, state }) {
1640
1719
  this.serviceStates[type] = state;
1641
1720
  }