@polarfront-lab/ionian 3.0.5 → 3.0.6-hotfix.2

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
@@ -1,2067 +1,2 @@
1
- var __defProp = Object.defineProperty;
2
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import * as THREE from "three";
5
- import { Triangle, Vector3, Vector2, Mesh, OrthographicCamera, BufferGeometry, Float32BufferAttribute, NearestFilter, ShaderMaterial, WebGLRenderTarget, FloatType, RGBAFormat, DataTexture, ClampToEdgeWrapping } from "three";
6
- import { GLTFLoader, DRACOLoader } from "three-stdlib";
7
- const linear = (n) => n;
8
- function mitt(n) {
9
- return { all: n = n || /* @__PURE__ */ new Map(), on: function(t, e) {
10
- var i = n.get(t);
11
- i ? i.push(e) : n.set(t, [e]);
12
- }, off: function(t, e) {
13
- var i = n.get(t);
14
- i && (e ? i.splice(i.indexOf(e) >>> 0, 1) : n.set(t, []));
15
- }, emit: function(t, e) {
16
- var i = n.get(t);
17
- i && i.slice().map(function(n2) {
18
- n2(e);
19
- }), (i = n.get("*")) && i.slice().map(function(n2) {
20
- n2(t, e);
21
- });
22
- } };
23
- }
24
- class DefaultEventEmitter {
25
- constructor() {
26
- __publicField(this, "emitter", mitt());
27
- }
28
- emit(type, payload) {
29
- this.emitter.emit(type, payload);
30
- }
31
- off(type, handler) {
32
- this.emitter.off(type, handler);
33
- }
34
- on(type, handler) {
35
- this.emitter.on(type, handler);
36
- }
37
- once(type, handler) {
38
- this.emitter.on(type, (payload) => {
39
- this.emitter.off(type, handler);
40
- handler(payload);
41
- });
42
- }
43
- dispose() {
44
- this.emitter.all.clear();
45
- }
46
- }
47
- function createDataTexture(data, size) {
48
- const texture = new THREE.DataTexture(data, size, size, THREE.RGBAFormat, THREE.FloatType);
49
- texture.needsUpdate = true;
50
- return texture;
51
- }
52
- function createBlankDataTexture(size) {
53
- return createDataTexture(new Float32Array(4 * size * size), size);
54
- }
55
- function createSpherePoints(size) {
56
- const data = new Float32Array(size * size * 4);
57
- for (let i = 0; i < size; i++) {
58
- for (let j = 0; j < size; j++) {
59
- const index = i * size + j;
60
- let theta = Math.random() * Math.PI * 2;
61
- let phi = Math.acos(Math.random() * 2 - 1);
62
- let x = Math.sin(phi) * Math.cos(theta);
63
- let y = Math.sin(phi) * Math.sin(theta);
64
- let z = Math.cos(phi);
65
- data[4 * index] = x;
66
- data[4 * index + 1] = y;
67
- data[4 * index + 2] = z;
68
- data[4 * index + 3] = (Math.random() - 0.5) * 0.01;
69
- }
70
- }
71
- return createDataTexture(data, size);
72
- }
73
- function disposeMesh(mesh) {
74
- mesh.geometry.dispose();
75
- if (mesh.material instanceof THREE.Material) {
76
- mesh.material.dispose();
77
- } else {
78
- mesh.material.forEach((material) => material.dispose());
79
- }
80
- }
81
- function clamp(value, min, max) {
82
- value = Math.min(value, max);
83
- value = Math.max(value, min);
84
- return value;
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, "solidColorTextures", /* @__PURE__ */ new Map());
96
- __publicField(this, "fallbackTexture", new THREE.DataTexture(new Uint8Array([127, 127, 127, 255]), 1, 1, THREE.RGBAFormat));
97
- this.eventEmitter = eventEmitter;
98
- this.dracoLoader.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.5.7/");
99
- this.gltfLoader.setDRACOLoader(this.dracoLoader);
100
- this.fallbackTexture.name = "default-fallback-texture";
101
- this.updateServiceState("ready");
102
- }
103
- /**
104
- * Registers an asset.
105
- * @param id - The ID of the asset.
106
- * @param item - The asset to set.
107
- */
108
- register(id, item) {
109
- item.name = id;
110
- if (item instanceof THREE.Mesh) {
111
- const prev = this.meshes.get(id);
112
- if (prev) disposeMesh(prev);
113
- this.meshes.set(id, item);
114
- } else {
115
- const prev = this.textures.get(id);
116
- if (prev) prev.dispose();
117
- this.textures.set(id, item);
118
- }
119
- this.eventEmitter.emit("assetRegistered", { id });
120
- }
121
- getMesh(id) {
122
- return this.meshes.get(id) ?? null;
123
- }
124
- getMatcapTexture(id) {
125
- const texture = this.textures.get(id);
126
- if (!texture) this.eventEmitter.emit("invalidRequest", { message: `texture with id "${id}" not found. using solid color texture instead...` });
127
- return texture ?? this.fallbackTexture;
128
- }
129
- getSolidColorTexture(colorValue) {
130
- const colorKey = new THREE.Color(colorValue).getHexString();
131
- let texture = this.solidColorTextures.get(colorKey);
132
- if (texture) {
133
- return texture;
134
- }
135
- try {
136
- const texture2 = this.createSolidColorDataTexture(new THREE.Color(colorValue));
137
- this.solidColorTextures.set(colorKey, texture2);
138
- return texture2;
139
- } catch (error) {
140
- console.error(`Invalid color value provided to getSolidColorTexture: ${colorValue}`, error);
141
- this.eventEmitter.emit("invalidRequest", { message: `Invalid color value: ${colorValue}. Using fallback texture.` });
142
- return this.fallbackTexture;
143
- }
144
- }
145
- getFallbackTexture() {
146
- return this.fallbackTexture;
147
- }
148
- getMeshIDs() {
149
- return Array.from(this.meshes.keys());
150
- }
151
- getTextureIDs() {
152
- return Array.from(this.textures.keys());
153
- }
154
- getMeshes() {
155
- return Array.from(this.meshes.values());
156
- }
157
- getTextures() {
158
- return Array.from(this.textures.values());
159
- }
160
- createSolidColorDataTexture(color, size = 16) {
161
- const col = new THREE.Color(color);
162
- const width = size;
163
- const height = size;
164
- const data = new Uint8Array(width * height * 4);
165
- const r = Math.floor(col.r * 255);
166
- const g = Math.floor(col.g * 255);
167
- const b = Math.floor(col.b * 255);
168
- for (let i = 0; i < width * height; i++) {
169
- const index = i * 4;
170
- data[index] = r;
171
- data[index + 1] = g;
172
- data[index + 2] = b;
173
- data[index + 3] = 255;
174
- }
175
- const texture = new THREE.DataTexture(data, width, height, THREE.RGBAFormat);
176
- texture.type = THREE.UnsignedByteType;
177
- texture.wrapS = THREE.RepeatWrapping;
178
- texture.wrapT = THREE.RepeatWrapping;
179
- texture.minFilter = THREE.NearestFilter;
180
- texture.magFilter = THREE.NearestFilter;
181
- texture.needsUpdate = true;
182
- return texture;
183
- }
184
- /**
185
- * Loads a mesh asynchronously.
186
- * @param id - The ID of the mesh.
187
- * @param url - The URL of the mesh.
188
- * @param options - Optional parameters.
189
- * @returns The loaded mesh or null.
190
- */
191
- async loadMeshAsync(id, url, options = {}) {
192
- const gltf = await this.gltfLoader.loadAsync(url);
193
- try {
194
- if (options.meshName) {
195
- const mesh = gltf.scene.getObjectByName(options.meshName);
196
- this.register(id, mesh);
197
- return mesh;
198
- } else {
199
- const mesh = gltf.scene.children[0];
200
- this.register(id, mesh);
201
- return mesh;
202
- }
203
- } catch (error) {
204
- this.eventEmitter.emit("invalidRequest", { message: `failed to load mesh: ${id}. ${error}` });
205
- return null;
206
- }
207
- }
208
- /**
209
- * Loads a texture asynchronously.
210
- * @param id - The ID of the texture.
211
- * @param url - The URL of the texture.
212
- * @returns The loaded texture or null.
213
- */
214
- async loadTextureAsync(id, url) {
215
- try {
216
- const texture = await this.textureLoader.loadAsync(url);
217
- this.register(id, texture);
218
- return texture;
219
- } catch (error) {
220
- this.eventEmitter.emit("invalidRequest", { message: `failed to load texture: ${id}. ${error}` });
221
- return null;
222
- }
223
- }
224
- dispose() {
225
- this.updateServiceState("disposed");
226
- this.meshes.forEach((mesh) => disposeMesh(mesh));
227
- this.meshes.clear();
228
- this.textures.forEach((texture) => texture.dispose());
229
- this.textures.clear();
230
- this.solidColorTextures.forEach((texture) => texture.dispose());
231
- this.solidColorTextures.clear();
232
- this.fallbackTexture.dispose();
233
- }
234
- updateServiceState(serviceState) {
235
- this.serviceState = serviceState;
236
- this.eventEmitter.emit("serviceStateUpdated", { type: "asset", state: serviceState });
237
- }
238
- }
239
- const _face = new Triangle();
240
- const _color = new Vector3();
241
- const _uva = new Vector2(), _uvb = new Vector2(), _uvc = new Vector2();
242
- class MeshSurfaceSampler {
243
- /**
244
- * Constructs a mesh surface sampler.
245
- *
246
- * @param {Mesh} mesh - Surface mesh from which to sample.
247
- */
248
- constructor(mesh) {
249
- this.geometry = mesh.geometry;
250
- this.randomFunction = Math.random;
251
- this.indexAttribute = this.geometry.index;
252
- this.positionAttribute = this.geometry.getAttribute("position");
253
- this.normalAttribute = this.geometry.getAttribute("normal");
254
- this.colorAttribute = this.geometry.getAttribute("color");
255
- this.uvAttribute = this.geometry.getAttribute("uv");
256
- this.weightAttribute = null;
257
- this.distribution = null;
258
- }
259
- /**
260
- * Specifies a vertex attribute to be used as a weight when sampling from the surface.
261
- * Faces with higher weights are more likely to be sampled, and those with weights of
262
- * zero will not be sampled at all. For vector attributes, only .x is used in sampling.
263
- *
264
- * If no weight attribute is selected, sampling is randomly distributed by area.
265
- *
266
- * @param {string} name - The attribute name.
267
- * @return {MeshSurfaceSampler} A reference to this sampler.
268
- */
269
- setWeightAttribute(name) {
270
- this.weightAttribute = name ? this.geometry.getAttribute(name) : null;
271
- return this;
272
- }
273
- /**
274
- * Processes the input geometry and prepares to return samples. Any configuration of the
275
- * geometry or sampler must occur before this method is called. Time complexity is O(n)
276
- * for a surface with n faces.
277
- *
278
- * @return {MeshSurfaceSampler} A reference to this sampler.
279
- */
280
- build() {
281
- const indexAttribute = this.indexAttribute;
282
- const positionAttribute = this.positionAttribute;
283
- const weightAttribute = this.weightAttribute;
284
- const totalFaces = indexAttribute ? indexAttribute.count / 3 : positionAttribute.count / 3;
285
- const faceWeights = new Float32Array(totalFaces);
286
- for (let i = 0; i < totalFaces; i++) {
287
- let faceWeight = 1;
288
- let i0 = 3 * i;
289
- let i1 = 3 * i + 1;
290
- let i2 = 3 * i + 2;
291
- if (indexAttribute) {
292
- i0 = indexAttribute.getX(i0);
293
- i1 = indexAttribute.getX(i1);
294
- i2 = indexAttribute.getX(i2);
295
- }
296
- if (weightAttribute) {
297
- faceWeight = weightAttribute.getX(i0) + weightAttribute.getX(i1) + weightAttribute.getX(i2);
298
- }
299
- _face.a.fromBufferAttribute(positionAttribute, i0);
300
- _face.b.fromBufferAttribute(positionAttribute, i1);
301
- _face.c.fromBufferAttribute(positionAttribute, i2);
302
- faceWeight *= _face.getArea();
303
- faceWeights[i] = faceWeight;
304
- }
305
- const distribution = new Float32Array(totalFaces);
306
- let cumulativeTotal = 0;
307
- for (let i = 0; i < totalFaces; i++) {
308
- cumulativeTotal += faceWeights[i];
309
- distribution[i] = cumulativeTotal;
310
- }
311
- this.distribution = distribution;
312
- return this;
313
- }
314
- /**
315
- * Allows to set a custom random number generator. Default is `Math.random()`.
316
- *
317
- * @param {Function} randomFunction - A random number generator.
318
- * @return {MeshSurfaceSampler} A reference to this sampler.
319
- */
320
- setRandomGenerator(randomFunction) {
321
- this.randomFunction = randomFunction;
322
- return this;
323
- }
324
- /**
325
- * Selects a random point on the surface of the input geometry, returning the
326
- * position and optionally the normal vector, color and UV Coordinate at that point.
327
- * Time complexity is O(log n) for a surface with n faces.
328
- *
329
- * @param {Vector3} targetPosition - The target object holding the sampled position.
330
- * @param {Vector3} targetNormal - The target object holding the sampled normal.
331
- * @param {Color} targetColor - The target object holding the sampled color.
332
- * @param {Vector2} targetUV - The target object holding the sampled uv coordinates.
333
- * @return {MeshSurfaceSampler} A reference to this sampler.
334
- */
335
- sample(targetPosition, targetNormal, targetColor, targetUV) {
336
- const faceIndex = this._sampleFaceIndex();
337
- return this._sampleFace(faceIndex, targetPosition, targetNormal, targetColor, targetUV);
338
- }
339
- // private
340
- _sampleFaceIndex() {
341
- const cumulativeTotal = this.distribution[this.distribution.length - 1];
342
- return this._binarySearch(this.randomFunction() * cumulativeTotal);
343
- }
344
- _binarySearch(x) {
345
- const dist = this.distribution;
346
- let start = 0;
347
- let end = dist.length - 1;
348
- let index = -1;
349
- while (start <= end) {
350
- const mid = Math.ceil((start + end) / 2);
351
- if (mid === 0 || dist[mid - 1] <= x && dist[mid] > x) {
352
- index = mid;
353
- break;
354
- } else if (x < dist[mid]) {
355
- end = mid - 1;
356
- } else {
357
- start = mid + 1;
358
- }
359
- }
360
- return index;
361
- }
362
- _sampleFace(faceIndex, targetPosition, targetNormal, targetColor, targetUV) {
363
- let u = this.randomFunction();
364
- let v = this.randomFunction();
365
- if (u + v > 1) {
366
- u = 1 - u;
367
- v = 1 - v;
368
- }
369
- const indexAttribute = this.indexAttribute;
370
- let i0 = faceIndex * 3;
371
- let i1 = faceIndex * 3 + 1;
372
- let i2 = faceIndex * 3 + 2;
373
- if (indexAttribute) {
374
- i0 = indexAttribute.getX(i0);
375
- i1 = indexAttribute.getX(i1);
376
- i2 = indexAttribute.getX(i2);
377
- }
378
- _face.a.fromBufferAttribute(this.positionAttribute, i0);
379
- _face.b.fromBufferAttribute(this.positionAttribute, i1);
380
- _face.c.fromBufferAttribute(this.positionAttribute, i2);
381
- targetPosition.set(0, 0, 0).addScaledVector(_face.a, u).addScaledVector(_face.b, v).addScaledVector(_face.c, 1 - (u + v));
382
- if (targetNormal !== void 0) {
383
- if (this.normalAttribute !== void 0) {
384
- _face.a.fromBufferAttribute(this.normalAttribute, i0);
385
- _face.b.fromBufferAttribute(this.normalAttribute, i1);
386
- _face.c.fromBufferAttribute(this.normalAttribute, i2);
387
- targetNormal.set(0, 0, 0).addScaledVector(_face.a, u).addScaledVector(_face.b, v).addScaledVector(_face.c, 1 - (u + v)).normalize();
388
- } else {
389
- _face.getNormal(targetNormal);
390
- }
391
- }
392
- if (targetColor !== void 0 && this.colorAttribute !== void 0) {
393
- _face.a.fromBufferAttribute(this.colorAttribute, i0);
394
- _face.b.fromBufferAttribute(this.colorAttribute, i1);
395
- _face.c.fromBufferAttribute(this.colorAttribute, i2);
396
- _color.set(0, 0, 0).addScaledVector(_face.a, u).addScaledVector(_face.b, v).addScaledVector(_face.c, 1 - (u + v));
397
- targetColor.r = _color.x;
398
- targetColor.g = _color.y;
399
- targetColor.b = _color.z;
400
- }
401
- if (targetUV !== void 0 && this.uvAttribute !== void 0) {
402
- _uva.fromBufferAttribute(this.uvAttribute, i0);
403
- _uvb.fromBufferAttribute(this.uvAttribute, i1);
404
- _uvc.fromBufferAttribute(this.uvAttribute, i2);
405
- targetUV.set(0, 0).addScaledVector(_uva, u).addScaledVector(_uvb, v).addScaledVector(_uvc, 1 - (u + v));
406
- }
407
- return this;
408
- }
409
- }
410
- class DataTextureService {
411
- // Cache the current atlas
412
- /**
413
- * Creates a new DataTextureManager instance.
414
- * @param eventEmitter
415
- * @param textureSize
416
- */
417
- constructor(eventEmitter, textureSize) {
418
- __publicField(this, "textureSize");
419
- __publicField(this, "dataTextures");
420
- __publicField(this, "eventEmitter");
421
- __publicField(this, "currentAtlas", null);
422
- this.eventEmitter = eventEmitter;
423
- this.textureSize = textureSize;
424
- this.dataTextures = /* @__PURE__ */ new Map();
425
- this.updateServiceState("ready");
426
- }
427
- setTextureSize(textureSize) {
428
- if (this.textureSize === textureSize) return;
429
- this.textureSize = textureSize;
430
- this.dataTextures.forEach((texture) => texture.dispose());
431
- this.dataTextures.clear();
432
- if (this.currentAtlas) {
433
- this.currentAtlas.dispose();
434
- this.currentAtlas = null;
435
- }
436
- }
437
- /**
438
- * Prepares a mesh for sampling.
439
- * @returns The prepared data texture.
440
- * @param asset The asset to prepare.
441
- */
442
- async getDataTexture(asset) {
443
- const cachedTexture = this.dataTextures.get(asset.uuid);
444
- if (cachedTexture) {
445
- return cachedTexture;
446
- }
447
- const meshData = parseMeshData(asset);
448
- const array = sampleMesh(meshData, this.textureSize);
449
- const dataTexture = createDataTexture(array, this.textureSize);
450
- dataTexture.name = asset.name;
451
- this.dataTextures.set(asset.uuid, dataTexture);
452
- return dataTexture;
453
- }
454
- async dispose() {
455
- this.dataTextures.forEach((texture) => texture.dispose());
456
- this.dataTextures.clear();
457
- if (this.currentAtlas) {
458
- this.currentAtlas.dispose();
459
- this.currentAtlas = null;
460
- }
461
- this.updateServiceState("disposed");
462
- }
463
- updateServiceState(serviceState) {
464
- this.eventEmitter.emit("serviceStateUpdated", { type: "data-texture", state: serviceState });
465
- }
466
- /**
467
- * Creates a Texture Atlas containing position data for a sequence of meshes.
468
- * @param meshes An array of THREE.Mesh objects in the desired sequence.
469
- * @param singleTextureSize The desired resolution (width/height) for each mesh's data within the atlas.
470
- * @returns A Promise resolving to the generated DataTexture atlas.
471
- */
472
- async createSequenceDataTextureAtlas(meshes, singleTextureSize) {
473
- this.updateServiceState("loading");
474
- if (this.currentAtlas) {
475
- this.currentAtlas.dispose();
476
- this.currentAtlas = null;
477
- }
478
- const numMeshes = meshes.length;
479
- if (numMeshes === 0) {
480
- throw new Error("Mesh array cannot be empty.");
481
- }
482
- const atlasWidth = singleTextureSize * numMeshes;
483
- const atlasHeight = singleTextureSize;
484
- const atlasData = new Float32Array(atlasWidth * atlasHeight * 4);
485
- try {
486
- for (let i = 0; i < numMeshes; i++) {
487
- const mesh = meshes[i];
488
- const meshDataTexture = await this.getDataTexture(mesh);
489
- const meshTextureData = meshDataTexture.image.data;
490
- for (let y = 0; y < singleTextureSize; y++) {
491
- for (let x = 0; x < singleTextureSize; x++) {
492
- const sourceIndex = (y * singleTextureSize + x) * 4;
493
- const targetX = x + i * singleTextureSize;
494
- const targetIndex = (y * atlasWidth + targetX) * 4;
495
- atlasData[targetIndex] = meshTextureData[sourceIndex];
496
- atlasData[targetIndex + 1] = meshTextureData[sourceIndex + 1];
497
- atlasData[targetIndex + 2] = meshTextureData[sourceIndex + 2];
498
- atlasData[targetIndex + 3] = meshTextureData[sourceIndex + 3];
499
- }
500
- }
501
- }
502
- const atlasTexture = new THREE.DataTexture(atlasData, atlasWidth, atlasHeight, THREE.RGBAFormat, THREE.FloatType);
503
- atlasTexture.needsUpdate = true;
504
- atlasTexture.name = `atlas-${meshes.map((m) => m.name).join("-")}`;
505
- this.currentAtlas = atlasTexture;
506
- this.updateServiceState("ready");
507
- return atlasTexture;
508
- } catch (error) {
509
- this.updateServiceState("error");
510
- throw error;
511
- }
512
- }
513
- }
514
- function parseMeshData(mesh) {
515
- var _a;
516
- return {
517
- position: mesh.geometry.attributes.position.array,
518
- normal: (_a = mesh.geometry.attributes.normal) == null ? void 0 : _a.array,
519
- scale: { x: mesh.scale.x, y: mesh.scale.y, z: mesh.scale.z }
520
- };
521
- }
522
- function sampleMesh(meshData, size) {
523
- const geometry = new THREE.BufferGeometry();
524
- geometry.setAttribute("position", new THREE.BufferAttribute(new Float32Array(meshData.position), 3));
525
- if (meshData.normal) {
526
- geometry.setAttribute("normal", new THREE.BufferAttribute(new Float32Array(meshData.normal), 3));
527
- }
528
- const material = new THREE.MeshBasicMaterial();
529
- const mesh = new THREE.Mesh(geometry, material);
530
- mesh.scale.set(meshData.scale.x, meshData.scale.y, meshData.scale.z);
531
- const sampler = new MeshSurfaceSampler(mesh).build();
532
- const data = new Float32Array(size * size * 4);
533
- const position = new THREE.Vector3();
534
- for (let i = 0; i < size; i++) {
535
- for (let j = 0; j < size; j++) {
536
- const index = i * size + j;
537
- sampler.sample(position);
538
- data[4 * index] = position.x * meshData.scale.x;
539
- data[4 * index + 1] = position.y * meshData.scale.y;
540
- data[4 * index + 2] = position.z * meshData.scale.z;
541
- data[4 * index + 3] = (Math.random() - 0.5) * 0.01;
542
- }
543
- }
544
- return data;
545
- }
546
- const instanceFragmentShader = `
547
- varying vec2 vUv;
548
-
549
- uniform sampler2D uOriginTexture;
550
- uniform sampler2D uDestinationTexture;
551
-
552
- uniform float uProgress;
553
- varying vec3 vNormal;
554
- varying vec3 vViewPosition;
555
- void main() {
556
- vec3 viewDir = normalize( vViewPosition );
557
- vec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );
558
- vec3 y = cross( viewDir, x );
559
- vec2 uv = vec2( dot( x, vNormal ), dot( y, vNormal ) ) * 0.495 + 0.5; // 0.495 to remove artifacts caused by undersized matcap disks
560
-
561
- vec4 textureA = texture2D( uOriginTexture, uv );
562
- vec4 textureB = texture2D( uDestinationTexture, uv );
563
-
564
- vec4 finalColor = mix(textureA, textureB, uProgress);
565
- gl_FragColor = finalColor;
566
- }
567
- `;
568
- const instanceVertexShader = `
569
- varying vec2 vUv;
570
- uniform sampler2D uTexture;
571
- uniform sampler2D uVelocity;
572
- uniform float uTime;
573
- varying vec3 vNormal;
574
- attribute vec2 uvRef;
575
- varying vec3 vViewPosition;
576
-
577
- vec3 rotate3D(vec3 v, vec3 vel) {
578
- vec3 pos = v;
579
- vec3 up = vec3(0, 1, 0);
580
- vec3 axis = normalize(cross(up, vel));
581
- float angle = acos(dot(up, normalize(vel)));
582
- pos = pos * cos(angle) + cross(axis, pos) * sin(angle) + axis * dot(axis, pos) * (1. - cos(angle));
583
- return pos;
584
- }
585
-
586
- void main() {
587
- vUv = uv;
588
- vNormal = normal;
589
-
590
- vec4 color = texture2D(uTexture, uvRef);
591
- vec4 velocity = texture2D(uVelocity, uvRef);
592
- vec3 pos = color.xyz;// apply the texture to the vertex distribution.
593
-
594
- vec3 localPosition = position.xyz;
595
- if (length (velocity.xyz) < 0.0001) {
596
- velocity.xyz = vec3(0.0, 0.0001, 0.0001);
597
- }
598
- localPosition.y *= max(1.0, length(velocity.xyz) * 1000.0);
599
- localPosition = rotate3D(localPosition, velocity.xyz);
600
- vNormal = rotate3D(normal, velocity.xyz);
601
-
602
- mat4 instanceMat = instanceMatrix;
603
- instanceMat[3].xyz = pos.xyz;
604
-
605
- // unlike the traditional mvMatrix * position, we need to additional multiplication with the instance matrix.
606
- vec4 modelViewPosition = modelViewMatrix * instanceMat * vec4(localPosition, 1.0);
607
-
608
- vViewPosition = - modelViewPosition.xyz;
609
-
610
- gl_Position = projectionMatrix * modelViewPosition;
611
- }
612
- `;
613
- class InstancedMeshManager {
614
- /**
615
- * Creates a new InstancedMeshManager instance.
616
- * @param initialSize The initial size of the instanced mesh.
617
- */
618
- constructor(initialSize) {
619
- __publicField(this, "size");
620
- __publicField(this, "mesh");
621
- __publicField(this, "shaderMaterial");
622
- __publicField(this, "fallbackGeometry");
623
- __publicField(this, "uniforms");
624
- __publicField(this, "geometries");
625
- __publicField(this, "uvRefsCache");
626
- __publicField(this, "previousScale");
627
- this.size = initialSize;
628
- this.geometries = /* @__PURE__ */ new Map();
629
- this.uvRefsCache = /* @__PURE__ */ new Map();
630
- this.previousScale = { x: 1, y: 1, z: 1 };
631
- this.uniforms = {
632
- uTime: { value: 0 },
633
- uProgress: { value: 0 },
634
- uTexture: { value: null },
635
- uVelocity: { value: null },
636
- uOriginTexture: { value: null },
637
- uDestinationTexture: { value: null }
638
- };
639
- this.shaderMaterial = new THREE.ShaderMaterial({
640
- uniforms: this.uniforms,
641
- vertexShader: instanceVertexShader,
642
- fragmentShader: instanceFragmentShader
643
- });
644
- this.fallbackGeometry = new THREE.BoxGeometry(1e-3, 1e-3, 1e-3);
645
- this.mesh = this.createInstancedMesh(initialSize, this.fallbackGeometry, this.shaderMaterial);
646
- this.mesh.material = this.shaderMaterial;
647
- }
648
- /**
649
- * Gets the instanced mesh.
650
- * @returns The instanced mesh.
651
- */
652
- getMesh() {
653
- return this.mesh;
654
- }
655
- /**
656
- * Updates the instanced mesh.
657
- * @param elapsedTime The elapsed time.
658
- */
659
- update(elapsedTime) {
660
- const material = this.mesh.material;
661
- if (material instanceof THREE.ShaderMaterial || material instanceof THREE.RawShaderMaterial) {
662
- material.uniforms.uTime.value = elapsedTime;
663
- }
664
- }
665
- updateTextureInterpolation(textureA, textureB, progress) {
666
- this.uniforms.uOriginTexture.value = textureA;
667
- this.uniforms.uDestinationTexture.value = textureB;
668
- this.uniforms.uProgress.value = progress;
669
- }
670
- setGeometrySize(size) {
671
- this.mesh.geometry.scale(1 / this.previousScale.x, 1 / this.previousScale.y, 1 / this.previousScale.z);
672
- this.mesh.geometry.scale(size.x, size.y, size.z);
673
- this.previousScale = size;
674
- }
675
- /**
676
- * Use the matcap material for the instanced mesh.
677
- */
678
- useMatcapMaterial() {
679
- this.mesh.material = this.shaderMaterial;
680
- }
681
- /**
682
- * Use the specified geometry for the instanced mesh.
683
- * @param id The ID of the geometry to use.
684
- */
685
- useGeometry(id) {
686
- const geometry = this.geometries.get(id);
687
- if (geometry) {
688
- this.mesh.geometry = geometry;
689
- }
690
- }
691
- /**
692
- * Updates the velocity texture.
693
- * @param texture The velocity texture to update with.
694
- */
695
- updateVelocityTexture(texture) {
696
- this.shaderMaterial.uniforms.uVelocity.value = texture;
697
- }
698
- /**
699
- * Updates the position texture.
700
- * @param texture The position texture to update with.
701
- */
702
- updatePositionTexture(texture) {
703
- this.shaderMaterial.uniforms.uTexture.value = texture;
704
- }
705
- /**
706
- * Resizes or replaces the instanced mesh.
707
- * @param size The new size of the instanced mesh.
708
- * @returns An object containing the updated mesh, the previous mesh, and a boolean indicating whether the mesh was updated.
709
- */
710
- resize(size) {
711
- if (this.size === size) return { current: this.mesh, previous: this.mesh };
712
- this.size = size;
713
- const prev = this.mesh;
714
- this.mesh = this.createInstancedMesh(size, prev.geometry, prev.material);
715
- return { current: this.mesh, previous: prev };
716
- }
717
- /**
718
- * Disposes the resources used by the InstancedMeshManager.
719
- */
720
- dispose() {
721
- this.mesh.dispose();
722
- this.geometries.forEach((geometry) => geometry.dispose());
723
- this.shaderMaterial.dispose();
724
- this.uvRefsCache.clear();
725
- this.geometries.clear();
726
- }
727
- /**
728
- * Registers a geometry.
729
- * @param id The ID of the geometry to register.
730
- * @param geometry The geometry to register.
731
- */
732
- registerGeometry(id, geometry) {
733
- const previous = this.geometries.get(id);
734
- if (previous) {
735
- if (previous === geometry) {
736
- return;
737
- }
738
- }
739
- const uvRefs = this.createUVRefs(this.size);
740
- geometry.setAttribute("uvRef", uvRefs);
741
- this.geometries.set(id, geometry);
742
- if (this.mesh.geometry === previous) {
743
- this.mesh.geometry = geometry;
744
- }
745
- previous == null ? void 0 : previous.dispose();
746
- }
747
- /**
748
- * Gets the UV references for the specified size.
749
- * @param size The size for which to generate UV references.
750
- * @returns The UV references.
751
- */
752
- createUVRefs(size) {
753
- const cached = this.uvRefsCache.get(size);
754
- if (cached) {
755
- return cached;
756
- }
757
- const uvRefs = new Float32Array(size * size * 2);
758
- for (let i = 0; i < size; i++) {
759
- for (let j = 0; j < size; j++) {
760
- const index = i * size + j;
761
- uvRefs[2 * index] = j / (size - 1);
762
- uvRefs[2 * index + 1] = i / (size - 1);
763
- }
764
- }
765
- const attr = new THREE.InstancedBufferAttribute(uvRefs, 2);
766
- this.uvRefsCache.set(size, attr);
767
- return attr;
768
- }
769
- /**
770
- * Creates a new instanced mesh.
771
- * @param size The size of the instanced mesh.
772
- * @param geometry The geometry to use for the instanced mesh.
773
- * @param material The material to use for the instanced mesh.
774
- * @returns The created instanced mesh.
775
- */
776
- createInstancedMesh(size, geometry, material) {
777
- geometry = geometry || this.fallbackGeometry;
778
- geometry.setAttribute("uvRef", this.createUVRefs(size));
779
- const count = size * size;
780
- return new THREE.InstancedMesh(geometry, material, count);
781
- }
782
- }
783
- class IntersectionService {
784
- /**
785
- * Creates a new IntersectionService instance.
786
- * @param eventEmitter The event emitter used for emitting events.
787
- * @param camera The camera used for raycasting.
788
- */
789
- constructor(eventEmitter, camera) {
790
- __publicField(this, "active", true);
791
- __publicField(this, "raycaster", new THREE.Raycaster());
792
- __publicField(this, "mousePosition", new THREE.Vector2());
793
- __publicField(this, "camera");
794
- __publicField(this, "meshSequenceGeometries", []);
795
- // ADDED: Store cloned geometries
796
- __publicField(this, "meshSequenceUUIDs", []);
797
- // ADDED: Track UUIDs to avoid redundant cloning
798
- __publicField(this, "overallProgress", 0);
799
- // ADDED: Store overall progress (0-1)
800
- __publicField(this, "intersectionMesh", new THREE.Mesh());
801
- // Use a single mesh for intersection target
802
- __publicField(this, "geometryNeedsUpdate");
803
- __publicField(this, "eventEmitter");
804
- __publicField(this, "blendedGeometry");
805
- // Keep for the final blended result
806
- __publicField(this, "intersection");
807
- this.camera = camera;
808
- this.eventEmitter = eventEmitter;
809
- this.geometryNeedsUpdate = true;
810
- }
811
- setActive(active) {
812
- this.active = active;
813
- if (!active) {
814
- this.intersection = void 0;
815
- this.eventEmitter.emit("interactionPositionUpdated", { position: { x: 0, y: 0, z: 0, w: 0 } });
816
- }
817
- }
818
- getIntersectionMesh() {
819
- return this.intersectionMesh;
820
- }
821
- /**
822
- * Set the camera used for raycasting.
823
- * @param camera
824
- */
825
- setCamera(camera) {
826
- this.camera = camera;
827
- }
828
- /**
829
- * Sets the sequence of meshes used for intersection calculations.
830
- * Clones the geometries to avoid modifying originals.
831
- * @param meshes An array of THREE.Mesh objects in sequence.
832
- */
833
- setMeshSequence(meshes) {
834
- this.meshSequenceGeometries.forEach((geom) => geom.dispose());
835
- this.meshSequenceGeometries = [];
836
- this.meshSequenceUUIDs = [];
837
- if (!meshes || meshes.length === 0) {
838
- this.geometryNeedsUpdate = true;
839
- return;
840
- }
841
- meshes.forEach((mesh) => {
842
- if (mesh && mesh.geometry) {
843
- const clonedGeometry = mesh.geometry.clone();
844
- clonedGeometry.applyMatrix4(mesh.matrixWorld);
845
- this.meshSequenceGeometries.push(clonedGeometry);
846
- this.meshSequenceUUIDs.push(mesh.uuid);
847
- } else {
848
- console.warn("Invalid mesh provided to IntersectionService sequence.");
849
- }
850
- });
851
- this.geometryNeedsUpdate = true;
852
- }
853
- /**
854
- * Set the overall progress through the mesh sequence.
855
- * @param progress Value between 0.0 (first mesh) and 1.0 (last mesh).
856
- */
857
- setOverallProgress(progress) {
858
- const newProgress = THREE.MathUtils.clamp(progress, 0, 1);
859
- if (this.overallProgress !== newProgress) {
860
- this.overallProgress = newProgress;
861
- this.geometryNeedsUpdate = true;
862
- }
863
- }
864
- /**
865
- * Set the mouse position.
866
- * @param mousePosition
867
- */
868
- setPointerPosition(mousePosition) {
869
- if (mousePosition) this.mousePosition.copy(mousePosition);
870
- }
871
- /**
872
- * Calculate the intersection.
873
- * @returns The intersection point or undefined if no intersection was found.
874
- */
875
- calculate(instancedMesh) {
876
- var _a, _b, _c;
877
- if (!this.active || !this.camera || this.meshSequenceGeometries.length === 0) {
878
- if (this.intersection) {
879
- this.intersection = void 0;
880
- this.eventEmitter.emit("interactionPositionUpdated", { position: { x: 0, y: 0, z: 0, w: 0 } });
881
- }
882
- return void 0;
883
- }
884
- if (this.geometryNeedsUpdate) {
885
- if (this.blendedGeometry && this.blendedGeometry !== this.intersectionMesh.geometry) {
886
- this.blendedGeometry.dispose();
887
- }
888
- this.blendedGeometry = this.getBlendedGeometry();
889
- this.geometryNeedsUpdate = false;
890
- if (this.blendedGeometry) {
891
- if (this.intersectionMesh.geometry !== this.blendedGeometry) {
892
- if (this.intersectionMesh.geometry) this.intersectionMesh.geometry.dispose();
893
- this.intersectionMesh.geometry = this.blendedGeometry;
894
- }
895
- } else {
896
- if (this.intersectionMesh.geometry) this.intersectionMesh.geometry.dispose();
897
- this.intersectionMesh.geometry = new THREE.BufferGeometry();
898
- }
899
- }
900
- this.intersectionMesh.matrixWorld.copy(instancedMesh.matrixWorld);
901
- let newIntersection = void 0;
902
- if (this.blendedGeometry && this.blendedGeometry.attributes.position) {
903
- newIntersection = this.getFirstIntersection(this.camera, this.intersectionMesh);
904
- }
905
- 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;
906
- if (hasChanged) {
907
- this.intersection = newIntersection;
908
- if (this.intersection) {
909
- const worldPoint = new THREE.Vector3(this.intersection.x, this.intersection.y, this.intersection.z);
910
- const localPoint = instancedMesh.worldToLocal(worldPoint.clone());
911
- this.intersection.set(localPoint.x, localPoint.y, localPoint.z, 1);
912
- this.eventEmitter.emit("interactionPositionUpdated", { position: this.intersection });
913
- } else {
914
- this.eventEmitter.emit("interactionPositionUpdated", { position: { x: 0, y: 0, z: 0, w: 0 } });
915
- }
916
- }
917
- return this.intersection;
918
- }
919
- /**
920
- * Dispose the resources used by the IntersectionService.
921
- */
922
- dispose() {
923
- var _a;
924
- this.meshSequenceGeometries.forEach((geom) => geom.dispose());
925
- this.meshSequenceGeometries = [];
926
- this.meshSequenceUUIDs = [];
927
- if (this.blendedGeometry && this.blendedGeometry !== this.intersectionMesh.geometry) {
928
- this.blendedGeometry.dispose();
929
- }
930
- (_a = this.intersectionMesh.geometry) == null ? void 0 : _a.dispose();
931
- }
932
- updateIntersectionMesh(instancedMesh) {
933
- if (this.blendedGeometry) {
934
- if (this.blendedGeometry.uuid !== this.intersectionMesh.geometry.uuid) {
935
- this.intersectionMesh.geometry.dispose();
936
- this.intersectionMesh.geometry = this.blendedGeometry;
937
- }
938
- }
939
- this.intersectionMesh.matrix.copy(instancedMesh.matrixWorld);
940
- this.intersectionMesh.matrixWorld.copy(instancedMesh.matrixWorld);
941
- this.intersectionMesh.matrixAutoUpdate = false;
942
- this.intersectionMesh.updateMatrixWorld(true);
943
- }
944
- getFirstIntersection(camera, targetMesh) {
945
- this.raycaster.setFromCamera(this.mousePosition, camera);
946
- const intersects = this.raycaster.intersectObject(targetMesh, false);
947
- if (intersects.length > 0 && intersects[0].point) {
948
- const worldPoint = intersects[0].point;
949
- return new THREE.Vector4(worldPoint.x, worldPoint.y, worldPoint.z, 1);
950
- }
951
- return void 0;
952
- }
953
- getBlendedGeometry() {
954
- const numGeometries = this.meshSequenceGeometries.length;
955
- if (numGeometries === 0) {
956
- return void 0;
957
- }
958
- if (numGeometries === 1) {
959
- return this.meshSequenceGeometries[0];
960
- }
961
- const totalSegments = numGeometries - 1;
962
- const progressPerSegment = 1 / totalSegments;
963
- const scaledProgress = this.overallProgress * totalSegments;
964
- let indexA = Math.floor(scaledProgress);
965
- let indexB = indexA + 1;
966
- indexA = THREE.MathUtils.clamp(indexA, 0, totalSegments);
967
- indexB = THREE.MathUtils.clamp(indexB, 0, totalSegments);
968
- let localProgress = 0;
969
- if (progressPerSegment > 0) {
970
- localProgress = scaledProgress - indexA;
971
- }
972
- if (this.overallProgress >= 1) {
973
- indexA = totalSegments;
974
- indexB = totalSegments;
975
- localProgress = 1;
976
- }
977
- localProgress = THREE.MathUtils.clamp(localProgress, 0, 1);
978
- const geomA = this.meshSequenceGeometries[indexA];
979
- const geomB = this.meshSequenceGeometries[indexB];
980
- if (!geomA || !geomB) {
981
- console.error("IntersectionService: Invalid geometries found for blending at indices", indexA, indexB);
982
- return this.meshSequenceGeometries[0];
983
- }
984
- if (indexA === indexB) {
985
- return geomA;
986
- }
987
- return this.blendGeometry(geomA, geomB, localProgress);
988
- }
989
- blendGeometry(from, to, progress) {
990
- const blended = new THREE.BufferGeometry();
991
- const originPositions = from.attributes.position.array;
992
- const destinationPositions = to.attributes.position.array;
993
- const blendedPositions = new Float32Array(originPositions.length);
994
- for (let i = 0; i < originPositions.length; i += 3) {
995
- const originVert = new THREE.Vector3(originPositions[i], originPositions[i + 1], originPositions[i + 2]);
996
- const destinationVert = new THREE.Vector3(destinationPositions[i], destinationPositions[i + 1], destinationPositions[i + 2]);
997
- const blendedVert = new THREE.Vector3().lerpVectors(originVert, destinationVert, progress);
998
- blendedPositions[i] = blendedVert.x;
999
- blendedPositions[i + 1] = blendedVert.y;
1000
- blendedPositions[i + 2] = blendedVert.z;
1001
- }
1002
- blended.setAttribute("position", new THREE.BufferAttribute(blendedPositions, 3));
1003
- if (from.attributes.normal) blended.setAttribute("normal", from.attributes.normal.clone());
1004
- if (from.attributes.uv) blended.setAttribute("uv", from.attributes.uv.clone());
1005
- if (from.index) blended.setIndex(from.index.clone());
1006
- return blended;
1007
- }
1008
- }
1009
- const _camera = new OrthographicCamera(-1, 1, 1, -1, 0, 1);
1010
- class FullscreenTriangleGeometry extends BufferGeometry {
1011
- constructor() {
1012
- super();
1013
- this.setAttribute("position", new Float32BufferAttribute([-1, 3, 0, -1, -1, 0, 3, -1, 0], 3));
1014
- this.setAttribute("uv", new Float32BufferAttribute([0, 2, 0, 0, 2, 0], 2));
1015
- }
1016
- }
1017
- const _geometry = new FullscreenTriangleGeometry();
1018
- class FullScreenQuad {
1019
- /**
1020
- * Constructs a new full screen quad.
1021
- *
1022
- * @param {?Material} material - The material to render te full screen quad with.
1023
- */
1024
- constructor(material) {
1025
- this._mesh = new Mesh(_geometry, material);
1026
- }
1027
- /**
1028
- * Frees the GPU-related resources allocated by this instance. Call this
1029
- * method whenever the instance is no longer used in your app.
1030
- */
1031
- dispose() {
1032
- this._mesh.geometry.dispose();
1033
- }
1034
- /**
1035
- * Renders the full screen quad.
1036
- *
1037
- * @param {WebGLRenderer} renderer - The renderer.
1038
- */
1039
- render(renderer) {
1040
- renderer.render(this._mesh, _camera);
1041
- }
1042
- /**
1043
- * The quad's material.
1044
- *
1045
- * @type {?Material}
1046
- */
1047
- get material() {
1048
- return this._mesh.material;
1049
- }
1050
- set material(value) {
1051
- this._mesh.material = value;
1052
- }
1053
- }
1054
- class GPUComputationRenderer {
1055
- /**
1056
- * Constructs a new GPU computation renderer.
1057
- *
1058
- * @param {number} sizeX - Computation problem size is always 2d: sizeX * sizeY elements.
1059
- * @param {number} sizeY - Computation problem size is always 2d: sizeX * sizeY elements.
1060
- * @param {WebGLRenderer} renderer - The renderer.
1061
- */
1062
- constructor(sizeX, sizeY, renderer) {
1063
- this.variables = [];
1064
- this.currentTextureIndex = 0;
1065
- let dataType = FloatType;
1066
- const passThruUniforms = {
1067
- passThruTexture: { value: null }
1068
- };
1069
- const passThruShader = createShaderMaterial(getPassThroughFragmentShader(), passThruUniforms);
1070
- const quad = new FullScreenQuad(passThruShader);
1071
- this.setDataType = function(type) {
1072
- dataType = type;
1073
- return this;
1074
- };
1075
- this.addVariable = function(variableName, computeFragmentShader, initialValueTexture) {
1076
- const material = this.createShaderMaterial(computeFragmentShader);
1077
- const variable = {
1078
- name: variableName,
1079
- initialValueTexture,
1080
- material,
1081
- dependencies: null,
1082
- renderTargets: [],
1083
- wrapS: null,
1084
- wrapT: null,
1085
- minFilter: NearestFilter,
1086
- magFilter: NearestFilter
1087
- };
1088
- this.variables.push(variable);
1089
- return variable;
1090
- };
1091
- this.setVariableDependencies = function(variable, dependencies) {
1092
- variable.dependencies = dependencies;
1093
- };
1094
- this.init = function() {
1095
- if (renderer.capabilities.maxVertexTextures === 0) {
1096
- return "No support for vertex shader textures.";
1097
- }
1098
- for (let i = 0; i < this.variables.length; i++) {
1099
- const variable = this.variables[i];
1100
- variable.renderTargets[0] = this.createRenderTarget(sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter);
1101
- variable.renderTargets[1] = this.createRenderTarget(sizeX, sizeY, variable.wrapS, variable.wrapT, variable.minFilter, variable.magFilter);
1102
- this.renderTexture(variable.initialValueTexture, variable.renderTargets[0]);
1103
- this.renderTexture(variable.initialValueTexture, variable.renderTargets[1]);
1104
- const material = variable.material;
1105
- const uniforms = material.uniforms;
1106
- if (variable.dependencies !== null) {
1107
- for (let d = 0; d < variable.dependencies.length; d++) {
1108
- const depVar = variable.dependencies[d];
1109
- if (depVar.name !== variable.name) {
1110
- let found = false;
1111
- for (let j = 0; j < this.variables.length; j++) {
1112
- if (depVar.name === this.variables[j].name) {
1113
- found = true;
1114
- break;
1115
- }
1116
- }
1117
- if (!found) {
1118
- return "Variable dependency not found. Variable=" + variable.name + ", dependency=" + depVar.name;
1119
- }
1120
- }
1121
- uniforms[depVar.name] = { value: null };
1122
- material.fragmentShader = "\nuniform sampler2D " + depVar.name + ";\n" + material.fragmentShader;
1123
- }
1124
- }
1125
- }
1126
- this.currentTextureIndex = 0;
1127
- return null;
1128
- };
1129
- this.compute = function() {
1130
- const currentTextureIndex = this.currentTextureIndex;
1131
- const nextTextureIndex = this.currentTextureIndex === 0 ? 1 : 0;
1132
- for (let i = 0, il = this.variables.length; i < il; i++) {
1133
- const variable = this.variables[i];
1134
- if (variable.dependencies !== null) {
1135
- const uniforms = variable.material.uniforms;
1136
- for (let d = 0, dl = variable.dependencies.length; d < dl; d++) {
1137
- const depVar = variable.dependencies[d];
1138
- uniforms[depVar.name].value = depVar.renderTargets[currentTextureIndex].texture;
1139
- }
1140
- }
1141
- this.doRenderTarget(variable.material, variable.renderTargets[nextTextureIndex]);
1142
- }
1143
- this.currentTextureIndex = nextTextureIndex;
1144
- };
1145
- this.getCurrentRenderTarget = function(variable) {
1146
- return variable.renderTargets[this.currentTextureIndex];
1147
- };
1148
- this.getAlternateRenderTarget = function(variable) {
1149
- return variable.renderTargets[this.currentTextureIndex === 0 ? 1 : 0];
1150
- };
1151
- this.dispose = function() {
1152
- quad.dispose();
1153
- const variables = this.variables;
1154
- for (let i = 0; i < variables.length; i++) {
1155
- const variable = variables[i];
1156
- if (variable.initialValueTexture) variable.initialValueTexture.dispose();
1157
- const renderTargets = variable.renderTargets;
1158
- for (let j = 0; j < renderTargets.length; j++) {
1159
- const renderTarget = renderTargets[j];
1160
- renderTarget.dispose();
1161
- }
1162
- }
1163
- };
1164
- function addResolutionDefine(materialShader) {
1165
- materialShader.defines.resolution = "vec2( " + sizeX.toFixed(1) + ", " + sizeY.toFixed(1) + " )";
1166
- }
1167
- this.addResolutionDefine = addResolutionDefine;
1168
- function createShaderMaterial(computeFragmentShader, uniforms) {
1169
- uniforms = uniforms || {};
1170
- const material = new ShaderMaterial({
1171
- name: "GPUComputationShader",
1172
- uniforms,
1173
- vertexShader: getPassThroughVertexShader(),
1174
- fragmentShader: computeFragmentShader
1175
- });
1176
- addResolutionDefine(material);
1177
- return material;
1178
- }
1179
- this.createShaderMaterial = createShaderMaterial;
1180
- this.createRenderTarget = function(sizeXTexture, sizeYTexture, wrapS, wrapT, minFilter, magFilter) {
1181
- sizeXTexture = sizeXTexture || sizeX;
1182
- sizeYTexture = sizeYTexture || sizeY;
1183
- wrapS = wrapS || ClampToEdgeWrapping;
1184
- wrapT = wrapT || ClampToEdgeWrapping;
1185
- minFilter = minFilter || NearestFilter;
1186
- magFilter = magFilter || NearestFilter;
1187
- const renderTarget = new WebGLRenderTarget(sizeXTexture, sizeYTexture, {
1188
- wrapS,
1189
- wrapT,
1190
- minFilter,
1191
- magFilter,
1192
- format: RGBAFormat,
1193
- type: dataType,
1194
- depthBuffer: false
1195
- });
1196
- return renderTarget;
1197
- };
1198
- this.createTexture = function() {
1199
- const data = new Float32Array(sizeX * sizeY * 4);
1200
- const texture = new DataTexture(data, sizeX, sizeY, RGBAFormat, FloatType);
1201
- texture.needsUpdate = true;
1202
- return texture;
1203
- };
1204
- this.renderTexture = function(input, output) {
1205
- passThruUniforms.passThruTexture.value = input;
1206
- this.doRenderTarget(passThruShader, output);
1207
- passThruUniforms.passThruTexture.value = null;
1208
- };
1209
- this.doRenderTarget = function(material, output) {
1210
- const currentRenderTarget = renderer.getRenderTarget();
1211
- const currentXrEnabled = renderer.xr.enabled;
1212
- const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
1213
- renderer.xr.enabled = false;
1214
- renderer.shadowMap.autoUpdate = false;
1215
- quad.material = material;
1216
- renderer.setRenderTarget(output);
1217
- quad.render(renderer);
1218
- quad.material = passThruShader;
1219
- renderer.xr.enabled = currentXrEnabled;
1220
- renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
1221
- renderer.setRenderTarget(currentRenderTarget);
1222
- };
1223
- function getPassThroughVertexShader() {
1224
- return "void main() {\n\n gl_Position = vec4( position, 1.0 );\n\n}\n";
1225
- }
1226
- function getPassThroughFragmentShader() {
1227
- return "uniform sampler2D passThruTexture;\n\nvoid main() {\n\n vec2 uv = gl_FragCoord.xy / resolution.xy;\n\n gl_FragColor = texture2D( passThruTexture, uv );\n\n}\n";
1228
- }
1229
- }
1230
- }
1231
- const positionShader = `
1232
- uniform vec4 uInteractionPosition;
1233
- uniform float uTime;
1234
- uniform float uTractionForce;
1235
- uniform sampler2D uPositionAtlas;
1236
- uniform float uOverallProgress; // (0.0 to 1.0)
1237
- uniform int uNumMeshes;
1238
- uniform float uSingleTextureSize;
1239
-
1240
- float rand(vec2 co) {
1241
- return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
1242
- }
1243
-
1244
- // Helper function to get position from atlas
1245
- vec3 getAtlasPosition(vec2 uv, int meshIndex) {
1246
- float atlasWidth = uSingleTextureSize * float(uNumMeshes);
1247
- float atlasHeight = uSingleTextureSize; // Assuming height is single texture size
1248
-
1249
- // Calculate UV within the specific mesh's section of the atlas
1250
- float segmentWidthRatio = uSingleTextureSize / atlasWidth;
1251
- vec2 atlasUV = vec2(
1252
- uv.x * segmentWidthRatio + segmentWidthRatio * float(meshIndex),
1253
- uv.y // Assuming vertical layout doesn't change y
1254
- );
1255
-
1256
- return texture2D(uPositionAtlas, atlasUV).xyz;
1257
- }
1258
-
1259
- void main() {
1260
- // GPGPU UV calculation
1261
- vec2 uv = gl_FragCoord.xy / resolution.xy; // resolution is the size of the *output* texture (e.g., 256x256)
1262
-
1263
- vec3 currentPosition = texture2D(uCurrentPosition, uv).xyz;
1264
- vec3 currentVelocity = texture2D(uCurrentVelocity, uv).xyz;
1265
-
1266
- // --- Calculate Target Position from Atlas ---
1267
- vec3 targetPosition;
1268
- if (uNumMeshes <= 1) {
1269
- targetPosition = getAtlasPosition(uv, 0);
1270
- } else {
1271
- float totalSegments = float(uNumMeshes - 1);
1272
- float progressPerSegment = 1.0 / totalSegments;
1273
- float scaledProgress = uOverallProgress * totalSegments;
1274
-
1275
- int indexA = int(floor(scaledProgress));
1276
- // Clamp indexB to avoid going out of bounds
1277
- int indexB = min(indexA + 1, uNumMeshes - 1);
1278
-
1279
- // Ensure indexA is also within bounds (important if uOverallProgress is exactly 1.0)
1280
- indexA = min(indexA, uNumMeshes - 1);
1281
-
1282
-
1283
- float localProgress = fract(scaledProgress);
1284
-
1285
- // Handle edge case where progress is exactly 1.0
1286
- if (uOverallProgress == 1.0) {
1287
- indexA = uNumMeshes - 1;
1288
- indexB = uNumMeshes - 1;
1289
- localProgress = 1.0; // or 0.0 depending on how you want to handle it
1290
- }
1291
-
1292
-
1293
- vec3 positionA = getAtlasPosition(uv, indexA);
1294
- vec3 positionB = getAtlasPosition(uv, indexB);
1295
-
1296
- targetPosition = mix(positionA, positionB, localProgress);
1297
- }
1298
- // --- End Target Position Calculation ---
1299
-
1300
- // Particle attraction to target position
1301
- vec3 direction = normalize(targetPosition - currentPosition);
1302
- float dist = length(targetPosition - currentPosition);
1303
-
1304
- vec3 finalPosition = currentPosition;
1305
-
1306
- // Apply attraction force (simplified mix)
1307
- if (dist > 0.01) { // Only apply if significantly far
1308
- finalPosition = mix(currentPosition, targetPosition, 0.1 * uTractionForce);
1309
- }
1310
-
1311
- finalPosition += currentVelocity;
1312
- gl_FragColor = vec4(finalPosition, 1.0);
1313
- }
1314
- `;
1315
- const velocityShader = `
1316
- uniform vec4 uInteractionPosition;
1317
- uniform float uTime;
1318
- uniform float uTractionForce;
1319
- uniform float uMaxRepelDistance;
1320
- uniform sampler2D uPositionAtlas;
1321
- uniform float uOverallProgress;
1322
- uniform int uNumMeshes;
1323
- uniform float uSingleTextureSize;
1324
-
1325
- float rand(vec2 co) {
1326
- return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);
1327
- }
1328
-
1329
- // Helper function (same as in position shader)
1330
- vec3 getAtlasPosition(vec2 uv, int meshIndex) {
1331
- float atlasWidth = uSingleTextureSize * float(uNumMeshes);
1332
- float atlasHeight = uSingleTextureSize;
1333
- float segmentWidthRatio = uSingleTextureSize / atlasWidth;
1334
- vec2 atlasUV = vec2(uv.x * segmentWidthRatio + segmentWidthRatio * float(meshIndex), uv.y);
1335
- return texture2D(uPositionAtlas, atlasUV).xyz;
1336
- }
1337
-
1338
- void main() {
1339
- vec2 uv = gl_FragCoord.xy / resolution.xy;
1340
- float offset = rand(uv);
1341
-
1342
- vec3 currentPosition = texture2D(uCurrentPosition, uv).xyz;
1343
- vec3 currentVelocity = texture2D(uCurrentVelocity, uv).xyz;
1344
-
1345
- // --- Calculate Target Position from Atlas (same logic as position shader) ---
1346
- vec3 targetPosition;
1347
- if (uNumMeshes <= 1) {
1348
- targetPosition = getAtlasPosition(uv, 0);
1349
- } else {
1350
- float totalSegments = float(uNumMeshes - 1);
1351
- float progressPerSegment = 1.0 / totalSegments;
1352
- float scaledProgress = uOverallProgress * totalSegments;
1353
- int indexA = int(floor(scaledProgress));
1354
- int indexB = min(indexA + 1, uNumMeshes - 1);
1355
- indexA = min(indexA, uNumMeshes - 1);
1356
- float localProgress = fract(scaledProgress);
1357
- if (uOverallProgress == 1.0) {
1358
- indexA = uNumMeshes - 1;
1359
- indexB = uNumMeshes - 1;
1360
- localProgress = 1.0;
1361
- }
1362
- vec3 positionA = getAtlasPosition(uv, indexA);
1363
- vec3 positionB = getAtlasPosition(uv, indexB);
1364
- targetPosition = mix(positionA, positionB, localProgress);
1365
- }
1366
- // --- End Target Position Calculation ---
1367
-
1368
- vec3 finalVelocity = currentVelocity * 0.9; // Dampening
1369
-
1370
- // Particle traction force towards target (influences velocity)
1371
- vec3 direction = normalize(targetPosition - currentPosition);
1372
- float dist = length(targetPosition - currentPosition);
1373
- if (dist > 0.01) {
1374
- // Add force proportional to distance and traction setting
1375
- finalVelocity += direction * dist * 0.01 * uTractionForce; // Adjust multiplier as needed
1376
- }
1377
-
1378
- // Mouse repel force
1379
- if (uInteractionPosition.w > 0.0) { // Check if interaction is active (w component)
1380
- float pointerDistance = distance(currentPosition, uInteractionPosition.xyz);
1381
- if (pointerDistance < uMaxRepelDistance) {
1382
- float mouseRepelModifier = smoothstep(uMaxRepelDistance, 0.0, pointerDistance); // Smoother falloff
1383
- vec3 repelDirection = normalize(currentPosition - uInteractionPosition.xyz);
1384
- // Apply force based on proximity and interaction strength (w)
1385
- finalVelocity += repelDirection * mouseRepelModifier * uInteractionPosition.w * 0.01; // Adjust multiplier
1386
- }
1387
- }
1388
-
1389
- // Optional: Reset position if particle "dies" and respawns (lifespan logic)
1390
- float lifespan = 20.0;
1391
- float age = mod(uTime * 0.1 + lifespan * offset, lifespan); // Adjust time scale
1392
- if (age < 0.05) { // Small window for reset
1393
- finalVelocity = vec3(0.0); // Reset velocity on respawn
1394
- // Note: Resetting position directly here might cause jumps.
1395
- // It's often better handled in the position shader or by ensuring
1396
- // strong attraction force when dist is large.
1397
- }
1398
-
1399
-
1400
- gl_FragColor = vec4(finalVelocity, 1.0);
1401
- }
1402
- `;
1403
- class SimulationRenderer {
1404
- /**
1405
- * Creates a new SimulationRenderer instance.
1406
- * @param size The size of the simulation textures (width/height).
1407
- * @param webGLRenderer The WebGL renderer.
1408
- * @param initialPosition The initial position data texture (optional, defaults to sphere).
1409
- */
1410
- constructor(size, webGLRenderer, initialPosition) {
1411
- __publicField(this, "gpuComputationRenderer");
1412
- __publicField(this, "webGLRenderer");
1413
- // GPGPU Variables
1414
- __publicField(this, "velocityVar");
1415
- __publicField(this, "positionVar");
1416
- // Input Data Textures (References)
1417
- __publicField(this, "initialPositionDataTexture");
1418
- // Used only for first init
1419
- __publicField(this, "initialVelocityDataTexture");
1420
- // Blank texture for init
1421
- __publicField(this, "positionAtlasTexture", null);
1422
- // Holds the current mesh sequence atlas
1423
- // Uniforms
1424
- __publicField(this, "interactionPosition");
1425
- // Cache last known output textures
1426
- __publicField(this, "lastKnownPositionDataTexture");
1427
- __publicField(this, "lastKnownVelocityDataTexture");
1428
- this.webGLRenderer = webGLRenderer;
1429
- this.gpuComputationRenderer = new GPUComputationRenderer(size, size, this.webGLRenderer);
1430
- if (!webGLRenderer.capabilities.isWebGL2 && webGLRenderer.extensions.get("OES_texture_float")) {
1431
- this.gpuComputationRenderer.setDataType(THREE.FloatType);
1432
- } else if (!webGLRenderer.capabilities.isWebGL2) {
1433
- this.gpuComputationRenderer.setDataType(THREE.HalfFloatType);
1434
- }
1435
- this.initialPositionDataTexture = initialPosition ?? createSpherePoints(size);
1436
- this.initialVelocityDataTexture = createBlankDataTexture(size);
1437
- this.interactionPosition = new THREE.Vector4(0, 0, 0, 0);
1438
- this.velocityVar = this.gpuComputationRenderer.addVariable("uCurrentVelocity", velocityShader, this.initialVelocityDataTexture);
1439
- this.positionVar = this.gpuComputationRenderer.addVariable("uCurrentPosition", positionShader, this.initialPositionDataTexture);
1440
- this.velocityVar.material.uniforms.uTime = { value: 0 };
1441
- this.velocityVar.material.uniforms.uInteractionPosition = { value: this.interactionPosition };
1442
- this.velocityVar.material.uniforms.uCurrentPosition = { value: null };
1443
- this.velocityVar.material.uniforms.uTractionForce = { value: 0.1 };
1444
- this.velocityVar.material.uniforms.uMaxRepelDistance = { value: 0.3 };
1445
- this.velocityVar.material.uniforms.uPositionAtlas = { value: null };
1446
- this.velocityVar.material.uniforms.uOverallProgress = { value: 0 };
1447
- this.velocityVar.material.uniforms.uNumMeshes = { value: 1 };
1448
- this.velocityVar.material.uniforms.uSingleTextureSize = { value: size };
1449
- this.positionVar.material.uniforms.uTime = { value: 0 };
1450
- this.positionVar.material.uniforms.uTractionForce = { value: 0.1 };
1451
- this.positionVar.material.uniforms.uInteractionPosition = { value: this.interactionPosition };
1452
- this.positionVar.material.uniforms.uCurrentPosition = { value: null };
1453
- this.positionVar.material.uniforms.uCurrentVelocity = { value: null };
1454
- this.positionVar.material.uniforms.uPositionAtlas = { value: null };
1455
- this.positionVar.material.uniforms.uOverallProgress = { value: 0 };
1456
- this.positionVar.material.uniforms.uNumMeshes = { value: 1 };
1457
- this.positionVar.material.uniforms.uSingleTextureSize = { value: size };
1458
- this.gpuComputationRenderer.setVariableDependencies(this.positionVar, [this.positionVar, this.velocityVar]);
1459
- this.gpuComputationRenderer.setVariableDependencies(this.velocityVar, [this.velocityVar, this.positionVar]);
1460
- const initError = this.gpuComputationRenderer.init();
1461
- if (initError !== null) {
1462
- throw new Error("Failed to initialize SimulationRenderer: " + initError);
1463
- }
1464
- this.positionVar.material.uniforms.uPositionAtlas.value = this.initialPositionDataTexture;
1465
- this.positionVar.material.uniforms.uNumMeshes.value = 1;
1466
- this.velocityVar.material.uniforms.uNumMeshes.value = 1;
1467
- this.positionVar.material.uniforms.uSingleTextureSize.value = size;
1468
- this.velocityVar.material.uniforms.uSingleTextureSize.value = size;
1469
- this.positionVar.material.uniforms.uCurrentVelocity.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
1470
- this.velocityVar.material.uniforms.uCurrentPosition.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
1471
- this.lastKnownVelocityDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
1472
- this.lastKnownPositionDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
1473
- }
1474
- /**
1475
- * Sets the mesh sequence position atlas texture and related uniforms.
1476
- * @param entry Information about the atlas texture.
1477
- */
1478
- setPositionAtlas(entry) {
1479
- const expectedAtlasWidth = entry.singleTextureSize * entry.numMeshes;
1480
- if (entry.dataTexture.image.width !== expectedAtlasWidth || entry.dataTexture.image.height !== entry.singleTextureSize) {
1481
- console.error(
1482
- `SimulationRenderer: Atlas texture dimension mismatch! Expected ${expectedAtlasWidth}x${entry.singleTextureSize}, Got ${entry.dataTexture.image.width}x${entry.dataTexture.image.height}`
1483
- );
1484
- }
1485
- this.positionAtlasTexture = entry.dataTexture;
1486
- const numMeshes = entry.numMeshes > 0 ? entry.numMeshes : 1;
1487
- this.positionVar.material.uniforms.uPositionAtlas.value = this.positionAtlasTexture;
1488
- this.positionVar.material.uniforms.uNumMeshes.value = numMeshes;
1489
- this.positionVar.material.uniforms.uSingleTextureSize.value = entry.singleTextureSize;
1490
- this.velocityVar.material.uniforms.uPositionAtlas.value = this.positionAtlasTexture;
1491
- this.velocityVar.material.uniforms.uNumMeshes.value = numMeshes;
1492
- this.velocityVar.material.uniforms.uSingleTextureSize.value = entry.singleTextureSize;
1493
- this.positionVar.material.uniforms.uCurrentVelocity.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
1494
- this.velocityVar.material.uniforms.uCurrentPosition.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
1495
- }
1496
- /**
1497
- * Sets the overall progress for blending between meshes in the atlas.
1498
- * @param progress Value between 0.0 and 1.0.
1499
- */
1500
- setOverallProgress(progress) {
1501
- const clampedProgress = clamp(progress, 0, 1);
1502
- this.positionVar.material.uniforms.uOverallProgress.value = clampedProgress;
1503
- this.velocityVar.material.uniforms.uOverallProgress.value = clampedProgress;
1504
- }
1505
- setMaxRepelDistance(distance) {
1506
- this.velocityVar.material.uniforms.uMaxRepelDistance.value = distance;
1507
- }
1508
- setVelocityTractionForce(force) {
1509
- this.velocityVar.material.uniforms.uTractionForce.value = force;
1510
- }
1511
- setPositionalTractionForce(force) {
1512
- this.positionVar.material.uniforms.uTractionForce.value = force;
1513
- }
1514
- setInteractionPosition(position) {
1515
- this.interactionPosition.copy(position);
1516
- }
1517
- /**
1518
- * Disposes the resources used by the simulation renderer.
1519
- */
1520
- dispose() {
1521
- var _a, _b;
1522
- this.gpuComputationRenderer.dispose();
1523
- (_a = this.initialPositionDataTexture) == null ? void 0 : _a.dispose();
1524
- (_b = this.initialVelocityDataTexture) == null ? void 0 : _b.dispose();
1525
- this.positionAtlasTexture = null;
1526
- }
1527
- /**
1528
- * Computes the next step of the simulation.
1529
- * @param deltaTime The time elapsed since the last frame, in seconds.
1530
- */
1531
- compute(deltaTime) {
1532
- this.velocityVar.material.uniforms.uTime.value += deltaTime;
1533
- this.positionVar.material.uniforms.uTime.value += deltaTime;
1534
- this.positionVar.material.uniforms.uCurrentVelocity.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
1535
- this.velocityVar.material.uniforms.uCurrentPosition.value = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
1536
- this.gpuComputationRenderer.compute();
1537
- this.lastKnownVelocityDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture;
1538
- this.lastKnownPositionDataTexture = this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture;
1539
- }
1540
- /** Gets the current velocity texture (output from the last compute step). */
1541
- getVelocityTexture() {
1542
- return this.lastKnownVelocityDataTexture;
1543
- }
1544
- /** Gets the current position texture (output from the last compute step). */
1545
- getPositionTexture() {
1546
- return this.lastKnownPositionDataTexture;
1547
- }
1548
- }
1549
- class SimulationRendererService {
1550
- constructor(eventEmitter, size, webGLRenderer) {
1551
- __publicField(this, "state");
1552
- __publicField(this, "textureSize");
1553
- __publicField(this, "overallProgress");
1554
- // ADDED: Store overall progress
1555
- __publicField(this, "velocityTractionForce");
1556
- __publicField(this, "positionalTractionForce");
1557
- __publicField(this, "simulationRenderer");
1558
- __publicField(this, "webGLRenderer");
1559
- __publicField(this, "eventEmitter");
1560
- // Store atlas info
1561
- __publicField(this, "currentAtlasEntry", null);
1562
- // ADDED
1563
- __publicField(this, "lastKnownVelocityDataTexture");
1564
- __publicField(this, "lastKnownPositionDataTexture");
1565
- this.eventEmitter = eventEmitter;
1566
- this.webGLRenderer = webGLRenderer;
1567
- this.textureSize = size;
1568
- this.overallProgress = 0;
1569
- this.velocityTractionForce = 0.1;
1570
- this.positionalTractionForce = 0.1;
1571
- this.updateServiceState("initializing");
1572
- this.simulationRenderer = new SimulationRenderer(this.textureSize, this.webGLRenderer);
1573
- this.lastKnownVelocityDataTexture = this.simulationRenderer.getVelocityTexture();
1574
- this.lastKnownPositionDataTexture = this.simulationRenderer.getPositionTexture();
1575
- this.updateServiceState("ready");
1576
- }
1577
- /**
1578
- * Sets the position data texture atlas for the simulation.
1579
- * @param entry An object containing the atlas texture and related parameters.
1580
- */
1581
- setPositionAtlas(entry) {
1582
- const expectedWidth = entry.singleTextureSize * entry.numMeshes;
1583
- if (entry.dataTexture.image.width !== expectedWidth) {
1584
- this.eventEmitter.emit("invalidRequest", { message: `Atlas texture width mismatch.` });
1585
- return;
1586
- }
1587
- this.currentAtlasEntry = entry;
1588
- this.simulationRenderer.setPositionAtlas(entry);
1589
- }
1590
- /**
1591
- * Sets the overall progress for the mesh sequence transition.
1592
- * @param progress The progress value (0.0 to 1.0).
1593
- */
1594
- setOverallProgress(progress) {
1595
- this.overallProgress = progress;
1596
- this.simulationRenderer.setOverallProgress(this.overallProgress);
1597
- }
1598
- setTextureSize(size) {
1599
- this.updateServiceState("initializing");
1600
- this.simulationRenderer.dispose();
1601
- this.textureSize = size;
1602
- this.simulationRenderer = new SimulationRenderer(size, this.webGLRenderer);
1603
- this.updateServiceState("ready");
1604
- }
1605
- setVelocityTractionForce(force) {
1606
- this.velocityTractionForce = force;
1607
- this.simulationRenderer.setVelocityTractionForce(this.velocityTractionForce);
1608
- }
1609
- setPositionalTractionForce(force) {
1610
- this.positionalTractionForce = force;
1611
- this.simulationRenderer.setPositionalTractionForce(this.positionalTractionForce);
1612
- }
1613
- compute(elapsedTime) {
1614
- if (this.state !== "ready") return;
1615
- this.simulationRenderer.compute(elapsedTime);
1616
- this.lastKnownVelocityDataTexture = this.simulationRenderer.getVelocityTexture();
1617
- this.lastKnownPositionDataTexture = this.simulationRenderer.getPositionTexture();
1618
- }
1619
- getVelocityTexture() {
1620
- return this.lastKnownVelocityDataTexture;
1621
- }
1622
- getPositionTexture() {
1623
- return this.lastKnownPositionDataTexture;
1624
- }
1625
- dispose() {
1626
- this.updateServiceState("disposed");
1627
- this.simulationRenderer.dispose();
1628
- this.currentAtlasEntry = null;
1629
- }
1630
- updateServiceState(serviceState) {
1631
- this.state = serviceState;
1632
- this.eventEmitter.emit("serviceStateUpdated", { type: "simulation", state: serviceState });
1633
- }
1634
- setInteractionPosition(position) {
1635
- this.simulationRenderer.setInteractionPosition(position);
1636
- }
1637
- setMaxRepelDistance(distance) {
1638
- this.simulationRenderer.setMaxRepelDistance(distance);
1639
- }
1640
- }
1641
- class ExecutionStatusMap {
1642
- constructor() {
1643
- __publicField(this, "execStatus", /* @__PURE__ */ new Map());
1644
- }
1645
- get(type) {
1646
- const status = this.execStatus.get(type);
1647
- if (!status) {
1648
- this.execStatus.set(type, "idle");
1649
- return "idle";
1650
- }
1651
- return status;
1652
- }
1653
- set(type, status) {
1654
- this.execStatus.set(type, status);
1655
- }
1656
- }
1657
- class TransitionService {
1658
- constructor(eventEmitter) {
1659
- __publicField(this, "eventEmitter");
1660
- __publicField(this, "transitions", /* @__PURE__ */ new Map());
1661
- __publicField(this, "execStatus");
1662
- __publicField(this, "ongoingTransitions", /* @__PURE__ */ new Map());
1663
- this.eventEmitter = eventEmitter;
1664
- this.execStatus = new ExecutionStatusMap();
1665
- this.eventEmitter.on("transitionCancelled", this.handleTransitionCancelledEvent.bind(this));
1666
- }
1667
- /**
1668
- * Enqueues a transition.
1669
- * @param type - The type of transition.
1670
- * @param transition - The transition details.
1671
- * @param options - Optional transition options.
1672
- */
1673
- enqueue(type, transition, options = {}) {
1674
- const transitionQueueItem = {
1675
- ...transition,
1676
- ...options,
1677
- cancelled: false,
1678
- duration: transition.duration * 1e-3
1679
- // convert to seconds
1680
- };
1681
- this.getQueue(type).push(transitionQueueItem);
1682
- }
1683
- compute(elapsedTime) {
1684
- this.transitions.forEach((queue, type) => {
1685
- var _a;
1686
- if (queue.length && !this.ongoingTransitions.has(type)) {
1687
- const transition = queue.shift();
1688
- if (transition) {
1689
- this.ongoingTransitions.set(type, { ...transition, startTime: elapsedTime });
1690
- (_a = transition.onTransitionBegin) == null ? void 0 : _a.call(transition);
1691
- }
1692
- }
1693
- });
1694
- this.ongoingTransitions.forEach((transition, type) => {
1695
- var _a, _b, _c;
1696
- if (transition.cancelled) {
1697
- (_a = transition.onTransitionCancelled) == null ? void 0 : _a.call(transition);
1698
- this.ongoingTransitions.delete(type);
1699
- return;
1700
- }
1701
- const { startTime, duration, easing } = transition;
1702
- const timeDistance = elapsedTime - startTime;
1703
- const progress = clamp(easing(Math.min(1, timeDistance / duration)), 0, 1);
1704
- this.emitTransitionProgress(type, progress);
1705
- (_b = transition.onTransitionProgress) == null ? void 0 : _b.call(transition, progress);
1706
- if (progress >= 1) {
1707
- this.emitTransitionFinished(type);
1708
- (_c = transition.onTransitionFinished) == null ? void 0 : _c.call(transition);
1709
- this.ongoingTransitions.delete(type);
1710
- }
1711
- });
1712
- }
1713
- getQueue(type) {
1714
- const queue = this.transitions.get(type);
1715
- if (!queue) {
1716
- this.transitions.set(type, []);
1717
- return this.transitions.get(type) ?? [];
1718
- }
1719
- return queue;
1720
- }
1721
- handleTransitionCancelledEvent({ type }) {
1722
- var _a;
1723
- const transitions = this.getQueue(type);
1724
- while (transitions.length) transitions.pop();
1725
- const ongoingTransition = this.ongoingTransitions.get(type);
1726
- if (ongoingTransition) {
1727
- ongoingTransition.cancelled = true;
1728
- (_a = ongoingTransition.onTransitionCancelled) == null ? void 0 : _a.call(ongoingTransition);
1729
- }
1730
- }
1731
- emitTransitionProgress(type, progress) {
1732
- this.eventEmitter.emit("transitionProgressed", { type, progress });
1733
- }
1734
- emitTransitionFinished(type) {
1735
- this.eventEmitter.emit("transitionFinished", { type });
1736
- }
1737
- }
1738
- class ParticlesEngine {
1739
- /**
1740
- * Creates a new ParticlesEngine instance.
1741
- * @param params The parameters for creating the instance.
1742
- */
1743
- constructor(params) {
1744
- __publicField(this, "simulationRendererService");
1745
- __publicField(this, "renderer");
1746
- __publicField(this, "scene");
1747
- __publicField(this, "serviceStates");
1748
- // assets
1749
- __publicField(this, "assetService");
1750
- __publicField(this, "dataTextureManager");
1751
- __publicField(this, "instancedMeshManager");
1752
- __publicField(this, "transitionService");
1753
- __publicField(this, "engineState");
1754
- __publicField(this, "intersectionService");
1755
- __publicField(this, "meshSequenceAtlasTexture", null);
1756
- // ADDED: To store the generated atlas
1757
- __publicField(this, "eventEmitter");
1758
- const { scene, renderer, camera, textureSize, useIntersection = true } = params;
1759
- this.eventEmitter = new DefaultEventEmitter();
1760
- this.serviceStates = this.getInitialServiceStates();
1761
- this.eventEmitter.on("serviceStateUpdated", this.handleServiceStateUpdated.bind(this));
1762
- this.scene = scene;
1763
- this.renderer = renderer;
1764
- this.engineState = this.initialEngineState(params);
1765
- this.assetService = new AssetService(this.eventEmitter);
1766
- this.transitionService = new TransitionService(this.eventEmitter);
1767
- this.dataTextureManager = new DataTextureService(this.eventEmitter, textureSize);
1768
- this.simulationRendererService = new SimulationRendererService(this.eventEmitter, textureSize, this.renderer);
1769
- this.instancedMeshManager = new InstancedMeshManager(textureSize);
1770
- this.scene.add(this.instancedMeshManager.getMesh());
1771
- this.intersectionService = new IntersectionService(this.eventEmitter, camera);
1772
- if (!useIntersection) this.intersectionService.setActive(false);
1773
- this.setOverallProgress(0, false);
1774
- this.eventEmitter.on("interactionPositionUpdated", this.handleInteractionPositionUpdated.bind(this));
1775
- }
1776
- /**
1777
- * Renders the scene.
1778
- * @param elapsedTime The elapsed time since the last frame.
1779
- */
1780
- render(elapsedTime) {
1781
- const dt = elapsedTime / 1e3;
1782
- this.transitionService.compute(dt);
1783
- this.intersectionService.calculate(this.instancedMeshManager.getMesh());
1784
- this.simulationRendererService.compute(dt);
1785
- this.instancedMeshManager.update(dt);
1786
- this.instancedMeshManager.updateVelocityTexture(this.simulationRendererService.getVelocityTexture());
1787
- this.instancedMeshManager.updatePositionTexture(this.simulationRendererService.getPositionTexture());
1788
- }
1789
- setTextureSequence(sequence) {
1790
- this.engineState.textureSequence = sequence;
1791
- this.eventEmitter.emit("textureSequenceUpdated", { sequence });
1792
- this.setOverallProgress(0, false);
1793
- }
1794
- // --- Update setTextureSize ---
1795
- async setTextureSize(size) {
1796
- if (this.engineState.textureSize === size) {
1797
- return;
1798
- }
1799
- this.engineState.textureSize = size;
1800
- this.dataTextureManager.setTextureSize(size);
1801
- this.simulationRendererService.setTextureSize(size);
1802
- this.instancedMeshManager.resize(size);
1803
- if (this.engineState.meshSequence.length > 0) {
1804
- await this.setMeshSequence(this.engineState.meshSequence);
1805
- }
1806
- if (this.engineState.meshSequence.length > 0) {
1807
- await this.setMeshSequence(this.engineState.meshSequence);
1808
- }
1809
- this.simulationRendererService.setVelocityTractionForce(this.engineState.velocityTractionForce);
1810
- this.simulationRendererService.setPositionalTractionForce(this.engineState.positionalTractionForce);
1811
- this.simulationRendererService.setMaxRepelDistance(this.engineState.maxRepelDistance);
1812
- this.simulationRendererService.setOverallProgress(this.engineState.overallProgress);
1813
- this.intersectionService.setOverallProgress(this.engineState.overallProgress);
1814
- this.instancedMeshManager.setGeometrySize(this.engineState.instanceGeometryScale);
1815
- this.setOverallProgress(this.engineState.overallProgress, false);
1816
- }
1817
- registerMesh(id, mesh) {
1818
- this.assetService.register(id, mesh);
1819
- }
1820
- registerMatcap(id, matcap) {
1821
- this.assetService.register(id, matcap);
1822
- }
1823
- async fetchAndRegisterMesh(id, url) {
1824
- return await this.assetService.loadMeshAsync(id, url);
1825
- }
1826
- async fetchAndRegisterMatcap(id, url) {
1827
- return await this.assetService.loadTextureAsync(id, url);
1828
- }
1829
- useIntersect(use) {
1830
- this.intersectionService.setActive(use);
1831
- this.engineState.useIntersect = use;
1832
- if (!use) {
1833
- this.engineState.pointerPosition = { x: -99999999, y: -99999999 };
1834
- this.simulationRendererService.setInteractionPosition({ x: 0, y: 0, z: 0, w: 0 });
1835
- }
1836
- }
1837
- setPointerPosition(position) {
1838
- if (!this.engineState.useIntersect) return;
1839
- this.engineState.pointerPosition = position;
1840
- this.intersectionService.setPointerPosition(position);
1841
- }
1842
- setGeometrySize(geometrySize) {
1843
- this.engineState.instanceGeometryScale = geometrySize;
1844
- this.instancedMeshManager.setGeometrySize(geometrySize);
1845
- }
1846
- setVelocityTractionForce(force) {
1847
- this.engineState.velocityTractionForce = force;
1848
- this.simulationRendererService.setVelocityTractionForce(force);
1849
- }
1850
- setPositionalTractionForce(force) {
1851
- this.engineState.positionalTractionForce = force;
1852
- this.simulationRendererService.setPositionalTractionForce(force);
1853
- }
1854
- setMaxRepelDistance(distance) {
1855
- this.engineState.maxRepelDistance = distance;
1856
- this.simulationRendererService.setMaxRepelDistance(distance);
1857
- }
1858
- /**
1859
- * Sets the sequence of meshes for particle transitions.
1860
- * This will generate a texture atlas containing position data for all meshes.
1861
- * @param meshIDs An array of registered mesh IDs in the desired sequence order.
1862
- */
1863
- async setMeshSequence(meshIDs) {
1864
- if (!meshIDs || meshIDs.length < 1) {
1865
- this.eventEmitter.emit("invalidRequest", { message: "Mesh sequence must contain at least one mesh ID." });
1866
- this.engineState.meshSequence = [];
1867
- this.intersectionService.setMeshSequence([]);
1868
- return;
1869
- }
1870
- this.engineState.meshSequence = meshIDs;
1871
- this.engineState.overallProgress = 0;
1872
- const meshes = meshIDs.map((id) => this.assetService.getMesh(id)).filter((mesh) => mesh !== null);
1873
- if (meshes.length !== meshIDs.length) {
1874
- const missing = meshIDs.filter((id) => !this.assetService.getMesh(id));
1875
- console.warn(`Could not find meshes for IDs: ${missing.join(", ")}. Proceeding with ${meshes.length} found meshes.`);
1876
- this.eventEmitter.emit("invalidRequest", { message: `Could not find meshes for IDs: ${missing.join(", ")}` });
1877
- if (meshes.length < 1) {
1878
- this.engineState.meshSequence = [];
1879
- this.intersectionService.setMeshSequence([]);
1880
- return;
1881
- }
1882
- this.engineState.meshSequence = meshes.map((m) => m.name);
1883
- }
1884
- try {
1885
- this.meshSequenceAtlasTexture = await this.dataTextureManager.createSequenceDataTextureAtlas(meshes, this.engineState.textureSize);
1886
- this.simulationRendererService.setPositionAtlas({
1887
- dataTexture: this.meshSequenceAtlasTexture,
1888
- textureSize: this.engineState.textureSize,
1889
- // Pass the size of the *output* GPGPU texture
1890
- numMeshes: this.engineState.meshSequence.length,
1891
- // Use the potentially updated count
1892
- singleTextureSize: this.engineState.textureSize
1893
- // Size of one mesh's data within atlas
1894
- });
1895
- this.simulationRendererService.setOverallProgress(this.engineState.overallProgress);
1896
- this.intersectionService.setMeshSequence(meshes);
1897
- this.intersectionService.setOverallProgress(this.engineState.overallProgress);
1898
- this.setOverallProgress(0, false);
1899
- } catch (error) {
1900
- console.error("Failed during mesh sequence setup:", error);
1901
- this.meshSequenceAtlasTexture = null;
1902
- }
1903
- }
1904
- /**
1905
- * Sets the overall progress through the mesh sequence.
1906
- * @param progress A value between 0.0 (first mesh) and 1.0 (last mesh).
1907
- * @param override If true, cancels any ongoing mesh sequence transition before setting the value. Defaults to true.
1908
- */
1909
- setOverallProgress(progress, override = true) {
1910
- if (override) {
1911
- this.eventEmitter.emit("transitionCancelled", { type: "mesh-sequence" });
1912
- }
1913
- const clampedProgress = clamp(progress, 0, 1);
1914
- this.engineState.overallProgress = clampedProgress;
1915
- this.simulationRendererService.setOverallProgress(clampedProgress);
1916
- this.intersectionService.setOverallProgress(clampedProgress);
1917
- const { textureA, textureB, localProgress } = this.calculateTextureInterpolation(progress);
1918
- this.instancedMeshManager.updateTextureInterpolation(textureA, textureB, localProgress);
1919
- }
1920
- /**
1921
- * Schedules a smooth transition for the overall mesh sequence progress.
1922
- * @param targetProgress The final progress value (0.0 to 1.0) to transition to.
1923
- * @param duration Duration of the transition in milliseconds.
1924
- * @param easing Easing function to use.
1925
- * @param options Transition options (onBegin, onProgress, onFinished, onCancelled).
1926
- * @param override If true, cancels any ongoing mesh sequence transitions.
1927
- */
1928
- scheduleMeshSequenceTransition(targetProgress, duration = 1e3, easing = linear, options = {}, override = true) {
1929
- if (override) this.eventEmitter.emit("transitionCancelled", { type: "mesh-sequence" });
1930
- const startProgress = this.engineState.overallProgress;
1931
- const progressDiff = targetProgress - startProgress;
1932
- const handleProgressUpdate = (transitionProgress) => {
1933
- var _a;
1934
- const currentOverallProgress = startProgress + progressDiff * transitionProgress;
1935
- this.setOverallProgress(currentOverallProgress, false);
1936
- (_a = options.onTransitionProgress) == null ? void 0 : _a.call(options, currentOverallProgress);
1937
- };
1938
- const transitionDetail = { duration, easing };
1939
- const transitionOptions = {
1940
- ...options,
1941
- onTransitionProgress: handleProgressUpdate,
1942
- onTransitionBegin: options.onTransitionBegin,
1943
- onTransitionFinished: () => {
1944
- var _a;
1945
- this.setOverallProgress(targetProgress, false);
1946
- (_a = options.onTransitionFinished) == null ? void 0 : _a.call(options);
1947
- },
1948
- onTransitionCancelled: options.onTransitionCancelled
1949
- };
1950
- this.transitionService.enqueue("mesh-sequence", transitionDetail, transitionOptions);
1951
- }
1952
- handleServiceStateUpdated({ type, state }) {
1953
- this.serviceStates[type] = state;
1954
- }
1955
- getObject() {
1956
- return this.instancedMeshManager.getMesh();
1957
- }
1958
- getMeshIDs() {
1959
- return this.assetService.getMeshIDs();
1960
- }
1961
- getMatcapIDs() {
1962
- return this.assetService.getTextureIDs();
1963
- }
1964
- getMeshes() {
1965
- return this.assetService.getMeshes();
1966
- }
1967
- getTextures() {
1968
- return this.assetService.getTextures();
1969
- }
1970
- getTextureSize() {
1971
- return this.engineState.textureSize;
1972
- }
1973
- getUseIntersect() {
1974
- return this.engineState.useIntersect;
1975
- }
1976
- getEngineStateSnapshot() {
1977
- return { ...this.engineState };
1978
- }
1979
- /**
1980
- * Disposes the resources used by the engine.
1981
- */
1982
- dispose() {
1983
- var _a, _b, _c, _d, _e, _f;
1984
- if (this.scene && this.instancedMeshManager) {
1985
- this.scene.remove(this.instancedMeshManager.getMesh());
1986
- }
1987
- (_a = this.simulationRendererService) == null ? void 0 : _a.dispose();
1988
- (_b = this.instancedMeshManager) == null ? void 0 : _b.dispose();
1989
- (_c = this.intersectionService) == null ? void 0 : _c.dispose();
1990
- (_d = this.assetService) == null ? void 0 : _d.dispose();
1991
- (_e = this.dataTextureManager) == null ? void 0 : _e.dispose();
1992
- (_f = this.eventEmitter) == null ? void 0 : _f.dispose();
1993
- }
1994
- initialEngineState(params) {
1995
- return {
1996
- textureSize: params.textureSize,
1997
- meshSequence: [],
1998
- // ADDED
1999
- overallProgress: 0,
2000
- // ADDED
2001
- textureSequence: [],
2002
- velocityTractionForce: 0.1,
2003
- positionalTractionForce: 0.1,
2004
- maxRepelDistance: 0.3,
2005
- pointerPosition: { x: 0, y: 0 },
2006
- instanceGeometryScale: { x: 1, y: 1, z: 1 },
2007
- useIntersect: params.useIntersection ?? true
2008
- };
2009
- }
2010
- getInitialServiceStates() {
2011
- return {
2012
- "data-texture": "created",
2013
- "instanced-mesh": "created",
2014
- matcap: "created",
2015
- simulation: "created",
2016
- asset: "created"
2017
- };
2018
- }
2019
- handleInteractionPositionUpdated({ position }) {
2020
- this.simulationRendererService.setInteractionPosition(position);
2021
- }
2022
- calculateTextureInterpolation(progress) {
2023
- const sequence = this.engineState.textureSequence;
2024
- const numItems = sequence.length;
2025
- if (numItems === 0) {
2026
- const defaultTex = this.assetService.getFallbackTexture();
2027
- return { textureA: defaultTex, textureB: defaultTex, localProgress: 0 };
2028
- }
2029
- if (numItems === 1) {
2030
- const tex = this.getTextureForSequenceItem(sequence[0]);
2031
- return { textureA: tex, textureB: tex, localProgress: 0 };
2032
- }
2033
- const totalSegments = numItems - 1;
2034
- const progressPerSegment = 1 / totalSegments;
2035
- const scaledProgress = progress * totalSegments;
2036
- let indexA = Math.floor(scaledProgress);
2037
- let indexB = indexA + 1;
2038
- indexA = Math.max(0, Math.min(indexA, totalSegments));
2039
- indexB = Math.max(0, Math.min(indexB, totalSegments));
2040
- let localProgress = 0;
2041
- if (progressPerSegment > 0) {
2042
- localProgress = (progress - indexA * progressPerSegment) / progressPerSegment;
2043
- }
2044
- if (progress >= 1) {
2045
- indexA = totalSegments;
2046
- indexB = totalSegments;
2047
- localProgress = 1;
2048
- }
2049
- localProgress = Math.max(0, Math.min(localProgress, 1));
2050
- const itemA = sequence[indexA];
2051
- const itemB = sequence[indexB];
2052
- const textureA = this.getTextureForSequenceItem(itemA);
2053
- const textureB = this.getTextureForSequenceItem(itemB);
2054
- return { textureA, textureB, localProgress };
2055
- }
2056
- getTextureForSequenceItem(item) {
2057
- if (item.type === "matcap") {
2058
- return this.assetService.getMatcapTexture(item.id);
2059
- } else {
2060
- return this.assetService.getSolidColorTexture(item.value);
2061
- }
2062
- }
2063
- }
2064
- export {
2065
- ParticlesEngine
2066
- };
1
+ import*as e from"three";import{Triangle as t,Vector3 as i,Vector2 as s,Mesh as n,OrthographicCamera as r,BufferGeometry as o,Float32BufferAttribute as a,NearestFilter as l,ShaderMaterial as u,WebGLRenderTarget as c,FloatType as h,RGBAFormat as d,DataTexture as m,ClampToEdgeWrapping as g}from"three";import{GLTFLoader as v,DRACOLoader as p}from"three-stdlib";const f=e=>e;class x{emitter=function(e){return{all:e=e||/* @__PURE__ */new Map,on:function(t,i){var s=e.get(t);s?s.push(i):e.set(t,[i])},off:function(t,i){var s=e.get(t);s&&(i?s.splice(s.indexOf(i)>>>0,1):e.set(t,[]))},emit:function(t,i){var s=e.get(t);s&&s.slice().map(function(e){e(i)}),(s=e.get("*"))&&s.slice().map(function(e){e(t,i)})}}}();emit(e,t){this.emitter.emit(e,t)}off(e,t){this.emitter.off(e,t)}on(e,t){this.emitter.on(e,t)}once(e,t){this.emitter.on(e,i=>{this.emitter.off(e,t),t(i)})}dispose(){this.emitter.all.clear()}}function y(t,i){const s=new e.DataTexture(t,i,i,e.RGBAFormat,e.FloatType);return s.needsUpdate=!0,s}function S(t){t.geometry.dispose(),t.material instanceof e.Material?t.material.dispose():t.material.forEach(e=>e.dispose())}function T(e,t,i){return e=Math.min(e,i),e=Math.max(e,t)}class P{serviceState="created";eventEmitter;meshes=/* @__PURE__ */new Map;textures=/* @__PURE__ */new Map;gltfLoader=new v;textureLoader=new e.TextureLoader;dracoLoader=new p;solidColorTextures=/* @__PURE__ */new Map;fallbackTexture=new e.DataTexture(new Uint8Array([127,127,127,255]),1,1,e.RGBAFormat);constructor(e){this.eventEmitter=e,this.dracoLoader.setDecoderPath("https://www.gstatic.com/draco/versioned/decoders/1.5.7/"),this.gltfLoader.setDRACOLoader(this.dracoLoader),this.fallbackTexture.name="default-fallback-texture",this.updateServiceState("ready")}register(t,i){if(i.name=t,i instanceof e.Mesh){const e=this.meshes.get(t);e&&S(e),this.meshes.set(t,i)}else{const e=this.textures.get(t);e&&e.dispose(),this.textures.set(t,i)}this.eventEmitter.emit("assetRegistered",{id:t})}getMesh(e){return this.meshes.get(e)??null}getMatcapTexture(e){const t=this.textures.get(e);return t||this.eventEmitter.emit("invalidRequest",{message:`texture with id "${e}" not found. using solid color texture instead...`}),t??this.fallbackTexture}getSolidColorTexture(t){const i=new e.Color(t).getHexString();let s=this.solidColorTextures.get(i);if(s)return s;try{const s=this.createSolidColorDataTexture(new e.Color(t));return this.solidColorTextures.set(i,s),s}catch(n){return console.error(`Invalid color value provided to getSolidColorTexture: ${t}`,n),this.eventEmitter.emit("invalidRequest",{message:`Invalid color value: ${t}. Using fallback texture.`}),this.fallbackTexture}}getFallbackTexture(){return this.fallbackTexture}getMeshIDs(){return Array.from(this.meshes.keys())}getTextureIDs(){return Array.from(this.textures.keys())}getMeshes(){return Array.from(this.meshes.values())}getTextures(){return Array.from(this.textures.values())}createSolidColorDataTexture(t,i=16){const s=new e.Color(t),n=i,r=i,o=new Uint8Array(n*r*4),a=Math.floor(255*s.r),l=Math.floor(255*s.g),u=Math.floor(255*s.b);for(let e=0;e<n*r;e++){const t=4*e;o[t]=a,o[t+1]=l,o[t+2]=u,o[t+3]=255}const c=new e.DataTexture(o,n,r,e.RGBAFormat);return c.type=e.UnsignedByteType,c.wrapS=e.RepeatWrapping,c.wrapT=e.RepeatWrapping,c.minFilter=e.NearestFilter,c.magFilter=e.NearestFilter,c.needsUpdate=!0,c}async loadMeshAsync(e,t,i={}){const s=await this.gltfLoader.loadAsync(t);try{if(i.meshName){const t=s.scene.getObjectByName(i.meshName);return this.register(e,t),t}{const t=s.scene.children[0];return this.register(e,t),t}}catch(n){return this.eventEmitter.emit("invalidRequest",{message:`failed to load mesh: ${e}. ${n}`}),null}}async loadTextureAsync(e,t){try{const i=await this.textureLoader.loadAsync(t);return this.register(e,i),i}catch(i){return this.eventEmitter.emit("invalidRequest",{message:`failed to load texture: ${e}. ${i}`}),null}}dispose(){this.updateServiceState("disposed"),this.meshes.forEach(e=>S(e)),this.meshes.clear(),this.textures.forEach(e=>e.dispose()),this.textures.clear(),this.solidColorTextures.forEach(e=>e.dispose()),this.solidColorTextures.clear(),this.fallbackTexture.dispose()}updateServiceState(e){this.serviceState=e,this.eventEmitter.emit("serviceStateUpdated",{type:"asset",state:e})}}const M=new t,b=new i,w=new s,A=new s,V=new s;class R{constructor(e){this.geometry=e.geometry,this.randomFunction=Math.random,this.indexAttribute=this.geometry.index,this.positionAttribute=this.geometry.getAttribute("position"),this.normalAttribute=this.geometry.getAttribute("normal"),this.colorAttribute=this.geometry.getAttribute("color"),this.uvAttribute=this.geometry.getAttribute("uv"),this.weightAttribute=null,this.distribution=null}setWeightAttribute(e){return this.weightAttribute=e?this.geometry.getAttribute(e):null,this}build(){const e=this.indexAttribute,t=this.positionAttribute,i=this.weightAttribute,s=e?e.count/3:t.count/3,n=new Float32Array(s);for(let a=0;a<s;a++){let s=1,r=3*a,o=3*a+1,l=3*a+2;e&&(r=e.getX(r),o=e.getX(o),l=e.getX(l)),i&&(s=i.getX(r)+i.getX(o)+i.getX(l)),M.a.fromBufferAttribute(t,r),M.b.fromBufferAttribute(t,o),M.c.fromBufferAttribute(t,l),s*=M.getArea(),n[a]=s}const r=new Float32Array(s);let o=0;for(let a=0;a<s;a++)o+=n[a],r[a]=o;return this.distribution=r,this}setRandomGenerator(e){return this.randomFunction=e,this}sample(e,t,i,s){const n=this._sampleFaceIndex();return this._sampleFace(n,e,t,i,s)}_sampleFaceIndex(){const e=this.distribution[this.distribution.length-1];return this._binarySearch(this.randomFunction()*e)}_binarySearch(e){const t=this.distribution;let i=0,s=t.length-1,n=-1;for(;i<=s;){const r=Math.ceil((i+s)/2);if(0===r||t[r-1]<=e&&t[r]>e){n=r;break}e<t[r]?s=r-1:i=r+1}return n}_sampleFace(e,t,i,s,n){let r=this.randomFunction(),o=this.randomFunction();r+o>1&&(r=1-r,o=1-o);const a=this.indexAttribute;let l=3*e,u=3*e+1,c=3*e+2;return a&&(l=a.getX(l),u=a.getX(u),c=a.getX(c)),M.a.fromBufferAttribute(this.positionAttribute,l),M.b.fromBufferAttribute(this.positionAttribute,u),M.c.fromBufferAttribute(this.positionAttribute,c),t.set(0,0,0).addScaledVector(M.a,r).addScaledVector(M.b,o).addScaledVector(M.c,1-(r+o)),void 0!==i&&(void 0!==this.normalAttribute?(M.a.fromBufferAttribute(this.normalAttribute,l),M.b.fromBufferAttribute(this.normalAttribute,u),M.c.fromBufferAttribute(this.normalAttribute,c),i.set(0,0,0).addScaledVector(M.a,r).addScaledVector(M.b,o).addScaledVector(M.c,1-(r+o)).normalize()):M.getNormal(i)),void 0!==s&&void 0!==this.colorAttribute&&(M.a.fromBufferAttribute(this.colorAttribute,l),M.b.fromBufferAttribute(this.colorAttribute,u),M.c.fromBufferAttribute(this.colorAttribute,c),b.set(0,0,0).addScaledVector(M.a,r).addScaledVector(M.b,o).addScaledVector(M.c,1-(r+o)),s.r=b.x,s.g=b.y,s.b=b.z),void 0!==n&&void 0!==this.uvAttribute&&(w.fromBufferAttribute(this.uvAttribute,l),A.fromBufferAttribute(this.uvAttribute,u),V.fromBufferAttribute(this.uvAttribute,c),n.set(0,0).addScaledVector(w,r).addScaledVector(A,o).addScaledVector(V,1-(r+o))),this}}class z{textureSize;dataTextures;eventEmitter;currentAtlas=null;constructor(e,t){this.eventEmitter=e,this.textureSize=t,this.dataTextures=/* @__PURE__ */new Map,this.updateServiceState("ready")}setTextureSize(e){this.textureSize!==e&&(this.textureSize=e,this.dataTextures.forEach(e=>e.dispose()),this.dataTextures.clear(),this.currentAtlas&&(this.currentAtlas.dispose(),this.currentAtlas=null))}async getDataTexture(t){const i=this.dataTextures.get(t.uuid);if(i)return i;var s;const n=function(t,i){const s=new e.BufferGeometry;s.setAttribute("position",new e.BufferAttribute(new Float32Array(t.position),3)),t.normal&&s.setAttribute("normal",new e.BufferAttribute(new Float32Array(t.normal),3));const n=new e.MeshBasicMaterial,r=new e.Mesh(s,n);r.scale.set(t.scale.x,t.scale.y,t.scale.z);const o=new R(r).build(),a=new Float32Array(i*i*4),l=new e.Vector3;for(let e=0;e<i;e++)for(let s=0;s<i;s++){const n=e*i+s;o.sample(l),a[4*n]=l.x*t.scale.x,a[4*n+1]=l.y*t.scale.y,a[4*n+2]=l.z*t.scale.z,a[4*n+3]=.01*(Math.random()-.5)}return s.dispose(),n.dispose(),a}({position:(s=t).geometry.attributes.position.array,normal:s.geometry.attributes.normal?.array,scale:{x:s.scale.x,y:s.scale.y,z:s.scale.z}},this.textureSize),r=y(n,this.textureSize);return r.name=t.name,this.dataTextures.set(t.uuid,r),r}dispose(){this.dataTextures.forEach(e=>e.dispose()),this.dataTextures.clear(),this.currentAtlas&&(this.currentAtlas.dispose(),this.currentAtlas=null),this.updateServiceState("disposed")}updateServiceState(e){this.eventEmitter.emit("serviceStateUpdated",{type:"data-texture",state:e})}async createSequenceDataTextureAtlas(t,i){this.updateServiceState("loading"),this.currentAtlas&&(this.currentAtlas.dispose(),this.currentAtlas=null);const s=t.length;if(0===s)throw new Error("Mesh array cannot be empty.");const n=i*s,r=i,o=new Float32Array(n*r*4);try{for(let e=0;e<s;e++){const s=t[e],r=(await this.getDataTexture(s)).image.data;for(let t=0;t<i;t++)for(let s=0;s<i;s++){const a=4*(t*i+s),l=4*(t*n+(s+e*i));o[l]=r[a],o[l+1]=r[a+1],o[l+2]=r[a+2],o[l+3]=r[a+3]}}const a=new e.DataTexture(o,n,r,e.RGBAFormat,e.FloatType);return a.needsUpdate=!0,a.name=`atlas-${t.map(e=>e.name).join("-")}`,this.currentAtlas=a,this.updateServiceState("ready"),a}catch(a){throw this.updateServiceState("error"),a}}}class D{size;mesh;shaderMaterial;fallbackGeometry;uniforms;geometries;uvRefsCache;previousScale;constructor(t){this.size=t,this.geometries=/* @__PURE__ */new Map,this.uvRefsCache=/* @__PURE__ */new Map,this.previousScale={x:1,y:1,z:1},this.uniforms={uTime:{value:0},uProgress:{value:0},uTexture:{value:null},uVelocity:{value:null},uOriginTexture:{value:null},uDestinationTexture:{value:null}},this.shaderMaterial=new e.ShaderMaterial({uniforms:this.uniforms,vertexShader:"\nvarying vec2 vUv;\nuniform sampler2D uTexture;\nuniform sampler2D uVelocity;\nuniform float uTime;\nvarying vec3 vNormal;\nattribute vec2 uvRef;\nvarying vec3 vViewPosition;\n\nvec3 rotate3D(vec3 v, vec3 vel) {\n vec3 pos = v;\n vec3 up = vec3(0, 1, 0);\n vec3 axis = normalize(cross(up, vel));\n float angle = acos(dot(up, normalize(vel)));\n pos = pos * cos(angle) + cross(axis, pos) * sin(angle) + axis * dot(axis, pos) * (1. - cos(angle));\n return pos;\n}\n\nvoid main() {\n vUv = uv;\n vNormal = normal;\n\n vec4 color = texture2D(uTexture, uvRef);\n vec4 velocity = texture2D(uVelocity, uvRef);\n vec3 pos = color.xyz;// apply the texture to the vertex distribution.\n\n vec3 localPosition = position.xyz;\n if (length (velocity.xyz) < 0.0001) {\n velocity.xyz = vec3(0.0, 0.0001, 0.0001);\n }\n localPosition.y *= max(1.0, length(velocity.xyz) * 1000.0);\n localPosition = rotate3D(localPosition, velocity.xyz);\n vNormal = rotate3D(normal, velocity.xyz);\n\n mat4 instanceMat = instanceMatrix;\n instanceMat[3].xyz = pos.xyz;\n\n // unlike the traditional mvMatrix * position, we need to additional multiplication with the instance matrix.\n vec4 modelViewPosition = modelViewMatrix * instanceMat * vec4(localPosition, 1.0);\n\n vViewPosition = - modelViewPosition.xyz;\n\n gl_Position = projectionMatrix * modelViewPosition;\n}\n",fragmentShader:"\nvarying vec2 vUv;\n\nuniform sampler2D uOriginTexture;\nuniform sampler2D uDestinationTexture;\n\nuniform float uProgress;\nvarying vec3 vNormal;\nvarying vec3 vViewPosition;\nvoid main() {\n vec3 viewDir = normalize( vViewPosition );\n vec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n vec3 y = cross( viewDir, x );\n vec2 uv = vec2( dot( x, vNormal ), dot( y, vNormal ) ) * 0.495 + 0.5; // 0.495 to remove artifacts caused by undersized matcap disks\n\n vec4 textureA = texture2D( uOriginTexture, uv );\n vec4 textureB = texture2D( uDestinationTexture, uv );\n\n vec4 finalColor = mix(textureA, textureB, uProgress);\n gl_FragColor = finalColor;\n}\n"}),this.fallbackGeometry=new e.BoxGeometry(.001,.001,.001),this.mesh=this.createInstancedMesh(t,this.fallbackGeometry,this.shaderMaterial),this.mesh.material=this.shaderMaterial}getMesh(){return this.mesh}update(t){const i=this.mesh.material;(i instanceof e.ShaderMaterial||i instanceof e.RawShaderMaterial)&&(i.uniforms.uTime.value=t)}updateTextureInterpolation(e,t,i){this.uniforms.uOriginTexture.value=e,this.uniforms.uDestinationTexture.value=t,this.uniforms.uProgress.value=i}setGeometrySize(e){this.mesh.geometry.scale(1/this.previousScale.x,1/this.previousScale.y,1/this.previousScale.z),this.mesh.geometry.scale(e.x,e.y,e.z),this.previousScale=e}useMatcapMaterial(){this.mesh.material=this.shaderMaterial}useGeometry(e){const t=this.geometries.get(e);t&&(this.mesh.geometry=t)}updateVelocityTexture(e){this.shaderMaterial.uniforms.uVelocity.value=e}updatePositionTexture(e){this.shaderMaterial.uniforms.uTexture.value=e}resize(e){if(this.size===e)return this.mesh;this.size=e;const t=this.mesh;return this.mesh=this.createInstancedMesh(e,t.geometry,t.material),t.dispose(),this.mesh}dispose(){this.mesh.dispose(),this.geometries.forEach(e=>e.dispose()),this.shaderMaterial.dispose(),this.fallbackGeometry.dispose(),this.uvRefsCache.clear(),this.geometries.clear()}registerGeometry(e,t){const i=this.geometries.get(e);if(i&&i===t)return;const s=this.createUVRefs(this.size);t.setAttribute("uvRef",s),this.geometries.set(e,t),this.mesh.geometry===i&&(this.mesh.geometry=t),i?.dispose()}createUVRefs(t){const i=this.uvRefsCache.get(t);if(i)return i;const s=new Float32Array(t*t*2);for(let e=0;e<t;e++)for(let i=0;i<t;i++){const n=e*t+i;s[2*n]=i/(t-1),s[2*n+1]=e/(t-1)}const n=new e.InstancedBufferAttribute(s,2);return this.uvRefsCache.set(t,n),n}createInstancedMesh(t,i,s){(i=i||this.fallbackGeometry).setAttribute("uvRef",this.createUVRefs(t));const n=t*t;return new e.InstancedMesh(i,s,n)}}class C{active=!0;raycaster=new e.Raycaster;mousePosition=new e.Vector2;camera;meshSequenceGeometries=[];meshSequenceUUIDs=[];overallProgress=0;intersectionMesh=new e.Mesh;geometryNeedsUpdate;eventEmitter;blendedGeometry;intersection;constructor(e,t){this.camera=t,this.eventEmitter=e,this.geometryNeedsUpdate=!0}setActive(e){this.active=e,e||(this.intersection=void 0,this.eventEmitter.emit("interactionPositionUpdated",{position:{x:0,y:0,z:0,w:0}}))}getIntersectionMesh(){return this.intersectionMesh}setCamera(e){this.camera=e}setMeshSequence(e){this.meshSequenceGeometries.forEach(e=>e.dispose()),this.meshSequenceGeometries=[],this.meshSequenceUUIDs=[],e&&0!==e.length?(e.forEach(e=>{if(e&&e.geometry){const t=e.geometry.clone();t.applyMatrix4(e.matrixWorld),this.meshSequenceGeometries.push(t),this.meshSequenceUUIDs.push(e.uuid)}else console.warn("Invalid mesh provided to IntersectionService sequence.")}),this.geometryNeedsUpdate=!0):this.geometryNeedsUpdate=!0}setOverallProgress(t){const i=e.MathUtils.clamp(t,0,1);this.overallProgress!==i&&(this.overallProgress=i,this.geometryNeedsUpdate=!0)}setPointerPosition(e){e&&this.mousePosition.copy(e)}calculate(t){if(!this.active||!this.camera||0===this.meshSequenceGeometries.length)return void(this.intersection&&(this.intersection=void 0,this.eventEmitter.emit("interactionPositionUpdated",{position:{x:0,y:0,z:0,w:0}})));let i;this.geometryNeedsUpdate&&(this.blendedGeometry&&this.blendedGeometry!==this.intersectionMesh.geometry&&this.blendedGeometry.dispose(),this.blendedGeometry=this.getBlendedGeometry(),this.geometryNeedsUpdate=!1,this.blendedGeometry?this.intersectionMesh.geometry!==this.blendedGeometry&&(this.intersectionMesh.geometry&&this.intersectionMesh.geometry.dispose(),this.intersectionMesh.geometry=this.blendedGeometry):(this.intersectionMesh.geometry&&this.intersectionMesh.geometry.dispose(),this.intersectionMesh.geometry=new e.BufferGeometry)),this.intersectionMesh.matrixWorld.copy(t.matrixWorld),this.blendedGeometry&&this.blendedGeometry.attributes.position&&(i=this.getFirstIntersection(this.camera,this.intersectionMesh));if(this.intersection?.x!==i?.x||this.intersection?.y!==i?.y||this.intersection?.z!==i?.z||this.intersection&&!i||!this.intersection&&i)if(this.intersection=i,this.intersection){const i=new e.Vector3(this.intersection.x,this.intersection.y,this.intersection.z),s=t.worldToLocal(i.clone());this.intersection.set(s.x,s.y,s.z,1),this.eventEmitter.emit("interactionPositionUpdated",{position:this.intersection})}else this.eventEmitter.emit("interactionPositionUpdated",{position:{x:0,y:0,z:0,w:0}});return this.intersection}dispose(){this.meshSequenceGeometries.forEach(e=>e.dispose()),this.meshSequenceGeometries=[],this.meshSequenceUUIDs=[],this.blendedGeometry&&this.blendedGeometry!==this.intersectionMesh.geometry&&this.blendedGeometry.dispose(),this.intersectionMesh.geometry?.dispose()}updateIntersectionMesh(e){this.blendedGeometry&&this.blendedGeometry.uuid!==this.intersectionMesh.geometry.uuid&&(this.intersectionMesh.geometry.dispose(),this.intersectionMesh.geometry=this.blendedGeometry),this.intersectionMesh.matrix.copy(e.matrixWorld),this.intersectionMesh.matrixWorld.copy(e.matrixWorld),this.intersectionMesh.matrixAutoUpdate=!1,this.intersectionMesh.updateMatrixWorld(!0)}getFirstIntersection(t,i){this.raycaster.setFromCamera(this.mousePosition,t);const s=this.raycaster.intersectObject(i,!1);if(s.length>0&&s[0].point){const t=s[0].point;return new e.Vector4(t.x,t.y,t.z,1)}}getBlendedGeometry(){const t=this.meshSequenceGeometries.length;if(0===t)return;if(1===t)return this.meshSequenceGeometries[0];const i=t-1,s=1/i,n=this.overallProgress*i;let r=Math.floor(n),o=r+1;r=e.MathUtils.clamp(r,0,i),o=e.MathUtils.clamp(o,0,i);let a=0;s>0&&(a=n-r),this.overallProgress>=1&&(r=i,o=i,a=1),a=e.MathUtils.clamp(a,0,1);const l=this.meshSequenceGeometries[r],u=this.meshSequenceGeometries[o];return l&&u?r===o?l:this.blendGeometry(l,u,a):(console.error("IntersectionService: Invalid geometries found for blending at indices",r,o),this.meshSequenceGeometries[0])}blendGeometry(t,i,s){const n=new e.BufferGeometry,r=t.attributes.position.array,o=i.attributes.position.array,a=new Float32Array(r.length);for(let l=0;l<r.length;l+=3){const t=new e.Vector3(r[l],r[l+1],r[l+2]),i=new e.Vector3(o[l],o[l+1],o[l+2]),n=(new e.Vector3).lerpVectors(t,i,s);a[l]=n.x,a[l+1]=n.y,a[l+2]=n.z}return n.setAttribute("position",new e.BufferAttribute(a,3)),t.attributes.normal&&n.setAttribute("normal",t.attributes.normal.clone()),t.attributes.uv&&n.setAttribute("uv",t.attributes.uv.clone()),t.index&&n.setIndex(t.index.clone()),n}}const F=new r(-1,1,1,-1,0,1);const I=new class extends o{constructor(){super(),this.setAttribute("position",new a([-1,3,0,-1,-1,0,3,-1,0],3)),this.setAttribute("uv",new a([0,2,0,0,2,0],2))}};class E{constructor(e){this._mesh=new n(I,e)}dispose(){this._mesh.geometry.dispose()}render(e){e.render(this._mesh,F)}get material(){return this._mesh.material}set material(e){this._mesh.material=e}}class U{constructor(e,t,i){this.variables=[],this.currentTextureIndex=0;let s=h;const n={passThruTexture:{value:null}},r=v("uniform sampler2D passThruTexture;\n\nvoid main() {\n\n\tvec2 uv = gl_FragCoord.xy / resolution.xy;\n\n\tgl_FragColor = texture2D( passThruTexture, uv );\n\n}\n",n),o=new E(r);function a(i){i.defines.resolution="vec2( "+e.toFixed(1)+", "+t.toFixed(1)+" )"}function v(e,t){const i=new u({name:"GPUComputationShader",uniforms:t=t||{},vertexShader:"void main()\t{\n\n\tgl_Position = vec4( position, 1.0 );\n\n}\n",fragmentShader:e});return a(i),i}this.setDataType=function(e){return s=e,this},this.addVariable=function(e,t,i){const s={name:e,initialValueTexture:i,material:this.createShaderMaterial(t),dependencies:null,renderTargets:[],wrapS:null,wrapT:null,minFilter:l,magFilter:l};return this.variables.push(s),s},this.setVariableDependencies=function(e,t){e.dependencies=t},this.init=function(){if(0===i.capabilities.maxVertexTextures)return"No support for vertex shader textures.";for(let i=0;i<this.variables.length;i++){const s=this.variables[i];s.renderTargets[0]=this.createRenderTarget(e,t,s.wrapS,s.wrapT,s.minFilter,s.magFilter),s.renderTargets[1]=this.createRenderTarget(e,t,s.wrapS,s.wrapT,s.minFilter,s.magFilter),this.renderTexture(s.initialValueTexture,s.renderTargets[0]),this.renderTexture(s.initialValueTexture,s.renderTargets[1]);const n=s.material,r=n.uniforms;if(null!==s.dependencies)for(let e=0;e<s.dependencies.length;e++){const t=s.dependencies[e];if(t.name!==s.name){let e=!1;for(let i=0;i<this.variables.length;i++)if(t.name===this.variables[i].name){e=!0;break}if(!e)return"Variable dependency not found. Variable="+s.name+", dependency="+t.name}r[t.name]={value:null},n.fragmentShader="\nuniform sampler2D "+t.name+";\n"+n.fragmentShader}}return this.currentTextureIndex=0,null},this.compute=function(){const e=this.currentTextureIndex,t=0===this.currentTextureIndex?1:0;for(let i=0,s=this.variables.length;i<s;i++){const s=this.variables[i];if(null!==s.dependencies){const t=s.material.uniforms;for(let i=0,n=s.dependencies.length;i<n;i++){const n=s.dependencies[i];t[n.name].value=n.renderTargets[e].texture}}this.doRenderTarget(s.material,s.renderTargets[t])}this.currentTextureIndex=t},this.getCurrentRenderTarget=function(e){return e.renderTargets[this.currentTextureIndex]},this.getAlternateRenderTarget=function(e){return e.renderTargets[0===this.currentTextureIndex?1:0]},this.dispose=function(){o.dispose();const e=this.variables;for(let t=0;t<e.length;t++){const i=e[t];i.initialValueTexture&&i.initialValueTexture.dispose();const s=i.renderTargets;for(let e=0;e<s.length;e++){s[e].dispose()}}},this.addResolutionDefine=a,this.createShaderMaterial=v,this.createRenderTarget=function(i,n,r,o,a,u){return new c(i=i||e,n=n||t,{wrapS:r=r||g,wrapT:o=o||g,minFilter:a=a||l,magFilter:u=u||l,format:d,type:s,depthBuffer:!1})},this.createTexture=function(){const i=new Float32Array(e*t*4),s=new m(i,e,t,d,h);return s.needsUpdate=!0,s},this.renderTexture=function(e,t){n.passThruTexture.value=e,this.doRenderTarget(r,t),n.passThruTexture.value=null},this.doRenderTarget=function(e,t){const s=i.getRenderTarget(),n=i.xr.enabled,a=i.shadowMap.autoUpdate;i.xr.enabled=!1,i.shadowMap.autoUpdate=!1,o.material=e,i.setRenderTarget(t),o.render(i),o.material=r,i.xr.enabled=n,i.shadowMap.autoUpdate=a,i.setRenderTarget(s)}}}class G{gpuComputationRenderer;webGLRenderer;velocityVar;positionVar;initialPositionDataTexture;initialVelocityDataTexture;positionAtlasTexture=null;interactionPosition;lastKnownPositionDataTexture;lastKnownVelocityDataTexture;constructor(t,i,s){this.webGLRenderer=i,this.gpuComputationRenderer=new U(t,t,this.webGLRenderer),!i.capabilities.isWebGL2&&i.extensions.get("OES_texture_float")?this.gpuComputationRenderer.setDataType(e.FloatType):i.capabilities.isWebGL2||this.gpuComputationRenderer.setDataType(e.HalfFloatType),this.initialPositionDataTexture=s??function(e){const t=new Float32Array(e*e*4);for(let i=0;i<e;i++)for(let s=0;s<e;s++){const n=i*e+s;let r=Math.random()*Math.PI*2,o=Math.acos(2*Math.random()-1),a=Math.sin(o)*Math.cos(r),l=Math.sin(o)*Math.sin(r),u=Math.cos(o);t[4*n]=a,t[4*n+1]=l,t[4*n+2]=u,t[4*n+3]=.01*(Math.random()-.5)}return y(t,e)}(t),this.initialVelocityDataTexture=function(e){return y(new Float32Array(4*e*e),e)}(t),this.interactionPosition=new e.Vector4(0,0,0,0),this.velocityVar=this.gpuComputationRenderer.addVariable("uCurrentVelocity",'\nuniform vec4 uInteractionPosition;\nuniform float uTime;\nuniform float uTractionForce;\nuniform float uMaxRepelDistance;\nuniform sampler2D uPositionAtlas;\nuniform float uOverallProgress;\nuniform int uNumMeshes;\nuniform float uSingleTextureSize;\n\nfloat rand(vec2 co) {\n return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);\n}\n\n// Helper function (same as in position shader)\nvec3 getAtlasPosition(vec2 uv, int meshIndex) {\n float atlasWidth = uSingleTextureSize * float(uNumMeshes);\n float atlasHeight = uSingleTextureSize;\n float segmentWidthRatio = uSingleTextureSize / atlasWidth;\n vec2 atlasUV = vec2(uv.x * segmentWidthRatio + segmentWidthRatio * float(meshIndex), uv.y);\n return texture2D(uPositionAtlas, atlasUV).xyz;\n}\n\nvoid main() {\n vec2 uv = gl_FragCoord.xy / resolution.xy;\n float offset = rand(uv);\n\n vec3 currentPosition = texture2D(uCurrentPosition, uv).xyz;\n vec3 currentVelocity = texture2D(uCurrentVelocity, uv).xyz;\n\n // --- Calculate Target Position from Atlas (same logic as position shader) ---\n vec3 targetPosition;\n if (uNumMeshes <= 1) {\n targetPosition = getAtlasPosition(uv, 0);\n } else {\n float totalSegments = float(uNumMeshes - 1);\n float progressPerSegment = 1.0 / totalSegments;\n float scaledProgress = uOverallProgress * totalSegments;\n int indexA = int(floor(scaledProgress));\n int indexB = min(indexA + 1, uNumMeshes - 1);\n indexA = min(indexA, uNumMeshes - 1);\n float localProgress = fract(scaledProgress);\n if (uOverallProgress == 1.0) {\n indexA = uNumMeshes - 1;\n indexB = uNumMeshes - 1;\n localProgress = 1.0;\n }\n vec3 positionA = getAtlasPosition(uv, indexA);\n vec3 positionB = getAtlasPosition(uv, indexB);\n targetPosition = mix(positionA, positionB, localProgress);\n }\n // --- End Target Position Calculation ---\n\n vec3 finalVelocity = currentVelocity * 0.9; // Dampening\n\n // Particle traction force towards target (influences velocity)\n vec3 direction = normalize(targetPosition - currentPosition);\n float dist = length(targetPosition - currentPosition);\n if (dist > 0.01) {\n // Add force proportional to distance and traction setting\n finalVelocity += direction * dist * 0.01 * uTractionForce; // Adjust multiplier as needed\n }\n\n // Mouse repel force\n if (uInteractionPosition.w > 0.0) { // Check if interaction is active (w component)\n float pointerDistance = distance(currentPosition, uInteractionPosition.xyz);\n if (pointerDistance < uMaxRepelDistance) {\n float mouseRepelModifier = smoothstep(uMaxRepelDistance, 0.0, pointerDistance); // Smoother falloff\n vec3 repelDirection = normalize(currentPosition - uInteractionPosition.xyz);\n // Apply force based on proximity and interaction strength (w)\n finalVelocity += repelDirection * mouseRepelModifier * uInteractionPosition.w * 0.01; // Adjust multiplier\n }\n }\n\n // Optional: Reset position if particle "dies" and respawns (lifespan logic)\n float lifespan = 20.0;\n float age = mod(uTime * 0.1 + lifespan * offset, lifespan); // Adjust time scale\n if (age < 0.05) { // Small window for reset\n finalVelocity = vec3(0.0); // Reset velocity on respawn\n // Note: Resetting position directly here might cause jumps.\n // It\'s often better handled in the position shader or by ensuring\n // strong attraction force when dist is large.\n }\n\n\n gl_FragColor = vec4(finalVelocity, 1.0);\n}\n',this.initialVelocityDataTexture),this.positionVar=this.gpuComputationRenderer.addVariable("uCurrentPosition","\nuniform vec4 uInteractionPosition;\nuniform float uTime;\nuniform float uTractionForce;\nuniform sampler2D uPositionAtlas;\nuniform float uOverallProgress; // (0.0 to 1.0)\nuniform int uNumMeshes;\nuniform float uSingleTextureSize;\n\nfloat rand(vec2 co) {\n return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);\n}\n\n// Helper function to get position from atlas\nvec3 getAtlasPosition(vec2 uv, int meshIndex) {\n float atlasWidth = uSingleTextureSize * float(uNumMeshes);\n float atlasHeight = uSingleTextureSize; // Assuming height is single texture size\n\n // Calculate UV within the specific mesh's section of the atlas\n float segmentWidthRatio = uSingleTextureSize / atlasWidth;\n vec2 atlasUV = vec2(\n uv.x * segmentWidthRatio + segmentWidthRatio * float(meshIndex),\n uv.y // Assuming vertical layout doesn't change y\n );\n\n return texture2D(uPositionAtlas, atlasUV).xyz;\n}\n\nvoid main() {\n // GPGPU UV calculation\n vec2 uv = gl_FragCoord.xy / resolution.xy; // resolution is the size of the *output* texture (e.g., 256x256)\n\n vec3 currentPosition = texture2D(uCurrentPosition, uv).xyz;\n vec3 currentVelocity = texture2D(uCurrentVelocity, uv).xyz;\n\n // --- Calculate Target Position from Atlas ---\n vec3 targetPosition;\n if (uNumMeshes <= 1) {\n targetPosition = getAtlasPosition(uv, 0);\n } else {\n float totalSegments = float(uNumMeshes - 1);\n float progressPerSegment = 1.0 / totalSegments;\n float scaledProgress = uOverallProgress * totalSegments;\n\n int indexA = int(floor(scaledProgress));\n // Clamp indexB to avoid going out of bounds\n int indexB = min(indexA + 1, uNumMeshes - 1);\n\n // Ensure indexA is also within bounds (important if uOverallProgress is exactly 1.0)\n indexA = min(indexA, uNumMeshes - 1);\n\n\n float localProgress = fract(scaledProgress);\n\n // Handle edge case where progress is exactly 1.0\n if (uOverallProgress == 1.0) {\n indexA = uNumMeshes - 1;\n indexB = uNumMeshes - 1;\n localProgress = 1.0; // or 0.0 depending on how you want to handle it\n }\n\n\n vec3 positionA = getAtlasPosition(uv, indexA);\n vec3 positionB = getAtlasPosition(uv, indexB);\n\n targetPosition = mix(positionA, positionB, localProgress);\n }\n // --- End Target Position Calculation ---\n\n // Particle attraction to target position\n vec3 direction = normalize(targetPosition - currentPosition);\n float dist = length(targetPosition - currentPosition);\n\n vec3 finalPosition = currentPosition;\n\n // Apply attraction force (simplified mix)\n if (dist > 0.01) { // Only apply if significantly far\n finalPosition = mix(currentPosition, targetPosition, 0.1 * uTractionForce);\n }\n\n finalPosition += currentVelocity;\n gl_FragColor = vec4(finalPosition, 1.0);\n}\n",this.initialPositionDataTexture),this.velocityVar.material.uniforms.uTime={value:0},this.velocityVar.material.uniforms.uInteractionPosition={value:this.interactionPosition},this.velocityVar.material.uniforms.uCurrentPosition={value:null},this.velocityVar.material.uniforms.uTractionForce={value:.1},this.velocityVar.material.uniforms.uMaxRepelDistance={value:.3},this.velocityVar.material.uniforms.uPositionAtlas={value:null},this.velocityVar.material.uniforms.uOverallProgress={value:0},this.velocityVar.material.uniforms.uNumMeshes={value:1},this.velocityVar.material.uniforms.uSingleTextureSize={value:t},this.positionVar.material.uniforms.uTime={value:0},this.positionVar.material.uniforms.uTractionForce={value:.1},this.positionVar.material.uniforms.uInteractionPosition={value:this.interactionPosition},this.positionVar.material.uniforms.uCurrentPosition={value:null},this.positionVar.material.uniforms.uCurrentVelocity={value:null},this.positionVar.material.uniforms.uPositionAtlas={value:null},this.positionVar.material.uniforms.uOverallProgress={value:0},this.positionVar.material.uniforms.uNumMeshes={value:1},this.positionVar.material.uniforms.uSingleTextureSize={value:t},this.gpuComputationRenderer.setVariableDependencies(this.positionVar,[this.positionVar,this.velocityVar]),this.gpuComputationRenderer.setVariableDependencies(this.velocityVar,[this.velocityVar,this.positionVar]);const n=this.gpuComputationRenderer.init();if(null!==n)throw new Error("Failed to initialize SimulationRenderer: "+n);this.positionVar.material.uniforms.uPositionAtlas.value=this.initialPositionDataTexture,this.positionVar.material.uniforms.uNumMeshes.value=1,this.velocityVar.material.uniforms.uNumMeshes.value=1,this.positionVar.material.uniforms.uSingleTextureSize.value=t,this.velocityVar.material.uniforms.uSingleTextureSize.value=t,this.positionVar.material.uniforms.uCurrentVelocity.value=this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture,this.velocityVar.material.uniforms.uCurrentPosition.value=this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture,this.lastKnownVelocityDataTexture=this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture,this.lastKnownPositionDataTexture=this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture}setPositionAtlas(e){const t=e.singleTextureSize*e.numMeshes;e.dataTexture.image.width===t&&e.dataTexture.image.height===e.singleTextureSize||console.error(`SimulationRenderer: Atlas texture dimension mismatch! Expected ${t}x${e.singleTextureSize}, Got ${e.dataTexture.image.width}x${e.dataTexture.image.height}`),this.positionAtlasTexture=e.dataTexture;const i=e.numMeshes>0?e.numMeshes:1;this.positionVar.material.uniforms.uPositionAtlas.value=this.positionAtlasTexture,this.positionVar.material.uniforms.uNumMeshes.value=i,this.positionVar.material.uniforms.uSingleTextureSize.value=e.singleTextureSize,this.velocityVar.material.uniforms.uPositionAtlas.value=this.positionAtlasTexture,this.velocityVar.material.uniforms.uNumMeshes.value=i,this.velocityVar.material.uniforms.uSingleTextureSize.value=e.singleTextureSize,this.positionVar.material.uniforms.uCurrentVelocity.value=this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture,this.velocityVar.material.uniforms.uCurrentPosition.value=this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture}setOverallProgress(e){const t=T(e,0,1);this.positionVar.material.uniforms.uOverallProgress.value=t,this.velocityVar.material.uniforms.uOverallProgress.value=t}setMaxRepelDistance(e){this.velocityVar.material.uniforms.uMaxRepelDistance.value=e}setVelocityTractionForce(e){this.velocityVar.material.uniforms.uTractionForce.value=e}setPositionalTractionForce(e){this.positionVar.material.uniforms.uTractionForce.value=e}setInteractionPosition(e){this.interactionPosition.copy(e)}dispose(){this.gpuComputationRenderer.dispose(),this.initialPositionDataTexture?.dispose(),this.initialVelocityDataTexture?.dispose(),this.positionAtlasTexture=null}compute(e){this.velocityVar.material.uniforms.uTime.value+=e,this.positionVar.material.uniforms.uTime.value+=e,this.positionVar.material.uniforms.uCurrentVelocity.value=this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture,this.velocityVar.material.uniforms.uCurrentPosition.value=this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture,this.gpuComputationRenderer.compute(),this.lastKnownVelocityDataTexture=this.gpuComputationRenderer.getCurrentRenderTarget(this.velocityVar).texture,this.lastKnownPositionDataTexture=this.gpuComputationRenderer.getCurrentRenderTarget(this.positionVar).texture}getVelocityTexture(){return this.lastKnownVelocityDataTexture}getPositionTexture(){return this.lastKnownPositionDataTexture}}class q{state;textureSize;overallProgress;velocityTractionForce;positionalTractionForce;simulationRenderer;webGLRenderer;eventEmitter;currentAtlasEntry=null;lastKnownVelocityDataTexture;lastKnownPositionDataTexture;constructor(e,t,i){this.eventEmitter=e,this.webGLRenderer=i,this.textureSize=t,this.overallProgress=0,this.velocityTractionForce=.1,this.positionalTractionForce=.1,this.updateServiceState("initializing"),this.simulationRenderer=new G(this.textureSize,this.webGLRenderer),this.lastKnownVelocityDataTexture=this.simulationRenderer.getVelocityTexture(),this.lastKnownPositionDataTexture=this.simulationRenderer.getPositionTexture(),this.updateServiceState("ready")}setPositionAtlas(e){const t=e.singleTextureSize*e.numMeshes;e.dataTexture.image.width===t?(this.currentAtlasEntry=e,this.simulationRenderer.setPositionAtlas(e)):this.eventEmitter.emit("invalidRequest",{message:"Atlas texture width mismatch."})}setOverallProgress(e){this.overallProgress=e,this.simulationRenderer.setOverallProgress(this.overallProgress)}setTextureSize(e){this.updateServiceState("initializing"),this.simulationRenderer.dispose(),this.textureSize=e,this.simulationRenderer=new G(e,this.webGLRenderer),this.updateServiceState("ready")}setVelocityTractionForce(e){this.velocityTractionForce=e,this.simulationRenderer.setVelocityTractionForce(this.velocityTractionForce)}setPositionalTractionForce(e){this.positionalTractionForce=e,this.simulationRenderer.setPositionalTractionForce(this.positionalTractionForce)}compute(e){"ready"===this.state&&(this.simulationRenderer.compute(e),this.lastKnownVelocityDataTexture=this.simulationRenderer.getVelocityTexture(),this.lastKnownPositionDataTexture=this.simulationRenderer.getPositionTexture())}getVelocityTexture(){return this.lastKnownVelocityDataTexture}getPositionTexture(){return this.lastKnownPositionDataTexture}dispose(){this.updateServiceState("disposed"),this.simulationRenderer.dispose(),this.currentAtlasEntry=null}updateServiceState(e){this.state=e,this.eventEmitter.emit("serviceStateUpdated",{type:"simulation",state:e})}setInteractionPosition(e){this.simulationRenderer.setInteractionPosition(e)}setMaxRepelDistance(e){this.simulationRenderer.setMaxRepelDistance(e)}}class B{execStatus=/* @__PURE__ */new Map;get(e){const t=this.execStatus.get(e);return t||(this.execStatus.set(e,"idle"),"idle")}set(e,t){this.execStatus.set(e,t)}}class N{eventEmitter;transitions=/* @__PURE__ */new Map;execStatus;ongoingTransitions=/* @__PURE__ */new Map;boundHandleTransitionCancelled;constructor(e){this.eventEmitter=e,this.execStatus=new B,this.boundHandleTransitionCancelled=this.handleTransitionCancelledEvent.bind(this),this.eventEmitter.on("transitionCancelled",this.boundHandleTransitionCancelled)}dispose(){this.eventEmitter.off("transitionCancelled",this.boundHandleTransitionCancelled),this.ongoingTransitions.forEach(e=>{e.cancelled=!0}),this.ongoingTransitions.clear(),this.transitions.clear()}enqueue(e,t,i={}){const s={...t,...i,cancelled:!1,duration:.001*t.duration};this.getQueue(e).push(s)}compute(e){this.transitions.forEach((t,i)=>{if(t.length&&!this.ongoingTransitions.has(i)){const s=t.shift();s&&(this.ongoingTransitions.set(i,{...s,startTime:e}),s.onTransitionBegin?.())}}),this.ongoingTransitions.forEach((t,i)=>{if(t.cancelled)return t.onTransitionCancelled?.(),void this.ongoingTransitions.delete(i);const{startTime:s,duration:n,easing:r}=t,o=e-s,a=T(r(Math.min(1,o/n)),0,1);this.emitTransitionProgress(i,a),t.onTransitionProgress?.(a),a>=1&&(this.emitTransitionFinished(i),t.onTransitionFinished?.(),this.ongoingTransitions.delete(i))})}getQueue(e){const t=this.transitions.get(e);return t||(this.transitions.set(e,[]),this.transitions.get(e)??[])}handleTransitionCancelledEvent({type:e}){const t=this.getQueue(e);for(;t.length;)t.pop();const i=this.ongoingTransitions.get(e);i&&(i.cancelled=!0,i.onTransitionCancelled?.())}emitTransitionProgress(e,t){this.eventEmitter.emit("transitionProgressed",{type:e,progress:t})}emitTransitionFinished(e){this.eventEmitter.emit("transitionFinished",{type:e})}}class O{simulationRendererService;renderer;scene;serviceStates;assetService;dataTextureManager;instancedMeshManager;transitionService;engineState;intersectionService;meshSequenceAtlasTexture=null;eventEmitter;boundHandleServiceStateUpdated;boundHandleInteractionPositionUpdated;constructor(e){const{scene:t,renderer:i,camera:s,textureSize:n,useIntersection:r=!0}=e;this.eventEmitter=new x,this.serviceStates=this.getInitialServiceStates(),this.boundHandleServiceStateUpdated=this.handleServiceStateUpdated.bind(this),this.boundHandleInteractionPositionUpdated=this.handleInteractionPositionUpdated.bind(this),this.eventEmitter.on("serviceStateUpdated",this.boundHandleServiceStateUpdated),this.scene=t,this.renderer=i,this.engineState=this.initialEngineState(e),this.assetService=new P(this.eventEmitter),this.transitionService=new N(this.eventEmitter),this.dataTextureManager=new z(this.eventEmitter,n),this.simulationRendererService=new q(this.eventEmitter,n,this.renderer),this.instancedMeshManager=new D(n),this.scene.add(this.instancedMeshManager.getMesh()),this.intersectionService=new C(this.eventEmitter,s),r||this.intersectionService.setActive(!1),this.setOverallProgress(0,!1),this.eventEmitter.on("interactionPositionUpdated",this.boundHandleInteractionPositionUpdated)}render(e){const t=e/1e3;this.transitionService.compute(t),this.intersectionService.calculate(this.instancedMeshManager.getMesh()),this.simulationRendererService.compute(t),this.instancedMeshManager.update(t),this.instancedMeshManager.updateVelocityTexture(this.simulationRendererService.getVelocityTexture()),this.instancedMeshManager.updatePositionTexture(this.simulationRendererService.getPositionTexture())}setTextureSequence(e){this.engineState.textureSequence=e,this.eventEmitter.emit("textureSequenceUpdated",{sequence:e}),this.setOverallProgress(0,!1)}async setTextureSize(e){this.engineState.textureSize!==e&&(this.engineState.textureSize=e,this.dataTextureManager.setTextureSize(e),this.simulationRendererService.setTextureSize(e),this.scene.remove(this.instancedMeshManager.getMesh()),this.instancedMeshManager.resize(e),this.scene.add(this.instancedMeshManager.getMesh()),this.engineState.meshSequence.length>0&&await this.setMeshSequence(this.engineState.meshSequence),this.simulationRendererService.setVelocityTractionForce(this.engineState.velocityTractionForce),this.simulationRendererService.setPositionalTractionForce(this.engineState.positionalTractionForce),this.simulationRendererService.setMaxRepelDistance(this.engineState.maxRepelDistance),this.simulationRendererService.setOverallProgress(this.engineState.overallProgress),this.intersectionService.setOverallProgress(this.engineState.overallProgress),this.instancedMeshManager.setGeometrySize(this.engineState.instanceGeometryScale),this.setOverallProgress(this.engineState.overallProgress,!1))}registerMesh(e,t){this.assetService.register(e,t)}registerMatcap(e,t){this.assetService.register(e,t)}async fetchAndRegisterMesh(e,t){return await this.assetService.loadMeshAsync(e,t)}async fetchAndRegisterMatcap(e,t){return await this.assetService.loadTextureAsync(e,t)}useIntersect(e){this.intersectionService.setActive(e),this.engineState.useIntersect=e,e||(this.engineState.pointerPosition={x:-99999999,y:-99999999},this.simulationRendererService.setInteractionPosition({x:0,y:0,z:0,w:0}))}setPointerPosition(e){this.engineState.useIntersect&&(this.engineState.pointerPosition=e,this.intersectionService.setPointerPosition(e))}setGeometrySize(e){this.engineState.instanceGeometryScale=e,this.instancedMeshManager.setGeometrySize(e)}setVelocityTractionForce(e){this.engineState.velocityTractionForce=e,this.simulationRendererService.setVelocityTractionForce(e)}setPositionalTractionForce(e){this.engineState.positionalTractionForce=e,this.simulationRendererService.setPositionalTractionForce(e)}setMaxRepelDistance(e){this.engineState.maxRepelDistance=e,this.simulationRendererService.setMaxRepelDistance(e)}async setMeshSequence(e){if(!e||e.length<1)return this.eventEmitter.emit("invalidRequest",{message:"Mesh sequence must contain at least one mesh ID."}),this.engineState.meshSequence=[],void this.intersectionService.setMeshSequence([]);this.engineState.meshSequence=e,this.engineState.overallProgress=0;const t=e.map(e=>this.assetService.getMesh(e)).filter(e=>null!==e);if(t.length!==e.length){const i=e.filter(e=>!this.assetService.getMesh(e));if(console.warn(`Could not find meshes for IDs: ${i.join(", ")}. Proceeding with ${t.length} found meshes.`),this.eventEmitter.emit("invalidRequest",{message:`Could not find meshes for IDs: ${i.join(", ")}`}),t.length<1)return this.engineState.meshSequence=[],void this.intersectionService.setMeshSequence([]);this.engineState.meshSequence=t.map(e=>e.name)}try{this.meshSequenceAtlasTexture=await this.dataTextureManager.createSequenceDataTextureAtlas(t,this.engineState.textureSize),this.simulationRendererService.setPositionAtlas({dataTexture:this.meshSequenceAtlasTexture,textureSize:this.engineState.textureSize,numMeshes:this.engineState.meshSequence.length,singleTextureSize:this.engineState.textureSize}),this.simulationRendererService.setOverallProgress(this.engineState.overallProgress),this.intersectionService.setMeshSequence(t),this.intersectionService.setOverallProgress(this.engineState.overallProgress),this.setOverallProgress(0,!1)}catch(i){console.error("Failed during mesh sequence setup:",i),this.meshSequenceAtlasTexture=null}}setOverallProgress(e,t=!0){t&&this.eventEmitter.emit("transitionCancelled",{type:"mesh-sequence"});const i=T(e,0,1);this.engineState.overallProgress=i,this.simulationRendererService.setOverallProgress(i),this.intersectionService.setOverallProgress(i);const{textureA:s,textureB:n,localProgress:r}=this.calculateTextureInterpolation(e);this.instancedMeshManager.updateTextureInterpolation(s,n,r)}scheduleMeshSequenceTransition(e,t=1e3,i=f,s={},n=!0){n&&this.eventEmitter.emit("transitionCancelled",{type:"mesh-sequence"});const r=this.engineState.overallProgress,o=e-r,a={duration:t,easing:i},l={...s,onTransitionProgress:e=>{const t=r+o*e;this.setOverallProgress(t,!1),s.onTransitionProgress?.(t)},onTransitionBegin:s.onTransitionBegin,onTransitionFinished:()=>{this.setOverallProgress(e,!1),s.onTransitionFinished?.()},onTransitionCancelled:s.onTransitionCancelled};this.transitionService.enqueue("mesh-sequence",a,l)}handleServiceStateUpdated({type:e,state:t}){this.serviceStates[e]=t}getObject(){return this.instancedMeshManager.getMesh()}getMeshIDs(){return this.assetService.getMeshIDs()}getMatcapIDs(){return this.assetService.getTextureIDs()}getMeshes(){return this.assetService.getMeshes()}getTextures(){return this.assetService.getTextures()}getTextureSize(){return this.engineState.textureSize}getUseIntersect(){return this.engineState.useIntersect}getEngineStateSnapshot(){return{...this.engineState}}dispose(){this.scene&&this.instancedMeshManager&&this.scene.remove(this.instancedMeshManager.getMesh()),this.eventEmitter.off("serviceStateUpdated",this.boundHandleServiceStateUpdated),this.eventEmitter.off("interactionPositionUpdated",this.boundHandleInteractionPositionUpdated),this.simulationRendererService?.dispose(),this.instancedMeshManager?.dispose(),this.intersectionService?.dispose(),this.assetService?.dispose(),this.dataTextureManager?.dispose(),this.transitionService?.dispose(),this.eventEmitter?.dispose()}initialEngineState(e){return{textureSize:e.textureSize,meshSequence:[],overallProgress:0,textureSequence:[],velocityTractionForce:.1,positionalTractionForce:.1,maxRepelDistance:.3,pointerPosition:{x:0,y:0},instanceGeometryScale:{x:1,y:1,z:1},useIntersect:e.useIntersection??!0}}getInitialServiceStates(){return{"data-texture":"created","instanced-mesh":"created",matcap:"created",simulation:"created",asset:"created"}}handleInteractionPositionUpdated({position:e}){this.simulationRendererService.setInteractionPosition(e)}calculateTextureInterpolation(e){const t=this.engineState.textureSequence,i=t.length;if(0===i){const e=this.assetService.getFallbackTexture();return{textureA:e,textureB:e,localProgress:0}}if(1===i){const e=this.getTextureForSequenceItem(t[0]);return{textureA:e,textureB:e,localProgress:0}}const s=i-1,n=1/s,r=e*s;let o=Math.floor(r),a=o+1;o=Math.max(0,Math.min(o,s)),a=Math.max(0,Math.min(a,s));let l=0;n>0&&(l=(e-o*n)/n),e>=1&&(o=s,a=s,l=1),l=Math.max(0,Math.min(l,1));const u=t[o],c=t[a];return{textureA:this.getTextureForSequenceItem(u),textureB:this.getTextureForSequenceItem(c),localProgress:l}}getTextureForSequenceItem(e){return"matcap"===e.type?this.assetService.getMatcapTexture(e.id):this.assetService.getSolidColorTexture(e.value)}}export{O as ParticlesEngine};
2067
2
  //# sourceMappingURL=ionian.js.map