@cyberluke/three-particles 4.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3138 @@
1
+ import Easing from 'easing-functions';
2
+ import * as THREE3 from 'three';
3
+ import { ObjectUtils } from '@newkrok/three-utils';
4
+ import { StorageBufferAttribute } from 'three/webgpu';
5
+
6
+ // src/js/effects/three-particles/version.ts
7
+ var REVISION = "4.0.1" ;
8
+ if (typeof globalThis !== "undefined") {
9
+ const g = globalThis;
10
+ if (g.__THREE_PARTICLES__ && g.__THREE_PARTICLES__ !== REVISION) {
11
+ console.warn(
12
+ "WARNING: Multiple instances of @cyberluke/three-particles being imported."
13
+ );
14
+ } else {
15
+ g.__THREE_PARTICLES__ = REVISION;
16
+ }
17
+ }
18
+
19
+ // src/js/effects/three-particles/color-utils.ts
20
+ var sRGBToLinear = (c) => c < 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
21
+ var linearToSRGB = (c) => c < 31308e-7 ? c * 12.92 : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
22
+ var rgbSRGBToLinear = (c) => ({
23
+ r: sRGBToLinear(c.r ?? 0),
24
+ g: sRGBToLinear(c.g ?? 0),
25
+ b: sRGBToLinear(c.b ?? 0)
26
+ });
27
+
28
+ // src/js/effects/three-particles/three-particles-bezier.ts
29
+ var cache = [];
30
+ var nCr = (n, k) => {
31
+ let z = 1;
32
+ for (let i = 1; i <= k; i++) z *= (n + 1 - i) / i;
33
+ return z;
34
+ };
35
+ var createBezierCurveFunction = (particleSystemId, bezierPoints) => {
36
+ const cacheEntry = cache.find((item) => item.bezierPoints === bezierPoints);
37
+ if (cacheEntry) {
38
+ if (!cacheEntry.referencedBy.includes(particleSystemId))
39
+ cacheEntry.referencedBy.push(particleSystemId);
40
+ return cacheEntry.curveFunction;
41
+ }
42
+ const entry = {
43
+ referencedBy: [particleSystemId],
44
+ bezierPoints,
45
+ curveFunction: (percentage) => {
46
+ if (percentage < 0) return bezierPoints[0].y;
47
+ if (percentage > 1) return bezierPoints[bezierPoints.length - 1].y;
48
+ let start = 0;
49
+ let stop = bezierPoints.length - 1;
50
+ for (let i = 0; i < bezierPoints.length; i++) {
51
+ const point = bezierPoints[i];
52
+ if (percentage < (point.percentage ?? 0)) {
53
+ stop = i;
54
+ break;
55
+ }
56
+ if (point.percentage !== void 0) start = i;
57
+ }
58
+ const n = stop - start;
59
+ const calculatedPercentage = (percentage - (bezierPoints[start].percentage ?? 0)) / ((bezierPoints[stop].percentage ?? 1) - (bezierPoints[start].percentage ?? 0));
60
+ let value = 0;
61
+ for (let i = 0; i <= n; i++) {
62
+ const p = bezierPoints[start + i];
63
+ const c = nCr(n, i) * Math.pow(1 - calculatedPercentage, n - i) * Math.pow(calculatedPercentage, i);
64
+ value += c * p.y;
65
+ }
66
+ return value;
67
+ }
68
+ };
69
+ cache.push(entry);
70
+ return entry.curveFunction;
71
+ };
72
+ var removeBezierCurveFunction = (particleSystemId) => {
73
+ while (true) {
74
+ const index = cache.findIndex(
75
+ (item) => item.referencedBy.includes(particleSystemId)
76
+ );
77
+ if (index === -1) break;
78
+ const entry = cache[index];
79
+ entry.referencedBy = entry.referencedBy.filter(
80
+ (id) => id !== particleSystemId
81
+ );
82
+ if (entry.referencedBy.length === 0) cache.splice(index, 1);
83
+ }
84
+ };
85
+ var getBezierCacheSize = () => cache.length;
86
+ var CurveFunctionId = /* @__PURE__ */ ((CurveFunctionId3) => {
87
+ CurveFunctionId3["BEZIER"] = "BEZIER";
88
+ CurveFunctionId3["LINEAR"] = "LINEAR";
89
+ CurveFunctionId3["QUADRATIC_IN"] = "QUADRATIC_IN";
90
+ CurveFunctionId3["QUADRATIC_OUT"] = "QUADRATIC_OUT";
91
+ CurveFunctionId3["QUADRATIC_IN_OUT"] = "QUADRATIC_IN_OUT";
92
+ CurveFunctionId3["CUBIC_IN"] = "CUBIC_IN";
93
+ CurveFunctionId3["CUBIC_OUT"] = "CUBIC_OUT";
94
+ CurveFunctionId3["CUBIC_IN_OUT"] = "CUBIC_IN_OUT";
95
+ CurveFunctionId3["QUARTIC_IN"] = "QUARTIC_IN";
96
+ CurveFunctionId3["QUARTIC_OUT"] = "QUARTIC_OUT";
97
+ CurveFunctionId3["QUARTIC_IN_OUT"] = "QUARTIC_IN_OUT";
98
+ CurveFunctionId3["QUINTIC_IN"] = "QUINTIC_IN";
99
+ CurveFunctionId3["QUINTIC_OUT"] = "QUINTIC_OUT";
100
+ CurveFunctionId3["QUINTIC_IN_OUT"] = "QUINTIC_IN_OUT";
101
+ CurveFunctionId3["SINUSOIDAL_IN"] = "SINUSOIDAL_IN";
102
+ CurveFunctionId3["SINUSOIDAL_OUT"] = "SINUSOIDAL_OUT";
103
+ CurveFunctionId3["SINUSOIDAL_IN_OUT"] = "SINUSOIDAL_IN_OUT";
104
+ CurveFunctionId3["EXPONENTIAL_IN"] = "EXPONENTIAL_IN";
105
+ CurveFunctionId3["EXPONENTIAL_OUT"] = "EXPONENTIAL_OUT";
106
+ CurveFunctionId3["EXPONENTIAL_IN_OUT"] = "EXPONENTIAL_IN_OUT";
107
+ CurveFunctionId3["CIRCULAR_IN"] = "CIRCULAR_IN";
108
+ CurveFunctionId3["CIRCULAR_OUT"] = "CIRCULAR_OUT";
109
+ CurveFunctionId3["CIRCULAR_IN_OUT"] = "CIRCULAR_IN_OUT";
110
+ CurveFunctionId3["ELASTIC_IN"] = "ELASTIC_IN";
111
+ CurveFunctionId3["ELASTIC_OUT"] = "ELASTIC_OUT";
112
+ CurveFunctionId3["ELASTIC_IN_OUT"] = "ELASTIC_IN_OUT";
113
+ CurveFunctionId3["BACK_IN"] = "BACK_IN";
114
+ CurveFunctionId3["BACK_OUT"] = "BACK_OUT";
115
+ CurveFunctionId3["BACK_IN_OUT"] = "BACK_IN_OUT";
116
+ CurveFunctionId3["BOUNCE_IN"] = "BOUNCE_IN";
117
+ CurveFunctionId3["BOUNCE_OUT"] = "BOUNCE_OUT";
118
+ CurveFunctionId3["BOUNCE_IN_OUT"] = "BOUNCE_IN_OUT";
119
+ return CurveFunctionId3;
120
+ })(CurveFunctionId || {});
121
+ var curveFunctionIdMap = {
122
+ ["LINEAR" /* LINEAR */]: Easing.Linear.None,
123
+ ["QUADRATIC_IN" /* QUADRATIC_IN */]: Easing.Quadratic.In,
124
+ ["QUADRATIC_OUT" /* QUADRATIC_OUT */]: Easing.Quadratic.Out,
125
+ ["QUADRATIC_IN_OUT" /* QUADRATIC_IN_OUT */]: Easing.Quadratic.InOut,
126
+ ["CUBIC_IN" /* CUBIC_IN */]: Easing.Cubic.In,
127
+ ["CUBIC_OUT" /* CUBIC_OUT */]: Easing.Cubic.Out,
128
+ ["CUBIC_IN_OUT" /* CUBIC_IN_OUT */]: Easing.Cubic.InOut,
129
+ ["QUARTIC_IN" /* QUARTIC_IN */]: Easing.Quartic.In,
130
+ ["QUARTIC_OUT" /* QUARTIC_OUT */]: Easing.Quartic.Out,
131
+ ["QUARTIC_IN_OUT" /* QUARTIC_IN_OUT */]: Easing.Quartic.InOut,
132
+ ["QUINTIC_IN" /* QUINTIC_IN */]: Easing.Quintic.In,
133
+ ["QUINTIC_OUT" /* QUINTIC_OUT */]: Easing.Quintic.Out,
134
+ ["QUINTIC_IN_OUT" /* QUINTIC_IN_OUT */]: Easing.Quintic.InOut,
135
+ ["SINUSOIDAL_IN" /* SINUSOIDAL_IN */]: Easing.Sinusoidal.In,
136
+ ["SINUSOIDAL_OUT" /* SINUSOIDAL_OUT */]: Easing.Sinusoidal.Out,
137
+ ["SINUSOIDAL_IN_OUT" /* SINUSOIDAL_IN_OUT */]: Easing.Sinusoidal.InOut,
138
+ ["EXPONENTIAL_IN" /* EXPONENTIAL_IN */]: Easing.Exponential.In,
139
+ ["EXPONENTIAL_OUT" /* EXPONENTIAL_OUT */]: Easing.Exponential.Out,
140
+ ["EXPONENTIAL_IN_OUT" /* EXPONENTIAL_IN_OUT */]: Easing.Exponential.InOut,
141
+ ["CIRCULAR_IN" /* CIRCULAR_IN */]: Easing.Circular.In,
142
+ ["CIRCULAR_OUT" /* CIRCULAR_OUT */]: Easing.Circular.Out,
143
+ ["CIRCULAR_IN_OUT" /* CIRCULAR_IN_OUT */]: Easing.Circular.InOut,
144
+ ["ELASTIC_IN" /* ELASTIC_IN */]: Easing.Elastic.In,
145
+ ["ELASTIC_OUT" /* ELASTIC_OUT */]: Easing.Elastic.Out,
146
+ ["ELASTIC_IN_OUT" /* ELASTIC_IN_OUT */]: Easing.Elastic.InOut,
147
+ ["BACK_IN" /* BACK_IN */]: Easing.Back.In,
148
+ ["BACK_OUT" /* BACK_OUT */]: Easing.Back.Out,
149
+ ["BACK_IN_OUT" /* BACK_IN_OUT */]: Easing.Back.InOut,
150
+ ["BOUNCE_IN" /* BOUNCE_IN */]: Easing.Bounce.In,
151
+ ["BOUNCE_OUT" /* BOUNCE_OUT */]: Easing.Bounce.Out,
152
+ ["BOUNCE_IN_OUT" /* BOUNCE_IN_OUT */]: Easing.Bounce.InOut
153
+ };
154
+ var getCurveFunction = (curveFunctionId) => typeof curveFunctionId === "function" ? curveFunctionId : curveFunctionIdMap[curveFunctionId];
155
+
156
+ // src/js/effects/three-particles/three-particles-enums.ts
157
+ var SimulationSpace = /* @__PURE__ */ ((SimulationSpace2) => {
158
+ SimulationSpace2["LOCAL"] = "LOCAL";
159
+ SimulationSpace2["WORLD"] = "WORLD";
160
+ return SimulationSpace2;
161
+ })(SimulationSpace || {});
162
+ var Shape = /* @__PURE__ */ ((Shape2) => {
163
+ Shape2["SPHERE"] = "SPHERE";
164
+ Shape2["CONE"] = "CONE";
165
+ Shape2["BOX"] = "BOX";
166
+ Shape2["CIRCLE"] = "CIRCLE";
167
+ Shape2["RECTANGLE"] = "RECTANGLE";
168
+ return Shape2;
169
+ })(Shape || {});
170
+ var EmitFrom = /* @__PURE__ */ ((EmitFrom2) => {
171
+ EmitFrom2["VOLUME"] = "VOLUME";
172
+ EmitFrom2["SHELL"] = "SHELL";
173
+ EmitFrom2["EDGE"] = "EDGE";
174
+ return EmitFrom2;
175
+ })(EmitFrom || {});
176
+ var TimeMode = /* @__PURE__ */ ((TimeMode2) => {
177
+ TimeMode2["LIFETIME"] = "LIFETIME";
178
+ TimeMode2["FPS"] = "FPS";
179
+ return TimeMode2;
180
+ })(TimeMode || {});
181
+ var LifeTimeCurve = /* @__PURE__ */ ((LifeTimeCurve2) => {
182
+ LifeTimeCurve2["BEZIER"] = "BEZIER";
183
+ LifeTimeCurve2["EASING"] = "EASING";
184
+ return LifeTimeCurve2;
185
+ })(LifeTimeCurve || {});
186
+ var SubEmitterTrigger = /* @__PURE__ */ ((SubEmitterTrigger3) => {
187
+ SubEmitterTrigger3["BIRTH"] = "BIRTH";
188
+ SubEmitterTrigger3["DEATH"] = "DEATH";
189
+ return SubEmitterTrigger3;
190
+ })(SubEmitterTrigger || {});
191
+ var ForceFieldType = /* @__PURE__ */ ((ForceFieldType3) => {
192
+ ForceFieldType3["POINT"] = "POINT";
193
+ ForceFieldType3["DIRECTIONAL"] = "DIRECTIONAL";
194
+ return ForceFieldType3;
195
+ })(ForceFieldType || {});
196
+ var RendererType = /* @__PURE__ */ ((RendererType2) => {
197
+ RendererType2["POINTS"] = "POINTS";
198
+ RendererType2["INSTANCED"] = "INSTANCED";
199
+ RendererType2["TRAIL"] = "TRAIL";
200
+ RendererType2["MESH"] = "MESH";
201
+ return RendererType2;
202
+ })(RendererType || {});
203
+ var ForceFieldFalloff = /* @__PURE__ */ ((ForceFieldFalloff3) => {
204
+ ForceFieldFalloff3["NONE"] = "NONE";
205
+ ForceFieldFalloff3["LINEAR"] = "LINEAR";
206
+ ForceFieldFalloff3["QUADRATIC"] = "QUADRATIC";
207
+ return ForceFieldFalloff3;
208
+ })(ForceFieldFalloff || {});
209
+ var CollisionPlaneMode = /* @__PURE__ */ ((CollisionPlaneMode3) => {
210
+ CollisionPlaneMode3["KILL"] = "KILL";
211
+ CollisionPlaneMode3["CLAMP"] = "CLAMP";
212
+ CollisionPlaneMode3["BOUNCE"] = "BOUNCE";
213
+ return CollisionPlaneMode3;
214
+ })(CollisionPlaneMode || {});
215
+ var SimulationBackend = /* @__PURE__ */ ((SimulationBackend2) => {
216
+ SimulationBackend2["AUTO"] = "AUTO";
217
+ SimulationBackend2["CPU"] = "CPU";
218
+ SimulationBackend2["GPU"] = "GPU";
219
+ return SimulationBackend2;
220
+ })(SimulationBackend || {});
221
+
222
+ // src/js/effects/three-particles/three-particles-constants.ts
223
+ var SCALAR_STRIDE = 10;
224
+ var S_IS_ACTIVE = 0;
225
+ var S_LIFETIME = 1;
226
+ var S_START_LIFETIME = 2;
227
+ var S_START_FRAME = 3;
228
+ var S_SIZE = 4;
229
+ var S_ROTATION = 5;
230
+ var S_COLOR_R = 6;
231
+ var S_COLOR_G = 7;
232
+ var S_COLOR_B = 8;
233
+ var S_COLOR_A = 9;
234
+ var calculateRandomPositionAndVelocityOnSphere = (position, quaternion, velocity, speed, {
235
+ radius,
236
+ radiusThickness,
237
+ arc
238
+ }) => {
239
+ const u = Math.random() * (arc / 360);
240
+ const v = Math.random();
241
+ const randomizedDistanceRatio = Math.random();
242
+ const theta = 2 * Math.PI * u;
243
+ const phi = Math.acos(2 * v - 1);
244
+ const sinPhi = Math.sin(phi);
245
+ const xDirection = sinPhi * Math.cos(theta);
246
+ const yDirection = sinPhi * Math.sin(theta);
247
+ const zDirection = Math.cos(phi);
248
+ const normalizedThickness = 1 - radiusThickness;
249
+ position.x = radius * normalizedThickness * xDirection + radius * radiusThickness * randomizedDistanceRatio * xDirection;
250
+ position.y = radius * normalizedThickness * yDirection + radius * radiusThickness * randomizedDistanceRatio * yDirection;
251
+ position.z = radius * normalizedThickness * zDirection + radius * radiusThickness * randomizedDistanceRatio * zDirection;
252
+ position.applyQuaternion(quaternion);
253
+ const speedMultiplierByPosition = 1 / position.length();
254
+ velocity.set(
255
+ position.x * speedMultiplierByPosition * speed,
256
+ position.y * speedMultiplierByPosition * speed,
257
+ position.z * speedMultiplierByPosition * speed
258
+ );
259
+ velocity.applyQuaternion(quaternion);
260
+ };
261
+ var calculateRandomPositionAndVelocityOnCone = (position, quaternion, velocity, speed, {
262
+ radius,
263
+ radiusThickness,
264
+ arc,
265
+ angle = 90
266
+ }) => {
267
+ const theta = 2 * Math.PI * Math.random() * (arc / 360);
268
+ const randomizedDistanceRatio = Math.random();
269
+ const xDirection = Math.cos(theta);
270
+ const yDirection = Math.sin(theta);
271
+ const normalizedThickness = 1 - radiusThickness;
272
+ position.x = radius * normalizedThickness * xDirection + radius * radiusThickness * randomizedDistanceRatio * xDirection;
273
+ position.y = radius * normalizedThickness * yDirection + radius * radiusThickness * randomizedDistanceRatio * yDirection;
274
+ position.z = 0;
275
+ position.applyQuaternion(quaternion);
276
+ const positionLength = position.length();
277
+ const normalizedAngle = Math.abs(
278
+ positionLength / radius * THREE3.MathUtils.degToRad(angle)
279
+ );
280
+ const sinNormalizedAngle = Math.sin(normalizedAngle);
281
+ const speedMultiplierByPosition = 1 / positionLength;
282
+ velocity.set(
283
+ position.x * sinNormalizedAngle * speedMultiplierByPosition * speed,
284
+ position.y * sinNormalizedAngle * speedMultiplierByPosition * speed,
285
+ Math.cos(normalizedAngle) * speed
286
+ );
287
+ velocity.applyQuaternion(quaternion);
288
+ };
289
+ var calculateRandomPositionAndVelocityOnBox = (position, quaternion, velocity, speed, { scale, emitFrom }) => {
290
+ const _scale = scale;
291
+ switch (emitFrom) {
292
+ case "VOLUME" /* VOLUME */:
293
+ position.x = Math.random() * _scale.x - _scale.x / 2;
294
+ position.y = Math.random() * _scale.y - _scale.y / 2;
295
+ position.z = Math.random() * _scale.z - _scale.z / 2;
296
+ break;
297
+ case "SHELL" /* SHELL */:
298
+ const side = Math.floor(Math.random() * 6);
299
+ const perpendicularAxis = side % 3;
300
+ const shellResult = [];
301
+ shellResult[perpendicularAxis] = side > 2 ? 1 : 0;
302
+ shellResult[(perpendicularAxis + 1) % 3] = Math.random();
303
+ shellResult[(perpendicularAxis + 2) % 3] = Math.random();
304
+ position.x = shellResult[0] * _scale.x - _scale.x / 2;
305
+ position.y = shellResult[1] * _scale.y - _scale.y / 2;
306
+ position.z = shellResult[2] * _scale.z - _scale.z / 2;
307
+ break;
308
+ case "EDGE" /* EDGE */:
309
+ const side2 = Math.floor(Math.random() * 6);
310
+ const perpendicularAxis2 = side2 % 3;
311
+ const edge = Math.floor(Math.random() * 4);
312
+ const edgeResult = [];
313
+ edgeResult[perpendicularAxis2] = side2 > 2 ? 1 : 0;
314
+ edgeResult[(perpendicularAxis2 + 1) % 3] = edge < 2 ? Math.random() : edge - 2;
315
+ edgeResult[(perpendicularAxis2 + 2) % 3] = edge < 2 ? edge : Math.random();
316
+ position.x = edgeResult[0] * _scale.x - _scale.x / 2;
317
+ position.y = edgeResult[1] * _scale.y - _scale.y / 2;
318
+ position.z = edgeResult[2] * _scale.z - _scale.z / 2;
319
+ break;
320
+ }
321
+ position.applyQuaternion(quaternion);
322
+ velocity.set(0, 0, speed);
323
+ velocity.applyQuaternion(quaternion);
324
+ };
325
+ var calculateRandomPositionAndVelocityOnCircle = (position, quaternion, velocity, speed, {
326
+ radius,
327
+ radiusThickness,
328
+ arc
329
+ }) => {
330
+ const theta = 2 * Math.PI * Math.random() * (arc / 360);
331
+ const randomizedDistanceRatio = Math.random();
332
+ const xDirection = Math.cos(theta);
333
+ const yDirection = Math.sin(theta);
334
+ const normalizedThickness = 1 - radiusThickness;
335
+ position.x = radius * normalizedThickness * xDirection + radius * radiusThickness * randomizedDistanceRatio * xDirection;
336
+ position.y = radius * normalizedThickness * yDirection + radius * radiusThickness * randomizedDistanceRatio * yDirection;
337
+ position.z = 0;
338
+ position.applyQuaternion(quaternion);
339
+ const positionLength = position.length();
340
+ const speedMultiplierByPosition = 1 / positionLength;
341
+ velocity.set(
342
+ position.x * speedMultiplierByPosition * speed,
343
+ position.y * speedMultiplierByPosition * speed,
344
+ 0
345
+ );
346
+ velocity.applyQuaternion(quaternion);
347
+ };
348
+ var calculateRandomPositionAndVelocityOnRectangle = (position, quaternion, velocity, speed, { rotation, scale }) => {
349
+ const _scale = scale;
350
+ const _rotation = rotation;
351
+ const xOffset = Math.random() * _scale.x - _scale.x / 2;
352
+ const yOffset = Math.random() * _scale.y - _scale.y / 2;
353
+ const rotationX = THREE3.MathUtils.degToRad(_rotation.x);
354
+ const rotationY = THREE3.MathUtils.degToRad(_rotation.y);
355
+ position.x = xOffset * Math.cos(rotationY);
356
+ position.y = yOffset * Math.cos(rotationX);
357
+ position.z = xOffset * Math.sin(rotationY) - yOffset * Math.sin(rotationX);
358
+ position.applyQuaternion(quaternion);
359
+ velocity.set(0, 0, speed);
360
+ velocity.applyQuaternion(quaternion);
361
+ };
362
+ var createDefaultMeshTexture = () => {
363
+ try {
364
+ const canvas = document.createElement("canvas");
365
+ canvas.width = 1;
366
+ canvas.height = 1;
367
+ const context = canvas.getContext("2d");
368
+ if (context) {
369
+ context.fillStyle = "white";
370
+ context.fillRect(0, 0, 1, 1);
371
+ const texture = new THREE3.CanvasTexture(canvas);
372
+ texture.needsUpdate = true;
373
+ return texture;
374
+ }
375
+ return null;
376
+ } catch {
377
+ return null;
378
+ }
379
+ };
380
+ var createDefaultParticleTexture = () => {
381
+ try {
382
+ const canvas = document.createElement("canvas");
383
+ const size = 64;
384
+ canvas.width = size;
385
+ canvas.height = size;
386
+ const context = canvas.getContext("2d");
387
+ if (context) {
388
+ const centerX = size / 2;
389
+ const centerY = size / 2;
390
+ const radius = size / 2 - 2;
391
+ context.beginPath();
392
+ context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
393
+ context.fillStyle = "white";
394
+ context.fill();
395
+ const texture = new THREE3.CanvasTexture(canvas);
396
+ texture.needsUpdate = true;
397
+ return texture;
398
+ } else {
399
+ console.warn(
400
+ "Could not get 2D context to generate default particle texture."
401
+ );
402
+ return null;
403
+ }
404
+ } catch (error) {
405
+ console.warn("Error creating default particle texture:", error);
406
+ return null;
407
+ }
408
+ };
409
+ var isLifeTimeCurve = (value) => {
410
+ return typeof value !== "number" && "type" in value;
411
+ };
412
+ var getCurveFunctionFromConfig = (particleSystemId, lifetimeCurve) => {
413
+ if (lifetimeCurve.type === "BEZIER" /* BEZIER */) {
414
+ return createBezierCurveFunction(
415
+ particleSystemId,
416
+ lifetimeCurve.bezierPoints
417
+ );
418
+ }
419
+ if (lifetimeCurve.type === "EASING" /* EASING */) {
420
+ return lifetimeCurve.curveFunction;
421
+ }
422
+ const raw = lifetimeCurve;
423
+ if (Array.isArray(raw.bezierPoints)) {
424
+ return createBezierCurveFunction(
425
+ particleSystemId,
426
+ raw.bezierPoints
427
+ );
428
+ }
429
+ if (typeof raw.curveFunction === "function") {
430
+ return raw.curveFunction;
431
+ }
432
+ throw new Error(`Unsupported value type: ${lifetimeCurve}`);
433
+ };
434
+ var calculateValue = (particleSystemId, value, time = 0) => {
435
+ if (typeof value === "number") {
436
+ return value;
437
+ }
438
+ if ("min" in value && "max" in value) {
439
+ if (value.min === value.max) {
440
+ return value.min ?? 0;
441
+ }
442
+ return THREE3.MathUtils.randFloat(value.min ?? 0, value.max ?? 1);
443
+ }
444
+ const lifetimeCurve = value;
445
+ return getCurveFunctionFromConfig(particleSystemId, lifetimeCurve)(time) * (lifetimeCurve.scale ?? 1);
446
+ };
447
+
448
+ // src/js/effects/three-particles/three-particles-modifiers.ts
449
+ var noiseInput = new THREE3.Vector3(0, 0, 0);
450
+ var orbitalEuler = new THREE3.Euler();
451
+ var applyModifiers = ({
452
+ delta,
453
+ generalData,
454
+ normalizedConfig,
455
+ attributes,
456
+ scalarArray,
457
+ particleLifetimePercentage,
458
+ particleIndex,
459
+ updateFlags
460
+ }) => {
461
+ const {
462
+ particleSystemId,
463
+ startValues,
464
+ lifetimeValues,
465
+ linearVelocityData,
466
+ orbitalVelocityData,
467
+ noise,
468
+ modifierCurves
469
+ } = generalData;
470
+ const positionIndex = particleIndex * 3;
471
+ const positionArr = attributes.position.array;
472
+ const base = particleIndex * SCALAR_STRIDE;
473
+ if (linearVelocityData) {
474
+ const { speed, valueModifiers } = linearVelocityData[particleIndex];
475
+ const normalizedXSpeed = valueModifiers.x ? valueModifiers.x(particleLifetimePercentage) : speed.x;
476
+ const normalizedYSpeed = valueModifiers.y ? valueModifiers.y(particleLifetimePercentage) : speed.y;
477
+ const normalizedZSpeed = valueModifiers.z ? valueModifiers.z(particleLifetimePercentage) : speed.z;
478
+ positionArr[positionIndex] += normalizedXSpeed * delta;
479
+ positionArr[positionIndex + 1] += normalizedYSpeed * delta;
480
+ positionArr[positionIndex + 2] += normalizedZSpeed * delta;
481
+ if (updateFlags) updateFlags.position = true;
482
+ else attributes.position.needsUpdate = true;
483
+ }
484
+ if (orbitalVelocityData) {
485
+ const { speed, positionOffset, valueModifiers } = orbitalVelocityData[particleIndex];
486
+ positionArr[positionIndex] -= positionOffset.x;
487
+ positionArr[positionIndex + 1] -= positionOffset.y;
488
+ positionArr[positionIndex + 2] -= positionOffset.z;
489
+ const normalizedXSpeed = valueModifiers.x ? valueModifiers.x(particleLifetimePercentage) : speed.x;
490
+ const normalizedYSpeed = valueModifiers.y ? valueModifiers.y(particleLifetimePercentage) : speed.y;
491
+ const normalizedZSpeed = valueModifiers.z ? valueModifiers.z(particleLifetimePercentage) : speed.z;
492
+ orbitalEuler.set(
493
+ normalizedXSpeed * delta,
494
+ normalizedZSpeed * delta,
495
+ normalizedYSpeed * delta
496
+ );
497
+ positionOffset.applyEuler(orbitalEuler);
498
+ positionArr[positionIndex] += positionOffset.x;
499
+ positionArr[positionIndex + 1] += positionOffset.y;
500
+ positionArr[positionIndex + 2] += positionOffset.z;
501
+ if (updateFlags) updateFlags.position = true;
502
+ else attributes.position.needsUpdate = true;
503
+ }
504
+ if (normalizedConfig.sizeOverLifetime.isActive) {
505
+ const multiplier = modifierCurves?.size ? modifierCurves.size(particleLifetimePercentage) : calculateValue(
506
+ particleSystemId,
507
+ normalizedConfig.sizeOverLifetime.lifetimeCurve,
508
+ particleLifetimePercentage
509
+ );
510
+ scalarArray[base + S_SIZE] = startValues.startSize[particleIndex] * multiplier;
511
+ }
512
+ if (normalizedConfig.opacityOverLifetime.isActive) {
513
+ const multiplier = modifierCurves?.opacity ? modifierCurves.opacity(particleLifetimePercentage) : calculateValue(
514
+ particleSystemId,
515
+ normalizedConfig.opacityOverLifetime.lifetimeCurve,
516
+ particleLifetimePercentage
517
+ );
518
+ scalarArray[base + S_COLOR_A] = startValues.startOpacity[particleIndex] * multiplier;
519
+ }
520
+ if (normalizedConfig.colorOverLifetime.isActive) {
521
+ const rMultiplier = modifierCurves?.colorR ? modifierCurves.colorR(particleLifetimePercentage) : calculateValue(
522
+ particleSystemId,
523
+ normalizedConfig.colorOverLifetime.r,
524
+ particleLifetimePercentage
525
+ );
526
+ const gMultiplier = modifierCurves?.colorG ? modifierCurves.colorG(particleLifetimePercentage) : calculateValue(
527
+ particleSystemId,
528
+ normalizedConfig.colorOverLifetime.g,
529
+ particleLifetimePercentage
530
+ );
531
+ const bMultiplier = modifierCurves?.colorB ? modifierCurves.colorB(particleLifetimePercentage) : calculateValue(
532
+ particleSystemId,
533
+ normalizedConfig.colorOverLifetime.b,
534
+ particleLifetimePercentage
535
+ );
536
+ scalarArray[base + S_COLOR_R] = startValues.startColorR[particleIndex] * rMultiplier;
537
+ scalarArray[base + S_COLOR_G] = startValues.startColorG[particleIndex] * gMultiplier;
538
+ scalarArray[base + S_COLOR_B] = startValues.startColorB[particleIndex] * bMultiplier;
539
+ }
540
+ if (lifetimeValues.rotationOverLifetime) {
541
+ scalarArray[base + S_ROTATION] += lifetimeValues.rotationOverLifetime[particleIndex] * delta * 0.02;
542
+ }
543
+ if (noise.isActive) {
544
+ const {
545
+ sampler,
546
+ strength,
547
+ noisePower,
548
+ offsets,
549
+ positionAmount,
550
+ rotationAmount,
551
+ sizeAmount
552
+ } = noise;
553
+ let noiseOnPosition;
554
+ const noisePosition = (particleLifetimePercentage + (offsets ? offsets[particleIndex] : 0)) * 10 * strength;
555
+ noiseInput.set(noisePosition, 0, 0);
556
+ noiseOnPosition = sampler.get3(noiseInput);
557
+ positionArr[positionIndex] += noiseOnPosition * noisePower * positionAmount;
558
+ if (rotationAmount !== 0) {
559
+ scalarArray[base + S_ROTATION] += noiseOnPosition * noisePower * rotationAmount;
560
+ }
561
+ if (sizeAmount !== 0) {
562
+ scalarArray[base + S_SIZE] += noiseOnPosition * noisePower * sizeAmount;
563
+ }
564
+ noiseInput.set(noisePosition, noisePosition, 0);
565
+ noiseOnPosition = sampler.get3(noiseInput);
566
+ positionArr[positionIndex + 1] += noiseOnPosition * noisePower * positionAmount;
567
+ noiseInput.set(noisePosition, noisePosition, noisePosition);
568
+ noiseOnPosition = sampler.get3(noiseInput);
569
+ positionArr[positionIndex + 2] += noiseOnPosition * noisePower * positionAmount;
570
+ if (updateFlags) updateFlags.position = true;
571
+ else attributes.position.needsUpdate = true;
572
+ }
573
+ if (attributes.quat) {
574
+ const rotZ = scalarArray[base + S_ROTATION];
575
+ const halfZ = rotZ * 0.5;
576
+ const qi = particleIndex * 4;
577
+ attributes.quat.array[qi] = 0;
578
+ attributes.quat.array[qi + 1] = 0;
579
+ attributes.quat.array[qi + 2] = Math.sin(halfZ);
580
+ attributes.quat.array[qi + 3] = Math.cos(halfZ);
581
+ if (updateFlags) updateFlags.quat = true;
582
+ else attributes.quat.needsUpdate = true;
583
+ }
584
+ };
585
+
586
+ // src/js/effects/three-particles/three-particles-renderer-detect.ts
587
+ function isComputeCapableRenderer(renderer) {
588
+ return renderer !== null && renderer !== void 0 && typeof renderer === "object" && "compute" in renderer && typeof renderer.compute === "function" && "hasFeature" in renderer && typeof renderer.hasFeature === "function";
589
+ }
590
+ function resolveSimulationBackend(renderer, preference = "AUTO" /* AUTO */) {
591
+ const gpuCapable = isComputeCapableRenderer(renderer);
592
+ if (preference === "CPU" /* CPU */) {
593
+ return "CPU" /* CPU */;
594
+ }
595
+ if (preference === "GPU" /* GPU */) {
596
+ return gpuCapable ? "GPU" /* GPU */ : "CPU" /* CPU */;
597
+ }
598
+ return gpuCapable ? "GPU" /* GPU */ : "CPU" /* CPU */;
599
+ }
600
+ function resolveWebGPUEffectiveRendererType(requested) {
601
+ switch (requested) {
602
+ case "INSTANCED" /* INSTANCED */:
603
+ return "INSTANCED" /* INSTANCED */;
604
+ case "TRAIL" /* TRAIL */:
605
+ return "TRAIL" /* TRAIL */;
606
+ case "MESH" /* MESH */:
607
+ return "MESH" /* MESH */;
608
+ case "POINTS" /* POINTS */:
609
+ default:
610
+ return "POINTS" /* POINTS */;
611
+ }
612
+ }
613
+ var _particleSystemId = 0;
614
+ var createdParticleSystems = [];
615
+ var _tslMaterialFactory = null;
616
+ var _rendererBackendIsGPU = true;
617
+ var _cpuPreferenceWarned = false;
618
+ var _cpuPreferencePreferenceWarn = () => {
619
+ _cpuPreferenceWarned = true;
620
+ console.warn(
621
+ "three-particles: simulationBackend 'CPU' maps to the GPU kernel in 4.0.0 (GPU-only build)."
622
+ );
623
+ };
624
+ var registerTSLMaterialFactory = (factory, options) => {
625
+ if (options && "renderer" in options && !isComputeCapableRenderer(options.renderer)) {
626
+ console.warn(
627
+ "three-particles: registerTSLMaterialFactory skipped ??? the provided renderer does not support compute dispatches (expected THREE.WebGPURenderer). Particle systems will use the CPU/GLSL path."
628
+ );
629
+ return false;
630
+ }
631
+ _tslMaterialFactory = factory;
632
+ if (options && "renderer" in options) {
633
+ _rendererBackendIsGPU = !!options.renderer?.backend?.isWebGPUBackend;
634
+ } else {
635
+ _rendererBackendIsGPU = true;
636
+ }
637
+ return true;
638
+ };
639
+ new THREE3.Vector3();
640
+ new THREE3.Vector3();
641
+ new THREE3.Euler(0, 0, 0, "XYZ");
642
+ var _lastWorldPositionSnapshot = new THREE3.Vector3();
643
+ new THREE3.Vector3();
644
+ new THREE3.Vector3();
645
+ new THREE3.Quaternion();
646
+ var assertNamed = (cond, message) => {
647
+ if (!cond) {
648
+ throw new Error(`three-particles: ${message}`);
649
+ }
650
+ };
651
+ var normalizeVector2Value = (raw, fallback, label) => {
652
+ if (raw === void 0 || raw === null) {
653
+ return new THREE3.Vector2(fallback[0], fallback[1]);
654
+ }
655
+ if (raw instanceof THREE3.Vector2) return raw;
656
+ let n1;
657
+ let n2;
658
+ if (Array.isArray(raw)) {
659
+ n1 = Number(raw[0]);
660
+ n2 = Number(raw[1]);
661
+ } else if (typeof raw === "object") {
662
+ const o = raw;
663
+ n1 = o.x !== void 0 ? Number(o.x) : o.u !== void 0 ? Number(o.u) : void 0;
664
+ n2 = o.y !== void 0 ? Number(o.y) : o.v !== void 0 ? Number(o.v) : void 0;
665
+ }
666
+ assertNamed(
667
+ n1 !== void 0 && n2 !== void 0 && Number.isFinite(n1) && Number.isFinite(n2),
668
+ `${label} must be one of: Vector2, [x,y], [u,v], {x,y} or {u,v}`
669
+ );
670
+ return new THREE3.Vector2(n1, n2);
671
+ };
672
+ var normalizeTextureValue = (raw, label) => {
673
+ if (raw === void 0 || raw === null) return null;
674
+ assertNamed(
675
+ typeof raw === "object" && "image" in raw,
676
+ `${label} must be null or a texture object with .image (got ${String(raw)})`
677
+ );
678
+ return raw;
679
+ };
680
+ var normalizeDepthTextureValue = (raw, label) => {
681
+ if (raw === void 0 || raw === null) return null;
682
+ assertNamed(
683
+ typeof raw === "object" && "image" in raw,
684
+ `${label} must be a texture object with .image when set (got ${String(raw)})`
685
+ );
686
+ return raw;
687
+ };
688
+ var normalizeBackgroundToVector3 = (raw, label) => {
689
+ if (raw === void 0 || raw === null) return new THREE3.Vector3(1, 1, 1);
690
+ if (typeof raw === "number") {
691
+ const c = new THREE3.Color(raw);
692
+ return new THREE3.Vector3(c.r, c.g, c.b);
693
+ }
694
+ if (typeof raw === "string") {
695
+ const s = raw.trim();
696
+ const c = new THREE3.Color(s.startsWith("#") ? s : `#${s}`);
697
+ assertNamed(
698
+ Number.isFinite(c.r) && Number.isFinite(c.g) && Number.isFinite(c.b),
699
+ `${label} is not a valid hex color string`
700
+ );
701
+ return new THREE3.Vector3(c.r, c.g, c.b);
702
+ }
703
+ if (Array.isArray(raw)) {
704
+ const [r, g, b] = raw;
705
+ assertNamed(
706
+ Number.isFinite(r) && Number.isFinite(g) && Number.isFinite(b),
707
+ `${label} array must contain three finite numbers`
708
+ );
709
+ return new THREE3.Vector3(r, g, b);
710
+ }
711
+ const o = raw;
712
+ assertNamed(
713
+ Number.isFinite(Number(o.r)) && Number.isFinite(Number(o.g)) && Number.isFinite(Number(o.b)),
714
+ `${label} object must provide finite r/g/b`
715
+ );
716
+ return new THREE3.Vector3(Number(o.r), Number(o.g), Number(o.b));
717
+ };
718
+ new THREE3.Vector3();
719
+ new THREE3.Vector3();
720
+ new THREE3.Vector3();
721
+ new THREE3.Vector3();
722
+ new THREE3.Vector3();
723
+ new THREE3.Vector2();
724
+ var toVector3 = (v, fallback) => v ? new THREE3.Vector3(v.x ?? 0, v.y ?? 0, v.z ?? 0) : fallback.clone();
725
+ var normalizeForceFields = (rawForceFields) => (rawForceFields ?? []).map((ff) => ({
726
+ isActive: ff.isActive ?? true,
727
+ type: ff.type ?? "POINT" /* POINT */,
728
+ position: toVector3(ff.position, new THREE3.Vector3(0, 0, 0)),
729
+ direction: toVector3(ff.direction, new THREE3.Vector3(0, 1, 0)).normalize(),
730
+ strength: ff.strength ?? 1,
731
+ range: Math.max(0, ff.range ?? Infinity),
732
+ falloff: ff.falloff ?? "LINEAR" /* LINEAR */
733
+ }));
734
+ var normalizeCollisionPlanes = (rawPlanes) => (rawPlanes ?? []).map((cp) => ({
735
+ isActive: cp.isActive ?? true,
736
+ position: toVector3(cp.position, new THREE3.Vector3(0, 0, 0)),
737
+ normal: toVector3(cp.normal, new THREE3.Vector3(0, 1, 0)).normalize(),
738
+ mode: cp.mode ?? "KILL" /* KILL */,
739
+ dampen: Math.max(0, Math.min(1, cp.dampen ?? 0.5)),
740
+ lifetimeLoss: Math.max(0, Math.min(1, cp.lifetimeLoss ?? 0))
741
+ }));
742
+ var blendingMap = {
743
+ "THREE.NoBlending": THREE3.NoBlending,
744
+ "THREE.NormalBlending": THREE3.NormalBlending,
745
+ "THREE.AdditiveBlending": THREE3.AdditiveBlending,
746
+ "THREE.SubtractiveBlending": THREE3.SubtractiveBlending,
747
+ "THREE.MultiplyBlending": THREE3.MultiplyBlending
748
+ };
749
+ var toBlendingConstant = (v) => {
750
+ if (typeof v === "number") return v;
751
+ if (typeof v === "string") {
752
+ const key = v.startsWith("THREE.") ? v : `THREE.${v}`;
753
+ const mapped = blendingMap[key];
754
+ if (mapped !== void 0) return mapped;
755
+ }
756
+ return THREE3.NormalBlending;
757
+ };
758
+ var getDefaultParticleSystemConfig = () => JSON.parse(JSON.stringify(DEFAULT_PARTICLE_SYSTEM_CONFIG));
759
+ var DEFAULT_PARTICLE_SYSTEM_CONFIG = {
760
+ transform: {
761
+ position: new THREE3.Vector3(),
762
+ rotation: new THREE3.Vector3(),
763
+ scale: new THREE3.Vector3(1, 1, 1)
764
+ },
765
+ duration: 5,
766
+ looping: true,
767
+ startDelay: 0,
768
+ startLifetime: 5,
769
+ startSpeed: 1,
770
+ startSize: 1,
771
+ startOpacity: 1,
772
+ startRotation: 0,
773
+ startColor: {
774
+ min: { r: 1, g: 1, b: 1 },
775
+ max: { r: 1, g: 1, b: 1 }
776
+ },
777
+ gravity: 0,
778
+ simulationSpace: "LOCAL" /* LOCAL */,
779
+ simulationBackend: "AUTO" /* AUTO */,
780
+ maxParticles: 100,
781
+ emission: {
782
+ rateOverTime: 10,
783
+ rateOverDistance: 0,
784
+ bursts: []
785
+ },
786
+ shape: {
787
+ shape: "SPHERE" /* SPHERE */,
788
+ sphere: {
789
+ radius: 1,
790
+ radiusThickness: 1,
791
+ arc: 360
792
+ },
793
+ cone: {
794
+ angle: 25,
795
+ radius: 1,
796
+ radiusThickness: 1,
797
+ arc: 360
798
+ },
799
+ circle: {
800
+ radius: 1,
801
+ radiusThickness: 1,
802
+ arc: 360
803
+ },
804
+ rectangle: {
805
+ rotation: { x: 0, y: 0, z: 0 },
806
+ scale: { x: 1, y: 1 }
807
+ },
808
+ box: {
809
+ scale: { x: 1, y: 1, z: 1 },
810
+ emitFrom: "VOLUME" /* VOLUME */
811
+ }
812
+ },
813
+ map: void 0,
814
+ renderer: {
815
+ blending: THREE3.NormalBlending,
816
+ discardBackgroundColor: false,
817
+ backgroundColorTolerance: 1,
818
+ backgroundColor: { r: 1, g: 1, b: 1 },
819
+ transparent: true,
820
+ depthTest: true,
821
+ depthWrite: false,
822
+ softParticles: {
823
+ enabled: false,
824
+ intensity: 1
825
+ }
826
+ },
827
+ velocityOverLifetime: {
828
+ isActive: false,
829
+ linear: {
830
+ x: 0,
831
+ y: 0,
832
+ z: 0
833
+ },
834
+ orbital: {
835
+ x: 0,
836
+ y: 0,
837
+ z: 0
838
+ }
839
+ },
840
+ sizeOverLifetime: {
841
+ isActive: false,
842
+ lifetimeCurve: {
843
+ type: "BEZIER" /* BEZIER */,
844
+ scale: 1,
845
+ bezierPoints: [
846
+ { x: 0, y: 0, percentage: 0 },
847
+ { x: 1, y: 1, percentage: 1 }
848
+ ]
849
+ }
850
+ },
851
+ colorOverLifetime: {
852
+ isActive: false,
853
+ r: {
854
+ type: "BEZIER" /* BEZIER */,
855
+ scale: 1,
856
+ bezierPoints: [
857
+ { x: 0, y: 1, percentage: 0 },
858
+ { x: 1, y: 1, percentage: 1 }
859
+ ]
860
+ },
861
+ g: {
862
+ type: "BEZIER" /* BEZIER */,
863
+ scale: 1,
864
+ bezierPoints: [
865
+ { x: 0, y: 1, percentage: 0 },
866
+ { x: 1, y: 1, percentage: 1 }
867
+ ]
868
+ },
869
+ b: {
870
+ type: "BEZIER" /* BEZIER */,
871
+ scale: 1,
872
+ bezierPoints: [
873
+ { x: 0, y: 1, percentage: 0 },
874
+ { x: 1, y: 1, percentage: 1 }
875
+ ]
876
+ }
877
+ },
878
+ opacityOverLifetime: {
879
+ isActive: false,
880
+ lifetimeCurve: {
881
+ type: "BEZIER" /* BEZIER */,
882
+ scale: 1,
883
+ bezierPoints: [
884
+ { x: 0, y: 0, percentage: 0 },
885
+ { x: 1, y: 1, percentage: 1 }
886
+ ]
887
+ }
888
+ },
889
+ rotationOverLifetime: {
890
+ isActive: false,
891
+ min: 0,
892
+ max: 0
893
+ },
894
+ noise: {
895
+ isActive: false,
896
+ useRandomOffset: false,
897
+ strength: 1,
898
+ frequency: 0.5,
899
+ octaves: 1,
900
+ positionAmount: 1,
901
+ rotationAmount: 0,
902
+ sizeAmount: 0
903
+ },
904
+ textureSheetAnimation: {
905
+ tiles: new THREE3.Vector2(1, 1),
906
+ timeMode: "LIFETIME" /* LIFETIME */,
907
+ fps: 30,
908
+ startFrame: 0
909
+ },
910
+ forceFields: [],
911
+ collisionPlanes: []
912
+ };
913
+ var destroyParticleSystem = (particleSystem) => {
914
+ createdParticleSystems = createdParticleSystems.filter(
915
+ ({
916
+ particleSystem: savedParticleSystem,
917
+ trailMesh,
918
+ generalData: { particleSystemId }
919
+ }) => {
920
+ if (savedParticleSystem !== particleSystem) {
921
+ return true;
922
+ }
923
+ removeBezierCurveFunction(particleSystemId);
924
+ if (trailMesh) {
925
+ trailMesh.geometry.dispose();
926
+ if (Array.isArray(trailMesh.material))
927
+ trailMesh.material.forEach((m) => m.dispose());
928
+ else trailMesh.material.dispose();
929
+ if (trailMesh.parent) trailMesh.parent.remove(trailMesh);
930
+ }
931
+ savedParticleSystem.geometry.dispose();
932
+ if (Array.isArray(savedParticleSystem.material))
933
+ savedParticleSystem.material.forEach((material) => material.dispose());
934
+ else savedParticleSystem.material.dispose();
935
+ if (savedParticleSystem.parent)
936
+ savedParticleSystem.parent.remove(savedParticleSystem);
937
+ return false;
938
+ }
939
+ );
940
+ };
941
+ var _defaultTexture = null;
942
+ var getDefaultTexture = () => {
943
+ if (_defaultTexture) return _defaultTexture;
944
+ if (typeof document === "undefined") return null;
945
+ const canvas = document.createElement("canvas");
946
+ canvas.width = 1;
947
+ canvas.height = 1;
948
+ const ctx = canvas.getContext("2d");
949
+ if (ctx) {
950
+ ctx.fillStyle = "#ffffff";
951
+ ctx.fillRect(0, 0, 1, 1);
952
+ }
953
+ _defaultTexture = new THREE3.Texture(canvas);
954
+ _defaultTexture.needsUpdate = true;
955
+ return _defaultTexture;
956
+ };
957
+ var createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow) => {
958
+ const now = externalNow || Date.now();
959
+ const useTSL = _tslMaterialFactory !== null;
960
+ if (!useTSL) {
961
+ throw new Error(
962
+ "three-particles: WebGPU TSL material factory not registered. Call enableWebGPU(renderer) immediately after creating a WebGPURenderer. @cyberluke/three-particles 4.0.0 is GPU-only - no CPU fallback path exists."
963
+ );
964
+ }
965
+ if (!_rendererBackendIsGPU) {
966
+ throw new Error(
967
+ "three-particles: renderer is not a native WebGPU backend. This build has no WebGL2 fallback. Use a new THREE.WebGPURenderer()."
968
+ );
969
+ }
970
+ const factory = _tslMaterialFactory;
971
+ if (!factory.createComputePipeline) {
972
+ throw new Error(
973
+ "three-particles: active WebGPU renderer does not provide a complete TSL compute pipeline (createComputePipeline missing). No CPU fallback exists; install a WebGPU-capable backend."
974
+ );
975
+ }
976
+ const maxParticles = config.maxParticles || DEFAULT_PARTICLE_SYSTEM_CONFIG.maxParticles;
977
+ const normalizedConfig = ObjectUtils.deepMerge(
978
+ DEFAULT_PARTICLE_SYSTEM_CONFIG,
979
+ config,
980
+ { applyToFirstObject: false, skippedProperties: [] }
981
+ );
982
+ if (normalizedConfig.simulationBackend === "CPU") {
983
+ if (!_cpuPreferenceWarned) {
984
+ _cpuPreferencePreferenceWarn();
985
+ }
986
+ normalizedConfig.simulationBackend = "GPU" /* GPU */;
987
+ }
988
+ const requestedRendererType = normalizedConfig.renderer.rendererType || "POINTS" /* POINTS */;
989
+ const effectiveRendererType = resolveWebGPUEffectiveRendererType(
990
+ requestedRendererType
991
+ );
992
+ const rrType = effectiveRendererType;
993
+ const useInstancing = effectiveRendererType === "INSTANCED" /* INSTANCED */ || effectiveRendererType === "MESH" /* MESH */;
994
+ const trailConfig = normalizedConfig.renderer.trail;
995
+ const trailLength = Math.max(2, Math.round(trailConfig?.length ?? 20));
996
+ const trailHistoryAttribute = rrType === "TRAIL" /* TRAIL */ ? new StorageBufferAttribute(
997
+ new Float32Array(maxParticles * (trailLength + 1) * 4),
998
+ 4
999
+ ) : null;
1000
+ const trailDesc = trailHistoryAttribute ? {
1001
+ attribute: trailHistoryAttribute,
1002
+ meta: null,
1003
+ length: trailLength,
1004
+ minVertexDistance: trailConfig?.minVertexDistance ?? 0,
1005
+ maxTime: (trailConfig?.maxTime ?? 0) * 1e3
1006
+ } : null;
1007
+ const subEmitterConfigs = normalizedConfig.subEmitters ?? [];
1008
+ const fifos = subEmitterConfigs.map((se) => {
1009
+ const capacity = Math.max(1, Math.round(se.maxInstances ?? 32));
1010
+ const f = factory.createSubEmitterFifoAttribute(capacity);
1011
+ f.trigger = se.trigger === "BIRTH" ? 0 : 1;
1012
+ return f;
1013
+ });
1014
+ const fifoBaseStride = fifos.reduce((m, f) => Math.max(m, f.windowSize), 0);
1015
+ const forceFields = normalizeForceFields(normalizedConfig.forceFields);
1016
+ const collisionPlanes = normalizeCollisionPlanes(normalizedConfig.collisionPlanes);
1017
+ const pipeline = factory.createComputePipeline(
1018
+ maxParticles,
1019
+ useInstancing,
1020
+ normalizedConfig,
1021
+ _particleSystemId,
1022
+ // pre-increment inside generalData below would be off by 1; use the raw next id
1023
+ forceFields.length,
1024
+ collisionPlanes.length,
1025
+ fifos,
1026
+ trailDesc ?? void 0
1027
+ );
1028
+ const ribbonPipeline = trailDesc ? factory.createTrailRibbonUpdate({
1029
+ position: new StorageBufferAttribute(
1030
+ new Float32Array(maxParticles * trailLength * 2 * 4),
1031
+ 4
1032
+ ),
1033
+ next: new StorageBufferAttribute(
1034
+ new Float32Array(maxParticles * trailLength * 2 * 4),
1035
+ 4
1036
+ ),
1037
+ uvColorA: new StorageBufferAttribute(
1038
+ new Float32Array(maxParticles * trailLength * 2 * 4),
1039
+ 4
1040
+ ),
1041
+ colorB: new StorageBufferAttribute(
1042
+ new Float32Array(maxParticles * trailLength * 2 * 4),
1043
+ 4
1044
+ ),
1045
+ history: trailDesc.attribute,
1046
+ meta: trailDesc.meta,
1047
+ particleColor: pipeline.buffers.color,
1048
+ curveFns: {
1049
+ width: trailConfig?.widthOverTrail ? getCurveFunctionFromConfig(_particleSystemId, trailConfig.widthOverTrail) : void 0,
1050
+ opacity: trailConfig?.opacityOverTrail ? getCurveFunctionFromConfig(_particleSystemId, trailConfig.opacityOverTrail) : void 0,
1051
+ colorR: trailConfig?.colorOverTrail?.isActive ? getCurveFunctionFromConfig(_particleSystemId, trailConfig.colorOverTrail.r) : void 0,
1052
+ colorG: trailConfig?.colorOverTrail?.isActive ? getCurveFunctionFromConfig(_particleSystemId, trailConfig.colorOverTrail.g) : void 0,
1053
+ colorB: trailConfig?.colorOverTrail?.isActive ? getCurveFunctionFromConfig(_particleSystemId, trailConfig.colorOverTrail.b) : void 0
1054
+ },
1055
+ width: trailConfig?.width ?? 1,
1056
+ length: trailLength,
1057
+ maxTime: trailDesc.maxTime,
1058
+ maxParticles
1059
+ }) : null;
1060
+ const subEntries = [];
1061
+ for (let fi = 0; fi < subEmitterConfigs.length; fi++) {
1062
+ const se = subEmitterConfigs[fi];
1063
+ const fifo = fifos[fi];
1064
+ const childCfg = ObjectUtils.deepMerge(
1065
+ getDefaultParticleSystemConfig(),
1066
+ se.config ?? {},
1067
+ { applyToFirstObject: false, skippedProperties: [] }
1068
+ );
1069
+ const firstBurst = childCfg.emission?.bursts?.[0];
1070
+ const burstCount = firstBurst ? Math.max(
1071
+ 1,
1072
+ Math.ceil(
1073
+ calculateValue(
1074
+ _particleSystemId + 1 + fi,
1075
+ firstBurst.count,
1076
+ 0
1077
+ ) * (firstBurst.cycles ?? 1)
1078
+ )
1079
+ ) : 1;
1080
+ const perEvent = Math.min(burstCount, fifo.capacity);
1081
+ const childMax = Math.max(2, Math.min(perEvent * fifo.capacity, 65536));
1082
+ const childRequestedRendererType = childCfg.renderer?.rendererType;
1083
+ const childEffectiveRendererType = resolveWebGPUEffectiveRendererType(
1084
+ childRequestedRendererType
1085
+ );
1086
+ const childInstanced = childEffectiveRendererType === "INSTANCED" /* INSTANCED */ || childEffectiveRendererType === "MESH" /* MESH */;
1087
+ const childPipeline = factory.createComputePipeline(
1088
+ childMax,
1089
+ childInstanced,
1090
+ childCfg,
1091
+ _particleSystemId + 1 + fi,
1092
+ 0,
1093
+ 0,
1094
+ [],
1095
+ void 0
1096
+ );
1097
+ const childShapeParams = factory.encodeShapeEmitParams(
1098
+ childCfg,
1099
+ _particleSystemId + 1 + fi
1100
+ );
1101
+ const childVel = childCfg.velocityOverLifetime;
1102
+ const init = factory.createSubEmitterInitUpdate(
1103
+ childPipeline.buffers,
1104
+ childMax,
1105
+ childShapeParams,
1106
+ pipeline.buffers,
1107
+ maxParticles,
1108
+ fifo,
1109
+ se.inheritVelocity ?? 0,
1110
+ perEvent,
1111
+ {
1112
+ linear: [childVel?.linear?.x, childVel?.linear?.y, childVel?.linear?.z],
1113
+ orbital: [childVel?.orbital?.x, childVel?.orbital?.y, childVel?.orbital?.z]
1114
+ }
1115
+ );
1116
+ subEntries.push({
1117
+ fifo,
1118
+ pipeline: childPipeline,
1119
+ init,
1120
+ instanced: childInstanced,
1121
+ requestedRendererType: childRequestedRendererType,
1122
+ effectiveRendererType: childEffectiveRendererType,
1123
+ cfg: childCfg,
1124
+ object: null,
1125
+ perEvent,
1126
+ gravity: childCfg.gravity,
1127
+ noise: childCfg.noise?.isActive ? {
1128
+ isActive: true,
1129
+ strength: childCfg.noise.strength,
1130
+ noisePower: 0.15 * childCfg.noise.strength,
1131
+ frequency: childCfg.noise.frequency,
1132
+ positionAmount: childCfg.noise.positionAmount,
1133
+ rotationAmount: childCfg.noise.rotationAmount,
1134
+ sizeAmount: childCfg.noise.sizeAmount,
1135
+ fbmMax: 2 - Math.pow(2, -childCfg.noise.octaves)
1136
+ } : null,
1137
+ rate: childCfg.emission?.rateOverTime ? calculateValue(_particleSystemId + 1 + fi, childCfg.emission.rateOverTime, 0) : 0,
1138
+ acc: 0,
1139
+ lastEmit: 0,
1140
+ poseFrom: "self",
1141
+ selfPose: { x: 0, y: 0, z: 0, qx: 0, qy: 0, qz: 0, qw: 1, sx: 1, sy: 1, sz: 1, isWorld: childCfg.simulationSpace === "WORLD" /* WORLD */ ? 1 : 0 }
1142
+ });
1143
+ }
1144
+ const cameraNearFarSource = normalizedConfig.renderer.cameraNearFar;
1145
+ const tilesSource = normalizedConfig.textureSheetAnimation?.tiles;
1146
+ const elapsedUniform = { value: 0 };
1147
+ const sharedUniforms = {
1148
+ elapsed: elapsedUniform,
1149
+ viewportHeight: { value: 720 },
1150
+ cameraNearFar: {
1151
+ value: normalizeVector2Value(
1152
+ cameraNearFarSource,
1153
+ [0.1, 1e3],
1154
+ "renderer.cameraNearFar"
1155
+ )
1156
+ },
1157
+ useInstancing: { value: useInstancing },
1158
+ softParticlesEnabled: { value: !!normalizedConfig.renderer.softParticles?.enabled },
1159
+ softParticlesIntensity: {
1160
+ value: Math.max(normalizedConfig.renderer.softParticles?.intensity ?? 1, 1e-3)
1161
+ },
1162
+ sceneDepthTexture: {
1163
+ value: normalizeDepthTextureValue(
1164
+ normalizedConfig.renderer.softParticles?.depthTexture,
1165
+ "renderer.softParticles.depthTexture"
1166
+ )
1167
+ },
1168
+ discardBackgroundColor: { value: !!normalizedConfig.renderer.discardBackgroundColor },
1169
+ backgroundColor: { value: new THREE3.Color(16777215) },
1170
+ backgroundColorTolerance: { value: normalizedConfig.renderer.backgroundColorTolerance ?? 0 },
1171
+ map: {
1172
+ value: normalizeTextureValue(
1173
+ normalizedConfig.map ?? getDefaultTexture(),
1174
+ "map"
1175
+ )
1176
+ },
1177
+ startLifetime: { value: 0 },
1178
+ startSize: { value: 1 },
1179
+ startRotation: { value: 0 },
1180
+ startOpacity: { value: 1 },
1181
+ startColor: { value: new THREE3.Color(1, 1, 1) },
1182
+ lifetime: { value: 0 },
1183
+ color: { value: new THREE3.Color(1, 1, 1) },
1184
+ // Sprite-sheet animation fields consumed by tsl-shared.createParticleUniforms.
1185
+ fps: { value: normalizedConfig.textureSheetAnimation?.fps || 30 },
1186
+ useFPSForFrameIndex: {
1187
+ value: normalizedConfig.textureSheetAnimation?.timeMode === "FPS" /* FPS */
1188
+ },
1189
+ tiles: {
1190
+ // The ONLY normalizer: `tiles` reaches the TSL factory as a Vector2
1191
+ // (also {u,v} pairs are accepted per §10). The engine's own default is
1192
+ // already (1,1) via the merged default config.
1193
+ value: normalizeVector2Value(
1194
+ tilesSource,
1195
+ [1, 1],
1196
+ "textureSheetAnimation.tiles"
1197
+ )
1198
+ }
1199
+ };
1200
+ const bgVec = normalizeBackgroundToVector3(
1201
+ normalizedConfig.renderer.backgroundColor,
1202
+ "renderer.backgroundColor"
1203
+ );
1204
+ sharedUniforms.backgroundColor.value.setRGB(bgVec.x, bgVec.y, bgVec.z);
1205
+ const rendererConfig = {
1206
+ transparent: !!normalizedConfig.renderer.transparent,
1207
+ blending: toBlendingConstant(normalizedConfig.renderer.blending),
1208
+ depthTest: normalizedConfig.renderer.depthTest !== false,
1209
+ depthWrite: normalizedConfig.renderer.depthWrite !== false
1210
+ };
1211
+ const material = factory.createTSLParticleMaterial(
1212
+ rrType,
1213
+ sharedUniforms,
1214
+ rendererConfig,
1215
+ true
1216
+ );
1217
+ const buffers = pipeline.buffers;
1218
+ let geometry;
1219
+ if (useInstancing) {
1220
+ const g = new THREE3.InstancedBufferGeometry();
1221
+ const meshGeometry = normalizedConfig.renderer.mesh?.geometry;
1222
+ const baseGeometry = rrType === "MESH" /* MESH */ && meshGeometry ? meshGeometry : new THREE3.BufferGeometry();
1223
+ if (rrType !== "MESH" /* MESH */ || !meshGeometry) {
1224
+ const quad = new Float32Array([-0.5, -0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, 0.5, 0]);
1225
+ const quadUV = new Float32Array([0, 0, 1, 0, 1, 1, 0, 1]);
1226
+ const quadNormal = new Float32Array([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]);
1227
+ const idx = new Uint16Array([0, 1, 2, 0, 2, 3]);
1228
+ baseGeometry.setAttribute("position", new THREE3.BufferAttribute(quad, 3));
1229
+ baseGeometry.setAttribute("uv", new THREE3.BufferAttribute(quadUV, 2));
1230
+ baseGeometry.setAttribute("normal", new THREE3.BufferAttribute(quadNormal, 3));
1231
+ baseGeometry.setIndex(new THREE3.BufferAttribute(idx, 1));
1232
+ }
1233
+ g.setAttribute("position", baseGeometry.getAttribute("position"));
1234
+ if (baseGeometry.index !== null) g.setIndex(baseGeometry.index);
1235
+ g.instanceCount = maxParticles;
1236
+ g.setAttribute("instanceOffset", buffers.position);
1237
+ g.setAttribute("instanceColor", buffers.color);
1238
+ g.setAttribute("instanceParticleState", buffers.particleState);
1239
+ g.setAttribute("instanceStartValues", buffers.startValues);
1240
+ geometry = g;
1241
+ } else {
1242
+ const g = new THREE3.BufferGeometry();
1243
+ g.setAttribute("position", buffers.position);
1244
+ g.setAttribute("color", buffers.color);
1245
+ g.setAttribute("particleState", buffers.particleState);
1246
+ g.setAttribute("startValues", buffers.startValues);
1247
+ g.setDrawRange(0, maxParticles);
1248
+ geometry = g;
1249
+ g.instanceCount = maxParticles;
1250
+ }
1251
+ let trailGeometry = null;
1252
+ if (ribbonPipeline && trailDesc) {
1253
+ const rb = ribbonPipeline.buffers;
1254
+ const g = new THREE3.BufferGeometry();
1255
+ g.setAttribute("position", rb.position);
1256
+ g.setAttribute("trailNext", rb.next);
1257
+ g.setAttribute("trailUVColor", rb.uvColorA);
1258
+ g.setAttribute("trailColorBA", rb.colorB);
1259
+ const idx = new Uint32Array(maxParticles * (trailLength - 1) * 6);
1260
+ let o = 0;
1261
+ for (let pIdx = 0; pIdx < maxParticles; pIdx++) {
1262
+ for (let s = 0; s < trailLength - 1; s++) {
1263
+ const b = pIdx * trailLength * 2 + s * 2;
1264
+ idx[o++] = b;
1265
+ idx[o++] = b + 1;
1266
+ idx[o++] = b + 2;
1267
+ idx[o++] = b + 1;
1268
+ idx[o++] = b + 3;
1269
+ idx[o++] = b + 2;
1270
+ }
1271
+ }
1272
+ g.setIndex(new THREE3.BufferAttribute(idx, 1));
1273
+ g.setDrawRange(0, maxParticles * trailLength * 2);
1274
+ trailGeometry = g;
1275
+ }
1276
+ const trailMaterial = trailGeometry ? factory.createTSLTrailMaterial(
1277
+ {
1278
+ map: { value: normalizedConfig.map ?? getDefaultTexture() },
1279
+ useMap: { value: !!normalizedConfig.map },
1280
+ discardBackgroundColor: { value: !!normalizedConfig.renderer.discardBackgroundColor },
1281
+ backgroundColor: { value: normalizedConfig.renderer.backgroundColor ?? { r: 1, g: 1, b: 1 } },
1282
+ backgroundColorTolerance: { value: normalizedConfig.renderer.backgroundColorTolerance ?? 0 },
1283
+ softParticlesEnabled: { value: !!normalizedConfig.renderer.softParticles?.enabled },
1284
+ softParticlesIntensity: {
1285
+ value: Math.max(normalizedConfig.renderer.softParticles?.intensity ?? 1, 1e-3)
1286
+ },
1287
+ sceneDepthTexture: {
1288
+ value: normalizedConfig.renderer.softParticles?.depthTexture ?? null
1289
+ },
1290
+ cameraNearFar: { value: new THREE3.Vector2(0.1, 1e3) }
1291
+ },
1292
+ {
1293
+ transparent: !!normalizedConfig.renderer.transparent,
1294
+ blending: toBlendingConstant(normalizedConfig.renderer.blending),
1295
+ depthTest: normalizedConfig.renderer.depthTest !== false,
1296
+ depthWrite: normalizedConfig.renderer.depthWrite !== false
1297
+ }
1298
+ ) : null;
1299
+ const particleSystem = trailGeometry ? new THREE3.Mesh(trailGeometry, trailMaterial) : useInstancing ? new THREE3.Mesh(geometry, material) : new THREE3.Points(geometry, material);
1300
+ particleSystem.frustumCulled = false;
1301
+ for (const e of subEntries) {
1302
+ const cb = e.pipeline.buffers;
1303
+ const childMax = e.pipeline.allocatorCount - 1;
1304
+ const childGeometry = e.instanced ? (() => {
1305
+ const g = new THREE3.InstancedBufferGeometry();
1306
+ const quad = new Float32Array([-0.5, -0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0, -0.5, 0.5, 0]);
1307
+ const idx = new Uint16Array([0, 1, 2, 0, 2, 3]);
1308
+ g.setAttribute("position", new THREE3.BufferAttribute(quad, 3));
1309
+ g.setIndex(new THREE3.BufferAttribute(idx, 1));
1310
+ g.instanceCount = childMax;
1311
+ g.setAttribute("instanceOffset", cb.position);
1312
+ g.setAttribute("instanceColor", cb.color);
1313
+ g.setAttribute("instanceParticleState", cb.particleState);
1314
+ g.setAttribute("instanceStartValues", cb.startValues);
1315
+ return g;
1316
+ })() : (() => {
1317
+ const g = new THREE3.BufferGeometry();
1318
+ g.setAttribute("position", cb.position);
1319
+ g.setAttribute("color", cb.color);
1320
+ g.setAttribute("particleState", cb.particleState);
1321
+ g.setAttribute("startValues", cb.startValues);
1322
+ g.setDrawRange(0, childMax);
1323
+ return g;
1324
+ })();
1325
+ const childUniforms = {
1326
+ ...sharedUniforms,
1327
+ useInstancing: { value: e.instanced }
1328
+ };
1329
+ const childMaterial = factory.createTSLParticleMaterial(
1330
+ e.effectiveRendererType,
1331
+ childUniforms,
1332
+ rendererConfig,
1333
+ true
1334
+ );
1335
+ const childObject = e.instanced ? new THREE3.Mesh(childGeometry, childMaterial) : new THREE3.Points(childGeometry, childMaterial);
1336
+ childObject.frustumCulled = false;
1337
+ particleSystem.add(childObject);
1338
+ e.object = childObject;
1339
+ }
1340
+ if (import.meta.env?.DEV !== false) {
1341
+ const required = useInstancing ? [
1342
+ "position",
1343
+ // quad / mesh vertex positions
1344
+ "instanceOffset",
1345
+ // GPU particle position
1346
+ "instanceColor",
1347
+ // GPU particle RGBA
1348
+ "instanceParticleState",
1349
+ // GPU packed state vec4
1350
+ "instanceStartValues"
1351
+ // GPU packed initial-state vec4
1352
+ ] : ["position", "color", "particleState", "startValues"];
1353
+ for (const name of required) {
1354
+ if (!geometry.getAttribute(name)) {
1355
+ throw new Error(
1356
+ "three-particles: " + (useInstancing ? "instanced" : "POINTS") + " geometry " + name + " is missing its required contract attribute."
1357
+ );
1358
+ }
1359
+ }
1360
+ const contractIdentity = useInstancing ? [
1361
+ ["instanceOffset", buffers.position],
1362
+ ["instanceColor", buffers.color],
1363
+ ["instanceParticleState", buffers.particleState],
1364
+ ["instanceStartValues", buffers.startValues]
1365
+ ] : [
1366
+ ["position", buffers.position],
1367
+ ["color", buffers.color],
1368
+ ["particleState", buffers.particleState],
1369
+ ["startValues", buffers.startValues]
1370
+ ];
1371
+ for (const [name, buf] of contractIdentity) {
1372
+ if (geometry.getAttribute(name) !== buf) {
1373
+ throw new Error(
1374
+ `three-particles: attribute "${name}" is not the compute-owned storage buffer.`
1375
+ );
1376
+ }
1377
+ }
1378
+ const kind = pipeline.shapeUniforms.shapeKind.value;
1379
+ if (!(kind >= 0 && kind <= 4)) {
1380
+ throw new Error(
1381
+ `three-particles: gpuShapeKind ${kind} outside 0..4 (SPHERE..BOX).`
1382
+ );
1383
+ }
1384
+ if (!(maxParticles > 0)) {
1385
+ throw new Error("three-particles: maxParticles must be > 0.");
1386
+ }
1387
+ if (pipeline.allocatorCount !== maxParticles + 1) {
1388
+ throw new Error(
1389
+ "three-particles: allocator capacity must equal maxParticles + 1."
1390
+ );
1391
+ }
1392
+ const passLayouts = [
1393
+ ...pipeline.passLayouts ?? [],
1394
+ ...ribbonPipeline?.passLayouts ?? [],
1395
+ ...subEntries.flatMap((e) => [
1396
+ ...e.init.passLayouts ?? [],
1397
+ ...(e.pipeline.passLayouts ?? []).map(
1398
+ (p) => ({ ...p, name: `child:${p.name}` })
1399
+ )
1400
+ ])
1401
+ ];
1402
+ for (const pass of passLayouts) {
1403
+ if (pass.storageBindings > 8) {
1404
+ throw new Error(
1405
+ `${pass.name}: ${pass.storageBindings} storage buffers > guaranteed limit 8`
1406
+ );
1407
+ }
1408
+ }
1409
+ if (trailDesc && trailDesc.meta !== pipeline.trailMeta) {
1410
+ throw new Error("three-particles: trail ring meta buffer mismatch.");
1411
+ }
1412
+ for (const f of fifos) {
1413
+ const n = f.counter.array.length;
1414
+ if (n !== 2) {
1415
+ throw new Error(
1416
+ "three-particles: sub-emitter FIFO must expose exactly 2 ping-pong counter slots."
1417
+ );
1418
+ }
1419
+ const p = f.payload.array.length;
1420
+ if (p !== 2 * 6 * f.capacity) {
1421
+ throw new Error(
1422
+ "three-particles: sub-emitter FIFO payload length must be 2 * 6 * capacity."
1423
+ );
1424
+ }
1425
+ }
1426
+ }
1427
+ const _numOr = (v, d) => typeof v === "number" && Number.isFinite(v) ? v : d;
1428
+ const xform = normalizedConfig.transform;
1429
+ if (xform?.position) {
1430
+ particleSystem.position.set(
1431
+ _numOr(xform.position.x, 0),
1432
+ _numOr(xform.position.y, 0),
1433
+ _numOr(xform.position.z, 0)
1434
+ );
1435
+ }
1436
+ if (xform?.rotation) {
1437
+ particleSystem.rotation.set(
1438
+ THREE3.MathUtils.degToRad(_numOr(xform.rotation.x, 0)),
1439
+ THREE3.MathUtils.degToRad(_numOr(xform.rotation.y, 0)),
1440
+ THREE3.MathUtils.degToRad(_numOr(xform.rotation.z, 0))
1441
+ );
1442
+ }
1443
+ if (xform?.scale) {
1444
+ particleSystem.scale.set(
1445
+ _numOr(xform.scale.x, 1),
1446
+ _numOr(xform.scale.y, 1),
1447
+ _numOr(xform.scale.z, 1)
1448
+ );
1449
+ }
1450
+ particleSystem.updateMatrix();
1451
+ particleSystem.updateMatrixWorld(true);
1452
+ if (normalizedConfig.simulationSpace === "WORLD" /* WORLD */) {
1453
+ particleSystem.matrixWorldAutoUpdate = false;
1454
+ particleSystem.matrixWorld.identity();
1455
+ }
1456
+ const generalData = {
1457
+ particleSystemId: _particleSystemId++,
1458
+ normalizedLifetimePercentage: 0,
1459
+ distanceFromLastEmitByDistance: 0,
1460
+ lastWorldPosition: new THREE3.Vector3(-99999),
1461
+ currentWorldPosition: new THREE3.Vector3(-99999),
1462
+ worldPositionChange: new THREE3.Vector3(),
1463
+ sourceWorldMatrix: new THREE3.Matrix4(),
1464
+ worldQuaternion: new THREE3.Quaternion(),
1465
+ wrapperQuaternion: new THREE3.Quaternion(),
1466
+ worldScale: new THREE3.Vector3(1, 1, 1),
1467
+ worldEuler: new THREE3.Euler(),
1468
+ gravityVelocity: new THREE3.Vector3(0, 0, 0),
1469
+ startValues: {},
1470
+ linearVelocityData: void 0,
1471
+ orbitalVelocityData: void 0,
1472
+ lifetimeValues: {},
1473
+ creationTimes: new Float32Array(0),
1474
+ cpuDirtyParticleWatermark: -1,
1475
+ highWaterIndex: 0,
1476
+ noise: {
1477
+ isActive: normalizedConfig.noise.isActive,
1478
+ strength: normalizedConfig.noise.strength,
1479
+ // Oracle `0.15 * strength`; the single fbmMax division lives inside the
1480
+ // FBM sum (CPU: FBM.get3; GPU: the octave loop amp / fbmMax).
1481
+ noisePower: 0.15 * normalizedConfig.noise.strength,
1482
+ frequency: normalizedConfig.noise.frequency,
1483
+ positionAmount: normalizedConfig.noise.positionAmount,
1484
+ rotationAmount: normalizedConfig.noise.rotationAmount,
1485
+ sizeAmount: normalizedConfig.noise.sizeAmount,
1486
+ fbmMax: 2 - Math.pow(2, -normalizedConfig.noise.octaves)
1487
+ },
1488
+ isEnabled: true,
1489
+ burstStates: normalizedConfig.emission.bursts?.length ? normalizedConfig.emission.bursts.map(() => ({
1490
+ cyclesExecuted: 0,
1491
+ lastCycleTime: 0,
1492
+ probabilityPassed: false
1493
+ })) : void 0
1494
+ };
1495
+ const props = {
1496
+ particleSystem,
1497
+ mappedAttributes: {
1498
+ position: buffers.position,
1499
+ isActive: buffers.orbitalIsActive,
1500
+ lifetime: buffers.particleState,
1501
+ startLifetime: buffers.startValues,
1502
+ startFrame: buffers.particleState,
1503
+ size: buffers.particleState,
1504
+ rotation: buffers.particleState,
1505
+ color: buffers.color
1506
+ },
1507
+ // ?? Deprecated zero-size sentinels (GPU-only v4) ????
1508
+ // These legacy CPU particle-state fields are not authoritative anymore: the
1509
+ // compute kernels own the state in GPU storage. Only the TRAIL path (which
1510
+ // throws in v4) consumed them, so they are 0-length placeholders.
1511
+ scalarArray: new Float32Array(0),
1512
+ scalarInterleavedBuffer: new THREE3.InterleavedBuffer(
1513
+ new Float32Array(0),
1514
+ SCALAR_STRIDE
1515
+ ),
1516
+ elapsedUniform,
1517
+ generalData,
1518
+ onUpdate: () => {
1519
+ },
1520
+ onComplete: () => {
1521
+ },
1522
+ creationTime: now + (normalizedConfig.startDelay || 0),
1523
+ lastEmissionTime: now,
1524
+ emissionAccumulator: 0,
1525
+ duration: normalizedConfig.duration,
1526
+ looping: normalizedConfig.looping,
1527
+ simulationSpace: normalizedConfig.simulationSpace,
1528
+ gravity: normalizedConfig.gravity,
1529
+ normalizedForceFields: forceFields,
1530
+ normalizedCollisionPlanes: collisionPlanes,
1531
+ emission: normalizedConfig.emission,
1532
+ normalizedConfig,
1533
+ iterationCount: 0,
1534
+ velocities: [],
1535
+ freeList: [],
1536
+ deactivateParticle: () => {
1537
+ },
1538
+ killParticle: () => {
1539
+ },
1540
+ activateParticle: () => {
1541
+ },
1542
+ computePipeline: pipeline,
1543
+ useGPUCompute: true,
1544
+ computeDispatchReady: false,
1545
+ maxParticles,
1546
+ material,
1547
+ geometry,
1548
+ rrType,
1549
+ requestedRendererType,
1550
+ effectiveRendererType: rrType,
1551
+ sharedUniforms,
1552
+ allComputeNodes: [
1553
+ ...pipeline.computeNodes ?? [],
1554
+ ...ribbonPipeline ? [ribbonPipeline.ribbonNode] : [],
1555
+ ...subEntries.flatMap((e) => [
1556
+ e.init.commandBuildNode,
1557
+ e.init.childInitNode,
1558
+ ...e.init.counterClearNode != null ? [e.init.counterClearNode] : [],
1559
+ ...e.pipeline.computeNodes ?? []
1560
+ ])
1561
+ ],
1562
+ passNames: [
1563
+ ...pipeline.passNames ?? ["emit", "simulate"],
1564
+ ...ribbonPipeline ? ["trail-ribbon"] : [],
1565
+ ...subEntries.flatMap((e, ei) => [
1566
+ `sub${ei}:command-build`,
1567
+ `sub${ei}:child-init`,
1568
+ `sub${ei}:counter-clear`,
1569
+ `sub${ei}:child-emit`,
1570
+ `sub${ei}:child-sim`
1571
+ ])
1572
+ ],
1573
+ fifoBaseStride,
1574
+ ribbonUniforms: ribbonPipeline ? ribbonPipeline.uniforms : void 0,
1575
+ ribbonBuffers: ribbonPipeline ? ribbonPipeline.buffers : void 0,
1576
+ frameParity: 0,
1577
+ subEntries: subEntries.map((e) => ({
1578
+ fifo: { capacity: e.fifo.capacity, windowSize: e.fifo.windowSize },
1579
+ requestedRendererType: e.requestedRendererType,
1580
+ effectiveRendererType: e.effectiveRendererType,
1581
+ pipeline: e.pipeline,
1582
+ init: e.init,
1583
+ gravity: e.gravity,
1584
+ noise: e.noise,
1585
+ rate: e.rate,
1586
+ acc: 0,
1587
+ isWorld: e.selfPose.isWorld,
1588
+ quat: [e.selfPose.qx, e.selfPose.qy, e.selfPose.qz, e.selfPose.qw],
1589
+ scale: [e.selfPose.sx, e.selfPose.sy, e.selfPose.sz],
1590
+ position: [
1591
+ _numOr(e.cfg.transform?.position?.x, 0),
1592
+ _numOr(e.cfg.transform?.position?.y, 0),
1593
+ _numOr(e.cfg.transform?.position?.z, 0)
1594
+ ]
1595
+ }))
1596
+ };
1597
+ for (const e of subEntries) {
1598
+ if (!e.object) continue;
1599
+ const tf = e.cfg.transform;
1600
+ if (tf?.position) {
1601
+ e.object.position.set(
1602
+ _numOr(tf.position.x, 0),
1603
+ _numOr(tf.position.y, 0),
1604
+ _numOr(tf.position.z, 0)
1605
+ );
1606
+ }
1607
+ if (tf?.rotation) {
1608
+ e.object.rotation.set(
1609
+ THREE3.MathUtils.degToRad(_numOr(tf.rotation.x, 0)),
1610
+ THREE3.MathUtils.degToRad(_numOr(tf.rotation.y, 0)),
1611
+ THREE3.MathUtils.degToRad(_numOr(tf.rotation.z, 0))
1612
+ );
1613
+ }
1614
+ if (tf?.scale) {
1615
+ e.object.scale.set(
1616
+ _numOr(tf.scale.x, 1),
1617
+ _numOr(tf.scale.y, 1),
1618
+ _numOr(tf.scale.z, 1)
1619
+ );
1620
+ }
1621
+ e.object.updateMatrix();
1622
+ const q = new THREE3.Quaternion().setFromEuler(
1623
+ new THREE3.Euler(
1624
+ THREE3.MathUtils.degToRad(_numOr(tf?.rotation?.x, 0)),
1625
+ THREE3.MathUtils.degToRad(_numOr(tf?.rotation?.y, 0)),
1626
+ THREE3.MathUtils.degToRad(_numOr(tf?.rotation?.z, 0)),
1627
+ "XYZ"
1628
+ )
1629
+ );
1630
+ const entry = props.subEntries?.[subEntries.indexOf(e)];
1631
+ if (entry) {
1632
+ entry.quat = [q.x, q.y, q.z, q.w];
1633
+ entry.scale = [
1634
+ _numOr(tf?.scale?.x, 1),
1635
+ _numOr(tf?.scale?.y, 1),
1636
+ _numOr(tf?.scale?.z, 1)
1637
+ ];
1638
+ }
1639
+ }
1640
+ createdParticleSystems.push(props);
1641
+ const _dbgPassCounts = [
1642
+ ...(pipeline.passLayouts ?? []).map(
1643
+ (p) => [p.name, p.storageBindings]
1644
+ ),
1645
+ ...(ribbonPipeline?.passLayouts ?? []).map(
1646
+ (p) => [p.name, p.storageBindings]
1647
+ ),
1648
+ ...subEntries.flatMap((e, ei) => [
1649
+ ...(e.init.passLayouts ?? []).map(
1650
+ (p) => [`sub${ei}:${p.name}`, p.storageBindings]
1651
+ ),
1652
+ ...(e.pipeline.passLayouts ?? []).map(
1653
+ (p) => [`sub${ei}:${p.name}`, p.storageBindings]
1654
+ )
1655
+ ])
1656
+ ];
1657
+ const _dbgMaxPass = _dbgPassCounts.reduce((m, p) => Math.max(m, p[1]), 0);
1658
+ if (typeof console !== "undefined" && console.log) {
1659
+ const logCfg = normalizedConfig;
1660
+ const shpU = pipeline.shapeUniforms;
1661
+ const sv = logCfg.startValues;
1662
+ const u = pipeline.uniforms;
1663
+ console.log(`[PS:create] system #${generalData.particleSystemId}`, {
1664
+ rendererType: rrType,
1665
+ requestedRendererType,
1666
+ effectiveRendererType: rrType,
1667
+ simulationSpace: normalizedConfig.simulationSpace,
1668
+ maxParticles,
1669
+ useInstancing
1670
+ });
1671
+ console.log(`[PS:config] system #${generalData.particleSystemId}`, {
1672
+ shape: {
1673
+ publicKind: logCfg.shape?.shape ?? null,
1674
+ gpuShapeKind: shpU.shapeKind?.value ?? 0,
1675
+ radius: shpU.radius?.value ?? logCfg.shape?.radius ?? null,
1676
+ radiusThickness: shpU.radiusThickness?.value ?? null,
1677
+ arcDeg: shpU.arcDeg?.value ?? null,
1678
+ coneAngleDeg: shpU.coneAngleDeg?.value ?? null,
1679
+ rectScale: [shpU.rectScaleX?.value, shpU.rectScaleY?.value],
1680
+ rectRotationDeg: [shpU.rectRotXDeg?.value, shpU.rectRotYDeg?.value],
1681
+ boxScale: [shpU.boxSX?.value, shpU.boxSY?.value, shpU.boxSZ?.value],
1682
+ boxEmitFrom: shpU.boxEmitFrom?.value ?? null
1683
+ },
1684
+ transform: {
1685
+ position: xform?.position ?? null,
1686
+ rotation: xform?.rotation ?? null,
1687
+ scale: xform?.scale ?? null
1688
+ },
1689
+ emission: {
1690
+ rateOverTime: logCfg.emission?.rateOverTime ?? 0,
1691
+ rateOverDistance: logCfg.emission?.rateOverDistance ?? 0,
1692
+ bursts: logCfg.emission?.bursts?.length ?? 0
1693
+ },
1694
+ startValues: {
1695
+ lifetime: sv?.startLifetime ?? null,
1696
+ speed: sv?.startSpeed ?? null,
1697
+ size: sv?.startSize ?? null,
1698
+ rotation: sv?.startRotation ?? null,
1699
+ color: sv?.startColor ?? null,
1700
+ opacity: sv?.startOpacity ?? null
1701
+ },
1702
+ textureId: config.textureId ?? config._editorData?.textureId ?? null,
1703
+ textureResolved: !!normalizedConfig.map,
1704
+ forceFieldCount: forceFields.length,
1705
+ collisionPlaneCount: collisionPlanes.length,
1706
+ subEmitterCount: (normalizedConfig.subEmitters ?? []).length,
1707
+ trailEnabled: !!trailDesc,
1708
+ modifiers: {
1709
+ linearVelocity: !!logCfg.velocityOverLifetime?.isActive && (u.linearVelX !== void 0 || u.axisLinXMin !== void 0 || !!(logCfg.velocityOverLifetime?.linear && Object.values(logCfg.velocityOverLifetime.linear).some(
1710
+ (value) => value !== void 0 && value !== 0
1711
+ ))),
1712
+ orbitalVelocity: !!logCfg.velocityOverLifetime?.isActive && !!(logCfg.velocityOverLifetime?.orbital && Object.values(logCfg.velocityOverLifetime.orbital).some(
1713
+ (value) => value !== void 0 && value !== 0
1714
+ )),
1715
+ sizeOverLifetime: !!normalizedConfig.sizeOverLifetime?.isActive,
1716
+ opacityOverLifetime: !!normalizedConfig.opacityOverLifetime?.isActive,
1717
+ colorOverLifetime: !!normalizedConfig.colorOverLifetime?.isActive,
1718
+ rotationOverLifetime: !!normalizedConfig.rotationOverLifetime?.isActive,
1719
+ noise: !!normalizedConfig.noise?.isActive
1720
+ }
1721
+ });
1722
+ console.log(
1723
+ `[PS:pipeline] system #${generalData.particleSystemId}: ${(props.passNames ?? []).join(" -> ") || "emit -> simulate"} | storageBindings=${_dbgPassCounts.map((p) => `${p[0]}=${p[1]}\u22648`).join(" ")} | packedFloats=${pipeline.buffers.packedData?.length ?? 0}`
1724
+ );
1725
+ }
1726
+ const update = (cycleData) => {
1727
+ updateParticleSystemInstance(props, cycleData);
1728
+ };
1729
+ const resumeEmitter = () => {
1730
+ generalData.isEnabled = true;
1731
+ };
1732
+ const pauseEmitter = () => {
1733
+ generalData.isEnabled = false;
1734
+ };
1735
+ const dispose = () => {
1736
+ destroyParticleSystem(particleSystem);
1737
+ };
1738
+ const updateConfig = (partial) => {
1739
+ ObjectUtils.deepMerge(normalizedConfig, partial, {
1740
+ applyToFirstObject: true,
1741
+ skippedProperties: []
1742
+ });
1743
+ };
1744
+ return {
1745
+ instance: particleSystem,
1746
+ resumeEmitter,
1747
+ pauseEmitter,
1748
+ dispose,
1749
+ update,
1750
+ updateConfig,
1751
+ /**
1752
+ * ?? Deprecated synchronous active count ????
1753
+ * Returns -1 (= unsupported) in the GPU-only engine: the authoritative count
1754
+ * is `maxParticles - allocator[0]` which lives in GPU storage and is only
1755
+ * available through an explicit (throttled) `getArrayBufferAsync` read-back.
1756
+ */
1757
+ getActiveParticleCount: () => -1,
1758
+ computeNode: props.allComputeNodes && props.allComputeNodes.length > 0 ? props.allComputeNodes : pipeline.computeNodes ?? pipeline.computeNode,
1759
+ /**
1760
+ * ?? Temporary one-shot GPU debug handle (deprecated, no per-frame cost) ????
1761
+ * getActiveParticleCount() stays -1; this object is the raw material for an
1762
+ * explicit
1763
+ enderer.getArrayBufferAsync(...) read-back (bytes, multiples of 4).
1764
+ * lastEmitCount() mirrors uEmitCount, the u32 count written per frame.
1765
+ */
1766
+ gpuDebug: {
1767
+ maxParticles,
1768
+ allocatorCount: pipeline.allocatorCount,
1769
+ /** Canonical requested vs effective GPU renderer classes (§2). */
1770
+ requestedRendererType,
1771
+ effectiveRendererType: rrType,
1772
+ /** u32 birth system seed for this pipeline (written ONCE at create). */
1773
+ systemSeed: pipeline.uniforms.seed.value,
1774
+ buffers: pipeline.buffers,
1775
+ emitNode: pipeline.emitNode,
1776
+ simNode: pipeline.simNode,
1777
+ passNames: pipeline.passNames ?? ["emit", "simulate"],
1778
+ allPassNames: props.passNames ?? [],
1779
+ storageBindingCount: _dbgMaxPass,
1780
+ passBindingCounts: _dbgPassCounts,
1781
+ lastEmitCount: () => pipeline.uniforms.emitCount.value,
1782
+ /**
1783
+ * Per-sub-emitter-child canonical pairs (§2/§21): each child pool's own
1784
+ * requested vs effective renderer class + its events-per-frame.
1785
+ */
1786
+ subEmitters: (subEntries ?? []).map((e) => ({
1787
+ requestedRendererType: e.requestedRendererType ?? null,
1788
+ effectiveRendererType: e.effectiveRendererType,
1789
+ perEvent: e.perEvent
1790
+ })),
1791
+ /** Decode summary for the `[PS:config]` / `[PS:pipeline]` logs. */
1792
+ snapshot: () => {
1793
+ const shp = normalizedConfig.shape;
1794
+ const branch = shp.shape === "CONE" ? shp.cone : shp.shape === "CIRCLE" ? shp.circle : shp.sphere;
1795
+ const tex = normalizedConfig.map;
1796
+ return {
1797
+ systemId: generalData.particleSystemId,
1798
+ // Canonical effective + original requested renderer classes (§2).
1799
+ effectiveRendererType: rrType,
1800
+ requestedRendererType,
1801
+ rendererType: rrType,
1802
+ simulationSpace: normalizedConfig.simulationSpace,
1803
+ maxParticles,
1804
+ shape: {
1805
+ publicShape: shp.shape,
1806
+ gpuShapeKind: pipeline.shapeUniforms?.shapeKind?.value ?? 0,
1807
+ radius: branch?.radius ?? null,
1808
+ radiusThickness: branch?.radiusThickness ?? null,
1809
+ arcDeg: branch?.arc ?? null,
1810
+ coneAngleDeg: shp.shape === "CONE" ? shp.cone?.angle ?? null : null,
1811
+ rectScale: shp.rectangle?.scale ?? null,
1812
+ rectRotation: shp.rectangle?.rotation ?? null,
1813
+ boxScale: shp.box?.scale ?? null,
1814
+ boxEmitFrom: shp.box?.emitFrom ?? null
1815
+ },
1816
+ textureId: config.textureId ?? config._editorData?.textureId ?? null,
1817
+ textureResolved: !!normalizedConfig.map,
1818
+ textureDimensions: tex?.image ? [tex.image.width ?? 0, tex.image.height ?? 0] : null,
1819
+ forceFieldCount: (normalizedConfig.forceFields ?? []).length,
1820
+ collisionPlaneCount: (normalizedConfig.collisionPlanes ?? []).length,
1821
+ subEmitterCount: (normalizedConfig.subEmitters ?? []).length,
1822
+ trailEnabled: !!normalizedConfig.renderer.trail
1823
+ };
1824
+ }
1825
+ }
1826
+ };
1827
+ };
1828
+ var _lastUploadStampMap = /* @__PURE__ */ new WeakMap();
1829
+ var _cmdUploadSeen = /* @__PURE__ */ new WeakSet();
1830
+ var updateParticleSystemInstance = (props, { now, delta, elapsed }) => {
1831
+ const {
1832
+ generalData,
1833
+ normalizedConfig,
1834
+ particleSystem,
1835
+ elapsedUniform,
1836
+ creationTime,
1837
+ normalizedForceFields,
1838
+ normalizedCollisionPlanes,
1839
+ emission,
1840
+ computePipeline: pipeline,
1841
+ maxParticles = 0,
1842
+ allComputeNodes,
1843
+ subEntries,
1844
+ fifoBaseStride = 0,
1845
+ ribbonUniforms
1846
+ } = props;
1847
+ if (!pipeline) return;
1848
+ const u = pipeline.uniforms;
1849
+ const dur = normalizedConfig.duration;
1850
+ const lifetime = now - creationTime;
1851
+ const loop = normalizedConfig.looping;
1852
+ const iterationTimeMs = loop ? lifetime % (dur * 1e3) : lifetime;
1853
+ generalData.normalizedLifetimePercentage = Math.max(Math.min(iterationTimeMs / 1e3 / dur, 1), 0);
1854
+ elapsedUniform.value = elapsed;
1855
+ const gv = generalData.gravityVelocity;
1856
+ gv.set(0, normalizedConfig.gravity, 0);
1857
+ if (normalizedConfig.simulationSpace === "WORLD" /* WORLD */) {
1858
+ particleSystem.updateMatrix();
1859
+ _tmpM1.copy(particleSystem.matrix);
1860
+ if (particleSystem.parent) {
1861
+ particleSystem.parent.updateMatrixWorld();
1862
+ _tmpM1.premultiply(particleSystem.parent.matrixWorld);
1863
+ }
1864
+ _tmpM1.decompose(
1865
+ generalData.currentWorldPosition,
1866
+ generalData.worldQuaternion,
1867
+ generalData.worldScale
1868
+ );
1869
+ } else {
1870
+ particleSystem.updateMatrixWorld();
1871
+ particleSystem.getWorldPosition(generalData.currentWorldPosition);
1872
+ particleSystem.getWorldQuaternion(generalData.worldQuaternion);
1873
+ particleSystem.getWorldScale(generalData.worldScale);
1874
+ _tmpQ1.copy(generalData.worldQuaternion).invert();
1875
+ gv.applyQuaternion(_tmpQ1);
1876
+ gv.x /= generalData.worldScale.x || 1;
1877
+ gv.y /= generalData.worldScale.y || 1;
1878
+ gv.z /= generalData.worldScale.z || 1;
1879
+ }
1880
+ if (generalData.lastWorldPosition.x !== -99999) {
1881
+ _lastWorldPositionSnapshot.copy(generalData.lastWorldPosition);
1882
+ generalData.distanceFromLastEmitByDistance += _lastWorldPositionSnapshot.distanceTo(generalData.currentWorldPosition);
1883
+ }
1884
+ generalData.lastWorldPosition.copy(generalData.currentWorldPosition);
1885
+ let emitCount = 0;
1886
+ if (generalData.isEnabled && (loop || iterationTimeMs < dur * 1e3)) {
1887
+ const lastEmit = props.lastEmissionTime;
1888
+ const emissionDelta = now - lastEmit;
1889
+ if (emissionDelta > 0) {
1890
+ props.lastEmissionTime = now;
1891
+ if (emission.rateOverTime) {
1892
+ props.emissionAccumulator += calculateValue(
1893
+ generalData.particleSystemId,
1894
+ emission.rateOverTime,
1895
+ generalData.normalizedLifetimePercentage
1896
+ ) * (emissionDelta / 1e3);
1897
+ }
1898
+ }
1899
+ emitCount += Math.floor(props.emissionAccumulator);
1900
+ if (emitCount > 0) props.emissionAccumulator -= emitCount;
1901
+ if (emission.rateOverDistance && generalData.distanceFromLastEmitByDistance > 0) {
1902
+ const r = calculateValue(
1903
+ generalData.particleSystemId,
1904
+ emission.rateOverDistance,
1905
+ generalData.normalizedLifetimePercentage
1906
+ );
1907
+ if (r > 0) {
1908
+ const n2 = Math.floor(generalData.distanceFromLastEmitByDistance * r);
1909
+ emitCount += n2;
1910
+ generalData.distanceFromLastEmitByDistance = Math.max(
1911
+ generalData.distanceFromLastEmitByDistance - n2 / r,
1912
+ 0
1913
+ );
1914
+ }
1915
+ }
1916
+ if (emission.bursts && generalData.burstStates) {
1917
+ const bursts = emission.bursts;
1918
+ const states = generalData.burstStates;
1919
+ const tSec = iterationTimeMs / 1e3;
1920
+ for (let i = 0; i < bursts.length; i++) {
1921
+ const b = bursts[i];
1922
+ const s = states[i];
1923
+ const cyc = b.cycles ?? 1;
1924
+ const iv = b.interval ?? 0;
1925
+ const prob = b.probability ?? 1;
1926
+ if (loop && tSec < (b.time ?? 0) && s.cyclesExecuted > 0) {
1927
+ s.cyclesExecuted = 0;
1928
+ s.lastCycleTime = 0;
1929
+ s.probabilityPassed = false;
1930
+ }
1931
+ if (s.cyclesExecuted >= cyc) continue;
1932
+ const next = (b.time ?? 0) + s.cyclesExecuted * iv;
1933
+ if (tSec >= next) {
1934
+ if (s.cyclesExecuted === 0) s.probabilityPassed = Math.random() < prob;
1935
+ if (s.probabilityPassed) {
1936
+ emitCount += Math.floor(
1937
+ calculateValue(generalData.particleSystemId, b.count, generalData.normalizedLifetimePercentage)
1938
+ );
1939
+ }
1940
+ s.cyclesExecuted++;
1941
+ s.lastCycleTime = tSec;
1942
+ }
1943
+ }
1944
+ }
1945
+ if (emitCount > maxParticles) emitCount = maxParticles;
1946
+ }
1947
+ u.delta.value = delta;
1948
+ u.deltaMs.value = delta * 1e3;
1949
+ u.gravityVelocity.value.copy(gv);
1950
+ u.emitCount.value = emitCount;
1951
+ pipeline.emitNode.count = Math.max(1, emitCount);
1952
+ if (pipeline.subBirthEventsNode) {
1953
+ pipeline.subBirthEventsNode.count = Math.max(1, emitCount);
1954
+ }
1955
+ const n = generalData.noise;
1956
+ if (u.noiseStrength) u.noiseStrength.value = n.strength;
1957
+ if (u.noisePower) u.noisePower.value = n.noisePower;
1958
+ if (u.noiseFrequency) u.noiseFrequency.value = n.frequency;
1959
+ if (u.noisePositionAmount) u.noisePositionAmount.value = n.positionAmount;
1960
+ if (u.noiseRotationAmount) u.noiseRotationAmount.value = n.rotationAmount;
1961
+ if (u.noiseSizeAmount) u.noiseSizeAmount.value = n.sizeAmount;
1962
+ const pose = pipeline.emitterPose;
1963
+ if (pose) {
1964
+ if (normalizedConfig.simulationSpace === "WORLD" /* WORLD */) {
1965
+ particleSystem.updateMatrix();
1966
+ _tmpM1.copy(particleSystem.matrix);
1967
+ if (particleSystem.parent) {
1968
+ particleSystem.parent.updateMatrixWorld();
1969
+ _tmpM1.premultiply(particleSystem.parent.matrixWorld);
1970
+ }
1971
+ _tmpM1.decompose(_tmpV1, _tmpQ1, _tmpV2);
1972
+ pose.positionW.value.set(_tmpV1.x, _tmpV1.y, _tmpV1.z, 1);
1973
+ pose.wrapperQuat.value.set(_tmpQ1.x, _tmpQ1.y, _tmpQ1.z, _tmpQ1.w);
1974
+ pose.worldScale.value.set(_tmpV2.x || 1, _tmpV2.y || 1, _tmpV2.z || 1);
1975
+ } else {
1976
+ pose.positionW.value.set(0, 0, 0, 0);
1977
+ pose.wrapperQuat.value.set(0, 0, 0, 1);
1978
+ pose.worldScale.value.set(1, 1, 1);
1979
+ }
1980
+ }
1981
+ const parity = (props.frameParity ?? 0) % 2;
1982
+ const fifoBase = parity;
1983
+ if (u.fifoBase) u.fifoBase.value = fifoBase;
1984
+ if (u.nowMs) u.nowMs.value = now;
1985
+ if (ribbonUniforms?.nowMs) ribbonUniforms.nowMs.value = now;
1986
+ for (const e of subEntries ?? []) {
1987
+ const cp = e.pipeline;
1988
+ if (!cp) continue;
1989
+ const cu = cp.uniforms;
1990
+ if (cu.delta) cu.delta.value = delta;
1991
+ if (cu.deltaMs) cu.deltaMs.value = delta * 1e3;
1992
+ if (cu.nowMs) cu.nowMs.value = now;
1993
+ if (cu.gravityVelocity) {
1994
+ cu.gravityVelocity.value.set(
1995
+ 0,
1996
+ e.gravity,
1997
+ 0
1998
+ );
1999
+ }
2000
+ if (e.noise) {
2001
+ if (cu.noiseStrength) cu.noiseStrength.value = e.noise.strength;
2002
+ if (cu.noisePower) cu.noisePower.value = e.noise.noisePower;
2003
+ if (cu.noiseFrequency) cu.noiseFrequency.value = e.noise.frequency;
2004
+ if (cu.noisePositionAmount) cu.noisePositionAmount.value = e.noise.positionAmount;
2005
+ if (cu.noiseRotationAmount) cu.noiseRotationAmount.value = e.noise.rotationAmount;
2006
+ if (cu.noiseSizeAmount) cu.noiseSizeAmount.value = e.noise.sizeAmount;
2007
+ }
2008
+ if (cu.fifoBase) cu.fifoBase.value = fifoBase;
2009
+ if (e.init.uniforms.fifoBase) e.init.uniforms.fifoBase.value = fifoBase;
2010
+ let childEmit = 0;
2011
+ if (e.rate > 0) {
2012
+ e.acc += e.rate * delta / 1;
2013
+ childEmit = Math.floor(e.acc);
2014
+ if (childEmit > 0) e.acc -= childEmit;
2015
+ }
2016
+ const childCapacity = Math.max(2, (cp.allocatorCount ?? 2) - 1);
2017
+ if (childEmit > childCapacity) childEmit = childCapacity;
2018
+ if (cp.emitNode) cp.emitNode.count = Math.max(1, childEmit);
2019
+ if (cu.emitCount) cu.emitCount.value = childEmit;
2020
+ const cpose = cp.emitterPose;
2021
+ if (cpose) {
2022
+ if (e.isWorld === 1) {
2023
+ cpose.positionW.value.set(e.position[0], e.position[1], e.position[2], 1);
2024
+ cpose.wrapperQuat.value.set(e.quat[0], e.quat[1], e.quat[2], e.quat[3]);
2025
+ cpose.worldScale.value.set(e.scale[0], e.scale[1], e.scale[2]);
2026
+ } else {
2027
+ cpose.positionW.value.set(0, 0, 0, 0);
2028
+ cpose.wrapperQuat.value.set(0, 0, 0, 1);
2029
+ cpose.worldScale.value.set(1, 1, 1);
2030
+ }
2031
+ }
2032
+ const ip = e.init.uniforms;
2033
+ if (ip.positionW && ip.wrapperQuat) {
2034
+ if (e.isWorld === 1) {
2035
+ ip.positionW.value.set(e.position[0], e.position[1], e.position[2], 1);
2036
+ ip.wrapperQuat.value.set(e.quat[0], e.quat[1], e.quat[2], e.quat[3]);
2037
+ } else {
2038
+ ip.positionW.value.set(0, 0, 0, 0);
2039
+ ip.wrapperQuat.value.set(0, 0, 0, 1);
2040
+ }
2041
+ }
2042
+ }
2043
+ const ffInfo = pipeline.forceFieldInfo;
2044
+ const cInfo = pipeline.collisionPlaneInfo ?? null;
2045
+ if ((ffInfo || cInfo) && _tslMaterialFactory) {
2046
+ const cdArr = pipeline.buffers.packedData;
2047
+ const cdNode = pipeline.packedDataNode;
2048
+ if (ffInfo && normalizedForceFields.length > 0) {
2049
+ const encFF = _tslMaterialFactory.encodeForceFieldsForGPU(normalizedForceFields, generalData.particleSystemId, generalData.normalizedLifetimePercentage);
2050
+ let changedFF = false;
2051
+ for (let k = 0; k < encFF.length; k++) if (cdArr[ffInfo.offset + k] !== encFF[k]) {
2052
+ changedFF = true;
2053
+ break;
2054
+ }
2055
+ if (changedFF) {
2056
+ cdArr.set(encFF, ffInfo.offset);
2057
+ cdNode.addUpdateRange(ffInfo.offset, encFF.length);
2058
+ cdNode.needsUpdate = true;
2059
+ }
2060
+ ffInfo.countUniform.value = normalizedForceFields.length;
2061
+ }
2062
+ if (cInfo && normalizedCollisionPlanes.length > 0) {
2063
+ const encCP = _tslMaterialFactory.encodeCollisionPlanesForGPU(normalizedCollisionPlanes);
2064
+ let changedCP = false;
2065
+ for (let k = 0; k < encCP.length; k++) if (cdArr[cInfo.offset + k] !== encCP[k]) {
2066
+ changedCP = true;
2067
+ break;
2068
+ }
2069
+ if (changedCP) {
2070
+ cdArr.set(encCP, cInfo.offset);
2071
+ cdNode.addUpdateRange(cInfo.offset, encCP.length);
2072
+ cdNode.needsUpdate = true;
2073
+ }
2074
+ cInfo.countUniform.value = normalizedCollisionPlanes.length;
2075
+ }
2076
+ }
2077
+ const bufs = pipeline.buffers;
2078
+ let stamp = _lastUploadStampMap.get(bufs);
2079
+ if (stamp === void 0 || stamp === 0) {
2080
+ for (const key of Object.keys(bufs)) {
2081
+ const a = bufs[key];
2082
+ if (a && "needsUpdate" in a) a.needsUpdate = true;
2083
+ }
2084
+ _lastUploadStampMap.set(bufs, 1);
2085
+ } else {
2086
+ _lastUploadStampMap.set(bufs, stamp + 1);
2087
+ }
2088
+ for (const e of subEntries ?? []) {
2089
+ const cb = e.pipeline?.buffers;
2090
+ if (cb && !_lastUploadStampMap.has(cb)) {
2091
+ for (const key of Object.keys(cb)) {
2092
+ const a = cb[key];
2093
+ if (a && "needsUpdate" in a) a.needsUpdate = true;
2094
+ }
2095
+ _lastUploadStampMap.set(cb, 1);
2096
+ }
2097
+ const cmd = e.init.commandBuffer;
2098
+ if (cmd && "needsUpdate" in cmd && !_cmdUploadSeen.has(cmd)) {
2099
+ cmd.needsUpdate = true;
2100
+ _cmdUploadSeen.add(cmd);
2101
+ }
2102
+ }
2103
+ const rb = props.ribbonBuffers;
2104
+ if (rb && !_lastUploadStampMap.has(rb)) {
2105
+ for (const key of Object.keys(rb)) {
2106
+ const a = rb[key];
2107
+ if (a && "needsUpdate" in a) a.needsUpdate = true;
2108
+ }
2109
+ _lastUploadStampMap.set(rb, 1);
2110
+ }
2111
+ props.computeDispatchReady = true;
2112
+ props.iterationCount++;
2113
+ props.frameParity = (props.frameParity ?? 0) ^ 1;
2114
+ if (props.trailMesh) updateTrailGeometry(props, now);
2115
+ };
2116
+ var _tmpQ1 = new THREE3.Quaternion();
2117
+ var _tmpV1 = new THREE3.Vector3();
2118
+ var _tmpV2 = new THREE3.Vector3();
2119
+ var _tmpM1 = new THREE3.Matrix4();
2120
+ var catmullRom = (out, outIdx, p0x, p0y, p0z, p1x, p1y, p1z, p2x, p2y, p2z, p3x, p3y, p3z, t) => {
2121
+ const t2 = t * t;
2122
+ const t3 = t2 * t;
2123
+ out[outIdx] = 0.5 * (2 * p1x + (-p0x + p2x) * t + (2 * p0x - 5 * p1x + 4 * p2x - p3x) * t2 + (-p0x + 3 * p1x - 3 * p2x + p3x) * t3);
2124
+ out[outIdx + 1] = 0.5 * (2 * p1y + (-p0y + p2y) * t + (2 * p0y - 5 * p1y + 4 * p2y - p3y) * t2 + (-p0y + 3 * p1y - 3 * p2y + p3y) * t3);
2125
+ out[outIdx + 2] = 0.5 * (2 * p1z + (-p0z + p2z) * t + (2 * p0z - 5 * p1z + 4 * p2z - p3z) * t2 + (-p0z + 3 * p1z - 3 * p2z + p3z) * t3);
2126
+ };
2127
+ var clearTrailVertex = (vIdx, cIdx, aIdx, uvIdx, trailPosArr, trailNextArr, trailHalfWidthArr, trailUVArr, trailAlphaArr, trailColorArr, fallbackX, fallbackY, fallbackZ) => {
2128
+ trailPosArr[vIdx] = fallbackX;
2129
+ trailPosArr[vIdx + 1] = fallbackY;
2130
+ trailPosArr[vIdx + 2] = fallbackZ;
2131
+ trailPosArr[vIdx + 3] = fallbackX;
2132
+ trailPosArr[vIdx + 4] = fallbackY;
2133
+ trailPosArr[vIdx + 5] = fallbackZ;
2134
+ trailNextArr[vIdx] = fallbackX;
2135
+ trailNextArr[vIdx + 1] = fallbackY;
2136
+ trailNextArr[vIdx + 2] = fallbackZ;
2137
+ trailNextArr[vIdx + 3] = fallbackX;
2138
+ trailNextArr[vIdx + 4] = fallbackY;
2139
+ trailNextArr[vIdx + 5] = fallbackZ;
2140
+ trailHalfWidthArr[aIdx] = 0;
2141
+ trailHalfWidthArr[aIdx + 1] = 0;
2142
+ trailUVArr[uvIdx] = 0;
2143
+ trailUVArr[uvIdx + 1] = 0;
2144
+ trailUVArr[uvIdx + 2] = 0;
2145
+ trailUVArr[uvIdx + 3] = 0;
2146
+ trailAlphaArr[aIdx] = 0;
2147
+ trailAlphaArr[aIdx + 1] = 0;
2148
+ trailColorArr[cIdx] = 0;
2149
+ trailColorArr[cIdx + 1] = 0;
2150
+ trailColorArr[cIdx + 2] = 0;
2151
+ trailColorArr[cIdx + 3] = 0;
2152
+ trailColorArr[cIdx + 4] = 0;
2153
+ trailColorArr[cIdx + 5] = 0;
2154
+ trailColorArr[cIdx + 6] = 0;
2155
+ trailColorArr[cIdx + 7] = 0;
2156
+ };
2157
+ var writeTrailVertex = (vIdx, cIdx, aIdx, uvIdx, hx, hy, hz, nx, ny, nz, halfWidth, t, alpha, fr, fg, fb, ca, trailPosArr, trailNextArr, trailHalfWidthArr, trailUVArr, trailAlphaArr, trailColorArr) => {
2158
+ trailPosArr[vIdx] = hx;
2159
+ trailPosArr[vIdx + 1] = hy;
2160
+ trailPosArr[vIdx + 2] = hz;
2161
+ trailPosArr[vIdx + 3] = hx;
2162
+ trailPosArr[vIdx + 4] = hy;
2163
+ trailPosArr[vIdx + 5] = hz;
2164
+ trailNextArr[vIdx] = nx;
2165
+ trailNextArr[vIdx + 1] = ny;
2166
+ trailNextArr[vIdx + 2] = nz;
2167
+ trailNextArr[vIdx + 3] = nx;
2168
+ trailNextArr[vIdx + 4] = ny;
2169
+ trailNextArr[vIdx + 5] = nz;
2170
+ trailHalfWidthArr[aIdx] = halfWidth;
2171
+ trailHalfWidthArr[aIdx + 1] = halfWidth;
2172
+ trailUVArr[uvIdx] = 0;
2173
+ trailUVArr[uvIdx + 1] = t;
2174
+ trailUVArr[uvIdx + 2] = 1;
2175
+ trailUVArr[uvIdx + 3] = t;
2176
+ trailAlphaArr[aIdx] = alpha;
2177
+ trailAlphaArr[aIdx + 1] = alpha;
2178
+ trailColorArr[cIdx] = fr;
2179
+ trailColorArr[cIdx + 1] = fg;
2180
+ trailColorArr[cIdx + 2] = fb;
2181
+ trailColorArr[cIdx + 3] = ca;
2182
+ trailColorArr[cIdx + 4] = fr;
2183
+ trailColorArr[cIdx + 5] = fg;
2184
+ trailColorArr[cIdx + 6] = fb;
2185
+ trailColorArr[cIdx + 7] = ca;
2186
+ };
2187
+ var _rawPoints = null;
2188
+ var _rawPointsSize = 0;
2189
+ var _smoothedPoints = null;
2190
+ var _smoothedPointsSize = 0;
2191
+ var _ribbonIndices = null;
2192
+ var _ribbonIndicesSize = 0;
2193
+ var _ribbonCount = 0;
2194
+ var updateTrailGeometry = (props, now) => {
2195
+ const {
2196
+ generalData,
2197
+ trailPositionAttr,
2198
+ trailAlphaAttr,
2199
+ trailColorAttr,
2200
+ trailNextAttr: trailNextAttrCached,
2201
+ trailHalfWidthAttr: trailHalfWidthAttrCached,
2202
+ trailUVAttr: trailUVAttrCached,
2203
+ trailWidthCurveFn,
2204
+ trailOpacityCurveFn,
2205
+ trailColorOverTrailFns,
2206
+ trailConfig,
2207
+ mappedAttributes: ma
2208
+ } = props;
2209
+ if (!trailPositionAttr || !trailAlphaAttr || !trailColorAttr || !trailNextAttrCached || !trailHalfWidthAttrCached || !trailUVAttrCached || !trailWidthCurveFn || !trailOpacityCurveFn || !trailConfig || !generalData.positionHistory || !generalData.positionHistoryIndex || !generalData.positionHistoryCount)
2210
+ return;
2211
+ const trailLength = trailConfig.length;
2212
+ const positionHistory = generalData.positionHistory;
2213
+ const historyIndex = generalData.positionHistoryIndex;
2214
+ const historyCount = generalData.positionHistoryCount;
2215
+ const sampleTimes = generalData.trailSampleTimes;
2216
+ const lastSampledPos = generalData.trailLastSampledPosition;
2217
+ const prevNormal = generalData.trailPrevNormal;
2218
+ const minVertexDist = trailConfig.minVertexDistance;
2219
+ const minVertexDistSq = minVertexDist * minVertexDist;
2220
+ const maxTime = trailConfig.maxTime;
2221
+ const maxTimeMs = maxTime * 1e3;
2222
+ const useSmoothing = trailConfig.smoothing;
2223
+ const subdivisions = trailConfig.smoothingSubdivisions;
2224
+ const useTwistPrevention = trailConfig.twistPrevention;
2225
+ const ribbonId = trailConfig.ribbonId;
2226
+ const trailScalarArr = props.scalarArray;
2227
+ const positionArr = ma.position.array;
2228
+ const prevFilled = generalData.trailPrevFilledCount;
2229
+ const trailPosArr = trailPositionAttr.array;
2230
+ const trailAlphaArr = trailAlphaAttr.array;
2231
+ const trailColorArr = trailColorAttr.array;
2232
+ const trailNextArr = trailNextAttrCached.array;
2233
+ const trailUVArr = trailUVAttrCached.array;
2234
+ const trailHalfWidthArr = trailHalfWidthAttrCached.array;
2235
+ const verticesPerParticle = trailLength * 2;
2236
+ const hwm = generalData.highWaterIndex;
2237
+ const creationTimesLength = hwm > 0 ? hwm : generalData.creationTimes.length;
2238
+ let hasUpdates = false;
2239
+ const useRibbon = ribbonId !== void 0;
2240
+ let ribbonLeader = -1;
2241
+ if (useRibbon) {
2242
+ if (!_ribbonIndices || _ribbonIndicesSize < creationTimesLength) {
2243
+ _ribbonIndices = new Uint32Array(creationTimesLength);
2244
+ _ribbonIndicesSize = creationTimesLength;
2245
+ }
2246
+ _ribbonCount = 0;
2247
+ for (let i = 0; i < creationTimesLength; i++) {
2248
+ if (trailScalarArr[i * SCALAR_STRIDE + S_IS_ACTIVE])
2249
+ _ribbonIndices[_ribbonCount++] = i;
2250
+ }
2251
+ for (let i = 1; i < _ribbonCount; i++) {
2252
+ const key = _ribbonIndices[i];
2253
+ const keyTime = generalData.creationTimes[key];
2254
+ let j = i - 1;
2255
+ while (j >= 0 && generalData.creationTimes[_ribbonIndices[j]] > keyTime) {
2256
+ _ribbonIndices[j + 1] = _ribbonIndices[j];
2257
+ j--;
2258
+ }
2259
+ _ribbonIndices[j + 1] = key;
2260
+ }
2261
+ if (_ribbonCount > 0) ribbonLeader = _ribbonIndices[0];
2262
+ }
2263
+ for (let index = 0; index < creationTimesLength; index++) {
2264
+ const vertBase = index * verticesPerParticle;
2265
+ if (trailScalarArr[index * SCALAR_STRIDE + S_IS_ACTIVE]) {
2266
+ if (useRibbon && _ribbonCount >= 2 && index !== ribbonLeader) {
2267
+ const posIdx2 = index * 3;
2268
+ const px2 = positionArr[posIdx2];
2269
+ const py2 = positionArr[posIdx2 + 1];
2270
+ const pz2 = positionArr[posIdx2 + 2];
2271
+ const histBase = (index * trailLength + historyIndex[index]) * 3;
2272
+ positionHistory[histBase] = px2;
2273
+ positionHistory[histBase + 1] = py2;
2274
+ positionHistory[histBase + 2] = pz2;
2275
+ if (sampleTimes) {
2276
+ sampleTimes[index * trailLength + historyIndex[index]] = now;
2277
+ }
2278
+ historyIndex[index] = (historyIndex[index] + 1) % trailLength;
2279
+ if (historyCount[index] < trailLength) historyCount[index]++;
2280
+ continue;
2281
+ }
2282
+ hasUpdates = true;
2283
+ const posIdx = index * 3;
2284
+ const px = positionArr[posIdx];
2285
+ const py = positionArr[posIdx + 1];
2286
+ const pz = positionArr[posIdx + 2];
2287
+ let shouldSample = true;
2288
+ if (minVertexDist > 0 && lastSampledPos && historyCount[index] > 0) {
2289
+ const lsIdx = index * 3;
2290
+ const dx = px - lastSampledPos[lsIdx];
2291
+ const dy = py - lastSampledPos[lsIdx + 1];
2292
+ const dz = pz - lastSampledPos[lsIdx + 2];
2293
+ if (dx * dx + dy * dy + dz * dz < minVertexDistSq) {
2294
+ shouldSample = false;
2295
+ }
2296
+ }
2297
+ if (shouldSample) {
2298
+ const histBase = (index * trailLength + historyIndex[index]) * 3;
2299
+ positionHistory[histBase] = px;
2300
+ positionHistory[histBase + 1] = py;
2301
+ positionHistory[histBase + 2] = pz;
2302
+ if (sampleTimes) {
2303
+ sampleTimes[index * trailLength + historyIndex[index]] = now;
2304
+ }
2305
+ historyIndex[index] = (historyIndex[index] + 1) % trailLength;
2306
+ if (historyCount[index] < trailLength) historyCount[index]++;
2307
+ if (lastSampledPos) {
2308
+ const lsIdx = index * 3;
2309
+ lastSampledPos[lsIdx] = px;
2310
+ lastSampledPos[lsIdx + 1] = py;
2311
+ lastSampledPos[lsIdx + 2] = pz;
2312
+ }
2313
+ }
2314
+ let rawCount = historyCount[index];
2315
+ let effectiveCount = rawCount;
2316
+ if (maxTime > 0 && sampleTimes && rawCount > 0) {
2317
+ const sampleBase = index * trailLength;
2318
+ effectiveCount = 0;
2319
+ for (let s = 0; s < rawCount; s++) {
2320
+ const sampleSlot = (historyIndex[index] - 1 - s + trailLength * 2) % trailLength;
2321
+ const age = now - sampleTimes[sampleBase + sampleSlot];
2322
+ if (age <= maxTimeMs) {
2323
+ effectiveCount++;
2324
+ } else {
2325
+ break;
2326
+ }
2327
+ }
2328
+ }
2329
+ const count = effectiveCount;
2330
+ const ribbonWidth = trailConfig.width;
2331
+ const trailBase = index * SCALAR_STRIDE;
2332
+ const cr = trailScalarArr[trailBase + S_COLOR_R];
2333
+ const cg = trailScalarArr[trailBase + S_COLOR_G];
2334
+ const cb = trailScalarArr[trailBase + S_COLOR_B];
2335
+ const ca = trailScalarArr[trailBase + S_COLOR_A];
2336
+ const ringOff = index * trailLength * 3;
2337
+ const rawPtsSize = count * 3;
2338
+ if (!_rawPoints || _rawPointsSize < rawPtsSize) {
2339
+ _rawPoints = new Float32Array(rawPtsSize);
2340
+ _rawPointsSize = rawPtsSize;
2341
+ }
2342
+ const rawPts = _rawPoints;
2343
+ for (let s = 0; s < count; s++) {
2344
+ const histSlot = (historyIndex[index] - 1 - s + trailLength * 2) % trailLength * 3 + ringOff;
2345
+ rawPts[s * 3] = positionHistory[histSlot];
2346
+ rawPts[s * 3 + 1] = positionHistory[histSlot + 1];
2347
+ rawPts[s * 3 + 2] = positionHistory[histSlot + 2];
2348
+ }
2349
+ let finalPts;
2350
+ let finalCount;
2351
+ if (useSmoothing && count >= 3) {
2352
+ const segmentCount = count - 1;
2353
+ finalCount = segmentCount * subdivisions + 1;
2354
+ const neededSize = finalCount * 3;
2355
+ if (!_smoothedPoints || _smoothedPointsSize < neededSize) {
2356
+ _smoothedPoints = new Float32Array(neededSize);
2357
+ _smoothedPointsSize = neededSize;
2358
+ }
2359
+ finalPts = _smoothedPoints;
2360
+ for (let seg = 0; seg < segmentCount; seg++) {
2361
+ const i0 = Math.max(0, seg - 1);
2362
+ const i1 = seg;
2363
+ const i2 = Math.min(count - 1, seg + 1);
2364
+ const i3 = Math.min(count - 1, seg + 2);
2365
+ const p0x = rawPts[i0 * 3], p0y = rawPts[i0 * 3 + 1], p0z = rawPts[i0 * 3 + 2];
2366
+ const p1x = rawPts[i1 * 3], p1y = rawPts[i1 * 3 + 1], p1z = rawPts[i1 * 3 + 2];
2367
+ const p2x = rawPts[i2 * 3], p2y = rawPts[i2 * 3 + 1], p2z = rawPts[i2 * 3 + 2];
2368
+ const p3x = rawPts[i3 * 3], p3y = rawPts[i3 * 3 + 1], p3z = rawPts[i3 * 3 + 2];
2369
+ for (let sub = 0; sub < subdivisions; sub++) {
2370
+ const t = sub / subdivisions;
2371
+ const outIdx = (seg * subdivisions + sub) * 3;
2372
+ catmullRom(
2373
+ finalPts,
2374
+ outIdx,
2375
+ p0x,
2376
+ p0y,
2377
+ p0z,
2378
+ p1x,
2379
+ p1y,
2380
+ p1z,
2381
+ p2x,
2382
+ p2y,
2383
+ p2z,
2384
+ p3x,
2385
+ p3y,
2386
+ p3z,
2387
+ t
2388
+ );
2389
+ }
2390
+ }
2391
+ const lastOutIdx = (finalCount - 1) * 3;
2392
+ finalPts[lastOutIdx] = rawPts[(count - 1) * 3];
2393
+ finalPts[lastOutIdx + 1] = rawPts[(count - 1) * 3 + 1];
2394
+ finalPts[lastOutIdx + 2] = rawPts[(count - 1) * 3 + 2];
2395
+ } else {
2396
+ finalPts = rawPts;
2397
+ finalCount = count;
2398
+ }
2399
+ if (finalCount > trailLength) finalCount = trailLength;
2400
+ if (useSmoothing && finalCount >= 2) {
2401
+ const MIN_SEG_DIST_SQ = 1e-4 * 1e-4;
2402
+ for (let d = 1; d < finalCount; d++) {
2403
+ const pi = (d - 1) * 3;
2404
+ const ci = d * 3;
2405
+ const dx = finalPts[ci] - finalPts[pi];
2406
+ const dy = finalPts[ci + 1] - finalPts[pi + 1];
2407
+ const dz = finalPts[ci + 2] - finalPts[pi + 2];
2408
+ if (dx * dx + dy * dy + dz * dz < MIN_SEG_DIST_SQ) {
2409
+ finalPts[ci] = finalPts[pi];
2410
+ finalPts[ci + 1] = finalPts[pi + 1];
2411
+ finalPts[ci + 2] = finalPts[pi + 2];
2412
+ }
2413
+ }
2414
+ }
2415
+ const prevFilledSlots = prevFilled ? prevFilled[index] : trailLength;
2416
+ if (prevFilled) prevFilled[index] = finalCount;
2417
+ for (let s = 0; s < trailLength; s++) {
2418
+ const vIdx = (vertBase + s * 2) * 3;
2419
+ const cIdx = (vertBase + s * 2) * 4;
2420
+ const aIdx = vertBase + s * 2;
2421
+ const uvIdxBase = (vertBase + s * 2) * 2;
2422
+ if (s >= finalCount) {
2423
+ if (s >= prevFilledSlots) break;
2424
+ clearTrailVertex(
2425
+ vIdx,
2426
+ cIdx,
2427
+ aIdx,
2428
+ uvIdxBase,
2429
+ trailPosArr,
2430
+ trailNextArr,
2431
+ trailHalfWidthArr,
2432
+ trailUVArr,
2433
+ trailAlphaArr,
2434
+ trailColorArr,
2435
+ px,
2436
+ py,
2437
+ pz
2438
+ );
2439
+ continue;
2440
+ }
2441
+ const hx = finalPts[s * 3];
2442
+ const hy = finalPts[s * 3 + 1];
2443
+ const hz = finalPts[s * 3 + 2];
2444
+ let nx, ny, nz;
2445
+ if (s > 0 && s < finalCount - 1) {
2446
+ const px2 = finalPts[(s - 1) * 3];
2447
+ const py2 = finalPts[(s - 1) * 3 + 1];
2448
+ const pz2 = finalPts[(s - 1) * 3 + 2];
2449
+ const nx2 = finalPts[(s + 1) * 3];
2450
+ const ny2 = finalPts[(s + 1) * 3 + 1];
2451
+ const nz2 = finalPts[(s + 1) * 3 + 2];
2452
+ const atx = nx2 - px2;
2453
+ const aty = ny2 - py2;
2454
+ const atz = nz2 - pz2;
2455
+ const atLen = Math.sqrt(atx * atx + aty * aty + atz * atz);
2456
+ if (atLen > 1e-4) {
2457
+ nx = hx + atx / atLen;
2458
+ ny = hy + aty / atLen;
2459
+ nz = hz + atz / atLen;
2460
+ } else {
2461
+ nx = finalPts[(s + 1) * 3];
2462
+ ny = finalPts[(s + 1) * 3 + 1];
2463
+ nz = finalPts[(s + 1) * 3 + 2];
2464
+ }
2465
+ } else if (s < finalCount - 1) {
2466
+ nx = finalPts[(s + 1) * 3];
2467
+ ny = finalPts[(s + 1) * 3 + 1];
2468
+ nz = finalPts[(s + 1) * 3 + 2];
2469
+ } else if (finalCount >= 2) {
2470
+ const prevX = finalPts[(s - 1) * 3];
2471
+ const prevY = finalPts[(s - 1) * 3 + 1];
2472
+ const prevZ = finalPts[(s - 1) * 3 + 2];
2473
+ nx = hx + (hx - prevX);
2474
+ ny = hy + (hy - prevY);
2475
+ nz = hz + (hz - prevZ);
2476
+ } else {
2477
+ nx = hx;
2478
+ ny = hy + 1e-3;
2479
+ nz = hz;
2480
+ }
2481
+ const t = finalCount > 1 ? s / (finalCount - 1) : 0;
2482
+ let timeFade = 1;
2483
+ if (maxTime > 0 && sampleTimes && effectiveCount > 0) {
2484
+ const sampleBase = index * trailLength;
2485
+ if (useSmoothing && rawCount >= 2) {
2486
+ const rawF = s / Math.max(finalCount - 1, 1) * (rawCount - 1);
2487
+ const rawLo = Math.min(Math.floor(rawF), rawCount - 1);
2488
+ const rawHi = Math.min(rawLo + 1, rawCount - 1);
2489
+ const frac = rawF - rawLo;
2490
+ const slotLo = (historyIndex[index] - 1 - rawLo + trailLength * 2) % trailLength;
2491
+ const slotHi = (historyIndex[index] - 1 - rawHi + trailLength * 2) % trailLength;
2492
+ const ageLo = now - sampleTimes[sampleBase + slotLo];
2493
+ const ageHi = now - sampleTimes[sampleBase + slotHi];
2494
+ const age = ageLo + (ageHi - ageLo) * frac;
2495
+ timeFade = 1 - Math.min(age / maxTimeMs, 1);
2496
+ } else {
2497
+ const rawS = Math.min(s, rawCount - 1);
2498
+ const sampleSlot = (historyIndex[index] - 1 - rawS + trailLength * 2) % trailLength;
2499
+ const age = now - sampleTimes[sampleBase + sampleSlot];
2500
+ timeFade = 1 - Math.min(age / maxTimeMs, 1);
2501
+ }
2502
+ }
2503
+ const widthScale = trailWidthCurveFn(t);
2504
+ const opacityScale = trailOpacityCurveFn(t);
2505
+ const halfWidth = ribbonWidth * widthScale * 0.5;
2506
+ const alpha = ca * opacityScale * timeFade;
2507
+ const fr = trailColorOverTrailFns ? cr * trailColorOverTrailFns.r(t) : cr;
2508
+ const fg = trailColorOverTrailFns ? cg * trailColorOverTrailFns.g(t) : cg;
2509
+ const fb = trailColorOverTrailFns ? cb * trailColorOverTrailFns.b(t) : cb;
2510
+ writeTrailVertex(
2511
+ vIdx,
2512
+ cIdx,
2513
+ aIdx,
2514
+ uvIdxBase,
2515
+ hx,
2516
+ hy,
2517
+ hz,
2518
+ nx,
2519
+ ny,
2520
+ nz,
2521
+ halfWidth,
2522
+ t,
2523
+ alpha,
2524
+ fr,
2525
+ fg,
2526
+ fb,
2527
+ ca,
2528
+ trailPosArr,
2529
+ trailNextArr,
2530
+ trailHalfWidthArr,
2531
+ trailUVArr,
2532
+ trailAlphaArr,
2533
+ trailColorArr
2534
+ );
2535
+ }
2536
+ if (useTwistPrevention && prevNormal && finalCount >= 2) {
2537
+ const nIdx = index * 3;
2538
+ const tx = finalPts[3] - finalPts[0];
2539
+ const ty = finalPts[4] - finalPts[1];
2540
+ const tz = finalPts[5] - finalPts[2];
2541
+ const tLen = Math.sqrt(tx * tx + ty * ty + tz * tz);
2542
+ if (tLen > 1e-4) {
2543
+ const ntx = tx / tLen;
2544
+ const nty = ty / tLen;
2545
+ const ntz = tz / tLen;
2546
+ let upx = 0, upy = 1, upz = 0;
2547
+ const dot = ntx * upx + nty * upy + ntz * upz;
2548
+ if (Math.abs(dot) > 0.999) {
2549
+ upx = 1;
2550
+ upy = 0;
2551
+ upz = 0;
2552
+ }
2553
+ let cnx = nty * upz - ntz * upy;
2554
+ let cny = ntz * upx - ntx * upz;
2555
+ let cnz = ntx * upy - nty * upx;
2556
+ const cnLen = Math.sqrt(cnx * cnx + cny * cny + cnz * cnz);
2557
+ if (cnLen > 1e-4) {
2558
+ cnx /= cnLen;
2559
+ cny /= cnLen;
2560
+ cnz /= cnLen;
2561
+ }
2562
+ const prevNx = prevNormal[nIdx];
2563
+ const prevNy = prevNormal[nIdx + 1];
2564
+ const prevNz = prevNormal[nIdx + 2];
2565
+ const hasPrev = prevNx !== 0 || prevNy !== 0 || prevNz !== 0;
2566
+ if (hasPrev) {
2567
+ const normalDot = cnx * prevNx + cny * prevNy + cnz * prevNz;
2568
+ if (normalDot < 0) {
2569
+ for (let s = 0; s < Math.min(finalCount, trailLength); s++) {
2570
+ const aIdx = vertBase + s * 2;
2571
+ const hw = trailHalfWidthArr[aIdx];
2572
+ trailHalfWidthArr[aIdx] = -hw;
2573
+ trailHalfWidthArr[aIdx + 1] = -hw;
2574
+ }
2575
+ cnx = -cnx;
2576
+ cny = -cny;
2577
+ cnz = -cnz;
2578
+ }
2579
+ }
2580
+ prevNormal[nIdx] = cnx;
2581
+ prevNormal[nIdx + 1] = cny;
2582
+ prevNormal[nIdx + 2] = cnz;
2583
+ }
2584
+ }
2585
+ } else if (historyCount[index] > 0 || prevFilled && prevFilled[index] > 0) {
2586
+ hasUpdates = true;
2587
+ historyCount[index] = 0;
2588
+ historyIndex[index] = 0;
2589
+ const clearSlots = prevFilled ? prevFilled[index] : trailLength;
2590
+ if (prevFilled) prevFilled[index] = 0;
2591
+ for (let s = 0; s < clearSlots; s++) {
2592
+ const vIdx = (vertBase + s * 2) * 3;
2593
+ const cIdx = (vertBase + s * 2) * 4;
2594
+ const aIdx = vertBase + s * 2;
2595
+ const uvIdxBase = (vertBase + s * 2) * 2;
2596
+ clearTrailVertex(
2597
+ vIdx,
2598
+ cIdx,
2599
+ aIdx,
2600
+ uvIdxBase,
2601
+ trailPosArr,
2602
+ trailNextArr,
2603
+ trailHalfWidthArr,
2604
+ trailUVArr,
2605
+ trailAlphaArr,
2606
+ trailColorArr,
2607
+ 0,
2608
+ 0,
2609
+ 0
2610
+ );
2611
+ }
2612
+ }
2613
+ }
2614
+ if (useRibbon && _ribbonCount >= 2 && _ribbonIndices) {
2615
+ hasUpdates = true;
2616
+ const leader = _ribbonIndices[0];
2617
+ const leaderVertBase = leader * verticesPerParticle;
2618
+ const controlCount = _ribbonCount;
2619
+ const filledCount = Math.min(
2620
+ trailLength,
2621
+ Math.max(controlCount * 4, controlCount)
2622
+ );
2623
+ const chainSize = filledCount * 3;
2624
+ if (!_rawPoints || _rawPointsSize < chainSize) {
2625
+ _rawPoints = new Float32Array(chainSize);
2626
+ _rawPointsSize = chainSize;
2627
+ }
2628
+ if (controlCount === 2) {
2629
+ const p0Idx = _ribbonIndices[0] * 3;
2630
+ const p1Idx = _ribbonIndices[1] * 3;
2631
+ for (let i = 0; i < filledCount; i++) {
2632
+ const t = i / (filledCount - 1);
2633
+ _rawPoints[i * 3] = positionArr[p0Idx] + t * (positionArr[p1Idx] - positionArr[p0Idx]);
2634
+ _rawPoints[i * 3 + 1] = positionArr[p0Idx + 1] + t * (positionArr[p1Idx + 1] - positionArr[p0Idx + 1]);
2635
+ _rawPoints[i * 3 + 2] = positionArr[p0Idx + 2] + t * (positionArr[p1Idx + 2] - positionArr[p0Idx + 2]);
2636
+ }
2637
+ } else {
2638
+ const segments = controlCount - 1;
2639
+ const ptsPerSeg = Math.max(1, Math.floor((filledCount - 1) / segments));
2640
+ let wi = 0;
2641
+ for (let seg = 0; seg < segments && wi < filledCount; seg++) {
2642
+ const i0 = Math.max(0, seg - 1);
2643
+ const i1 = seg;
2644
+ const i2 = Math.min(controlCount - 1, seg + 1);
2645
+ const i3 = Math.min(controlCount - 1, seg + 2);
2646
+ const p0i = _ribbonIndices[i0] * 3;
2647
+ const p1i = _ribbonIndices[i1] * 3;
2648
+ const p2i = _ribbonIndices[i2] * 3;
2649
+ const p3i = _ribbonIndices[i3] * 3;
2650
+ const subCount = seg === segments - 1 ? filledCount - wi : ptsPerSeg;
2651
+ for (let sub = 0; sub < subCount && wi < filledCount; sub++) {
2652
+ const t = sub / subCount;
2653
+ catmullRom(
2654
+ _rawPoints,
2655
+ wi * 3,
2656
+ positionArr[p0i],
2657
+ positionArr[p0i + 1],
2658
+ positionArr[p0i + 2],
2659
+ positionArr[p1i],
2660
+ positionArr[p1i + 1],
2661
+ positionArr[p1i + 2],
2662
+ positionArr[p2i],
2663
+ positionArr[p2i + 1],
2664
+ positionArr[p2i + 2],
2665
+ positionArr[p3i],
2666
+ positionArr[p3i + 1],
2667
+ positionArr[p3i + 2],
2668
+ t
2669
+ );
2670
+ wi++;
2671
+ }
2672
+ }
2673
+ if (wi > 0) {
2674
+ const lastPIdx = _ribbonIndices[controlCount - 1] * 3;
2675
+ _rawPoints[(wi - 1) * 3] = positionArr[lastPIdx];
2676
+ _rawPoints[(wi - 1) * 3 + 1] = positionArr[lastPIdx + 1];
2677
+ _rawPoints[(wi - 1) * 3 + 2] = positionArr[lastPIdx + 2];
2678
+ }
2679
+ }
2680
+ const leaderBase = leader * SCALAR_STRIDE;
2681
+ const leaderCr = trailScalarArr[leaderBase + S_COLOR_R];
2682
+ const leaderCg = trailScalarArr[leaderBase + S_COLOR_G];
2683
+ const leaderCb = trailScalarArr[leaderBase + S_COLOR_B];
2684
+ const leaderCa = trailScalarArr[leaderBase + S_COLOR_A];
2685
+ const leaderPrevFilled = prevFilled ? prevFilled[leader] : trailLength;
2686
+ if (prevFilled) prevFilled[leader] = filledCount;
2687
+ for (let s = 0; s < trailLength; s++) {
2688
+ const vIdx = (leaderVertBase + s * 2) * 3;
2689
+ const cIdx = (leaderVertBase + s * 2) * 4;
2690
+ const aIdx = leaderVertBase + s * 2;
2691
+ const uvIdxBase = (leaderVertBase + s * 2) * 2;
2692
+ if (s >= filledCount) {
2693
+ if (s >= leaderPrevFilled) break;
2694
+ clearTrailVertex(
2695
+ vIdx,
2696
+ cIdx,
2697
+ aIdx,
2698
+ uvIdxBase,
2699
+ trailPosArr,
2700
+ trailNextArr,
2701
+ trailHalfWidthArr,
2702
+ trailUVArr,
2703
+ trailAlphaArr,
2704
+ trailColorArr,
2705
+ 0,
2706
+ 0,
2707
+ 0
2708
+ );
2709
+ continue;
2710
+ }
2711
+ const ptIdx = s * 3;
2712
+ const ptx = _rawPoints[ptIdx];
2713
+ const pty = _rawPoints[ptIdx + 1];
2714
+ const ptz = _rawPoints[ptIdx + 2];
2715
+ let nx, ny, nz;
2716
+ if (s > 0 && s < filledCount - 1) {
2717
+ const px2 = _rawPoints[(s - 1) * 3];
2718
+ const py2 = _rawPoints[(s - 1) * 3 + 1];
2719
+ const pz2 = _rawPoints[(s - 1) * 3 + 2];
2720
+ const nx2 = _rawPoints[(s + 1) * 3];
2721
+ const ny2 = _rawPoints[(s + 1) * 3 + 1];
2722
+ const nz2 = _rawPoints[(s + 1) * 3 + 2];
2723
+ const atx = nx2 - px2;
2724
+ const aty = ny2 - py2;
2725
+ const atz = nz2 - pz2;
2726
+ const atLen = Math.sqrt(atx * atx + aty * aty + atz * atz);
2727
+ if (atLen > 1e-4) {
2728
+ nx = ptx + atx / atLen;
2729
+ ny = pty + aty / atLen;
2730
+ nz = ptz + atz / atLen;
2731
+ } else {
2732
+ nx = _rawPoints[(s + 1) * 3];
2733
+ ny = _rawPoints[(s + 1) * 3 + 1];
2734
+ nz = _rawPoints[(s + 1) * 3 + 2];
2735
+ }
2736
+ } else if (s < filledCount - 1) {
2737
+ nx = _rawPoints[(s + 1) * 3];
2738
+ ny = _rawPoints[(s + 1) * 3 + 1];
2739
+ nz = _rawPoints[(s + 1) * 3 + 2];
2740
+ } else if (filledCount >= 2) {
2741
+ const prevX = _rawPoints[(s - 1) * 3];
2742
+ const prevY = _rawPoints[(s - 1) * 3 + 1];
2743
+ const prevZ = _rawPoints[(s - 1) * 3 + 2];
2744
+ nx = ptx + (ptx - prevX);
2745
+ ny = pty + (pty - prevY);
2746
+ nz = ptz + (ptz - prevZ);
2747
+ } else {
2748
+ nx = ptx;
2749
+ ny = pty + 1e-3;
2750
+ nz = ptz;
2751
+ }
2752
+ const t = filledCount > 1 ? s / (filledCount - 1) : 0;
2753
+ let ribbonTimeFade = 1;
2754
+ if (maxTime > 0 && controlCount >= 2) {
2755
+ const ctrlF = t * (controlCount - 1);
2756
+ const ctrlLo = Math.min(Math.floor(ctrlF), controlCount - 1);
2757
+ const ctrlHi = Math.min(ctrlLo + 1, controlCount - 1);
2758
+ const frac = ctrlF - ctrlLo;
2759
+ const ageLo = now - generalData.creationTimes[_ribbonIndices[ctrlLo]];
2760
+ const ageHi = now - generalData.creationTimes[_ribbonIndices[ctrlHi]];
2761
+ const age = ageLo + (ageHi - ageLo) * frac;
2762
+ ribbonTimeFade = 1 - Math.min(age / maxTimeMs, 1);
2763
+ }
2764
+ const widthScale = trailWidthCurveFn(t);
2765
+ const opacityScale = trailOpacityCurveFn(t);
2766
+ const halfWidth = trailConfig.width * widthScale * 0.5;
2767
+ const alpha = leaderCa * opacityScale * ribbonTimeFade;
2768
+ const fr = trailColorOverTrailFns ? leaderCr * trailColorOverTrailFns.r(t) : leaderCr;
2769
+ const fg = trailColorOverTrailFns ? leaderCg * trailColorOverTrailFns.g(t) : leaderCg;
2770
+ const fb = trailColorOverTrailFns ? leaderCb * trailColorOverTrailFns.b(t) : leaderCb;
2771
+ writeTrailVertex(
2772
+ vIdx,
2773
+ cIdx,
2774
+ aIdx,
2775
+ uvIdxBase,
2776
+ ptx,
2777
+ pty,
2778
+ ptz,
2779
+ nx,
2780
+ ny,
2781
+ nz,
2782
+ halfWidth,
2783
+ t,
2784
+ alpha,
2785
+ fr,
2786
+ fg,
2787
+ fb,
2788
+ leaderCa,
2789
+ trailPosArr,
2790
+ trailNextArr,
2791
+ trailHalfWidthArr,
2792
+ trailUVArr,
2793
+ trailAlphaArr,
2794
+ trailColorArr
2795
+ );
2796
+ }
2797
+ if (useTwistPrevention && prevNormal && filledCount >= 2) {
2798
+ const nIdx = leader * 3;
2799
+ const tx = _rawPoints[3] - _rawPoints[0];
2800
+ const ty = _rawPoints[4] - _rawPoints[1];
2801
+ const tz = _rawPoints[5] - _rawPoints[2];
2802
+ const tLen = Math.sqrt(tx * tx + ty * ty + tz * tz);
2803
+ if (tLen > 1e-4) {
2804
+ const ntx = tx / tLen;
2805
+ const nty = ty / tLen;
2806
+ const ntz = tz / tLen;
2807
+ let upx = 0, upy = 1, upz = 0;
2808
+ const dot = ntx * upx + nty * upy + ntz * upz;
2809
+ if (Math.abs(dot) > 0.999) {
2810
+ upx = 1;
2811
+ upy = 0;
2812
+ upz = 0;
2813
+ }
2814
+ let cnx = nty * upz - ntz * upy;
2815
+ let cny = ntz * upx - ntx * upz;
2816
+ let cnz = ntx * upy - nty * upx;
2817
+ const cnLen = Math.sqrt(cnx * cnx + cny * cny + cnz * cnz);
2818
+ if (cnLen > 1e-4) {
2819
+ cnx /= cnLen;
2820
+ cny /= cnLen;
2821
+ cnz /= cnLen;
2822
+ }
2823
+ const prevNx = prevNormal[nIdx];
2824
+ const prevNy = prevNormal[nIdx + 1];
2825
+ const prevNz = prevNormal[nIdx + 2];
2826
+ const hasPrev = prevNx !== 0 || prevNy !== 0 || prevNz !== 0;
2827
+ if (hasPrev) {
2828
+ const normalDot = cnx * prevNx + cny * prevNy + cnz * prevNz;
2829
+ if (normalDot < 0) {
2830
+ for (let s = 0; s < Math.min(filledCount, trailLength); s++) {
2831
+ const aIdx = leaderVertBase + s * 2;
2832
+ const hw = trailHalfWidthArr[aIdx];
2833
+ trailHalfWidthArr[aIdx] = -hw;
2834
+ trailHalfWidthArr[aIdx + 1] = -hw;
2835
+ }
2836
+ cnx = -cnx;
2837
+ cny = -cny;
2838
+ cnz = -cnz;
2839
+ }
2840
+ }
2841
+ prevNormal[nIdx] = cnx;
2842
+ prevNormal[nIdx + 1] = cny;
2843
+ prevNormal[nIdx + 2] = cnz;
2844
+ }
2845
+ }
2846
+ for (let ri = 1; ri < _ribbonCount; ri++) {
2847
+ const pIdx = _ribbonIndices[ri];
2848
+ const pVertBase = pIdx * verticesPerParticle;
2849
+ const pClearSlots = prevFilled ? prevFilled[pIdx] : trailLength;
2850
+ if (prevFilled) prevFilled[pIdx] = 0;
2851
+ for (let s = 0; s < pClearSlots; s++) {
2852
+ const vIdx = (pVertBase + s * 2) * 3;
2853
+ const cIdx = (pVertBase + s * 2) * 4;
2854
+ const aIdx = pVertBase + s * 2;
2855
+ const uvIdxBase = (pVertBase + s * 2) * 2;
2856
+ clearTrailVertex(
2857
+ vIdx,
2858
+ cIdx,
2859
+ aIdx,
2860
+ uvIdxBase,
2861
+ trailPosArr,
2862
+ trailNextArr,
2863
+ trailHalfWidthArr,
2864
+ trailUVArr,
2865
+ trailAlphaArr,
2866
+ trailColorArr,
2867
+ 0,
2868
+ 0,
2869
+ 0
2870
+ );
2871
+ }
2872
+ }
2873
+ }
2874
+ if (hasUpdates) {
2875
+ trailPositionAttr.needsUpdate = true;
2876
+ trailAlphaAttr.needsUpdate = true;
2877
+ trailColorAttr.needsUpdate = true;
2878
+ trailNextAttrCached.needsUpdate = true;
2879
+ trailHalfWidthAttrCached.needsUpdate = true;
2880
+ trailUVAttrCached.needsUpdate = true;
2881
+ }
2882
+ };
2883
+ var updateParticleSystems = (cycleData) => {
2884
+ createdParticleSystems.forEach(
2885
+ (props) => updateParticleSystemInstance(props, cycleData)
2886
+ );
2887
+ };
2888
+
2889
+ // src/js/effects/three-particles/three-particles-serialization.ts
2890
+ var SERIALIZATION_VERSION = 1;
2891
+ var reverseBlendingMap = new Map(
2892
+ Object.entries(blendingMap).map(([k, v]) => [
2893
+ v,
2894
+ k
2895
+ ])
2896
+ );
2897
+ var reverseCurveFunctionMap = /* @__PURE__ */ new Map();
2898
+ for (const [id, fn] of Object.entries(curveFunctionIdMap)) {
2899
+ if (fn) reverseCurveFunctionMap.set(fn, id);
2900
+ }
2901
+ function serializeAny(value, key) {
2902
+ if (value === null || value === void 0) return value;
2903
+ if (value instanceof THREE3.Vector3)
2904
+ return { x: value.x, y: value.y, z: value.z };
2905
+ if (value instanceof THREE3.Vector2) return { x: value.x, y: value.y };
2906
+ if (value instanceof THREE3.Texture) return void 0;
2907
+ if (typeof value === "function") return void 0;
2908
+ if (Array.isArray(value)) return value.map((item) => serializeAny(item));
2909
+ if (typeof value === "object") {
2910
+ const obj = value;
2911
+ if (obj["type"] === "EASING" /* EASING */ && typeof obj["curveFunction"] === "function") {
2912
+ const id = reverseCurveFunctionMap.get(
2913
+ obj["curveFunction"]
2914
+ );
2915
+ if (!id) {
2916
+ throw new Error(
2917
+ "Cannot serialize a custom curveFunction. Use a predefined CurveFunctionId instead."
2918
+ );
2919
+ }
2920
+ return {
2921
+ type: "EASING" /* EASING */,
2922
+ curveFunctionId: id,
2923
+ ...obj["scale"] !== void 0 ? { scale: obj["scale"] } : {}
2924
+ };
2925
+ }
2926
+ const result = {};
2927
+ for (const [k, v] of Object.entries(obj)) {
2928
+ if (k === "onUpdate" || k === "onComplete") continue;
2929
+ if (k === "blending" && typeof v === "number") {
2930
+ result[k] = reverseBlendingMap.get(v) ?? v;
2931
+ continue;
2932
+ }
2933
+ const serialized = serializeAny(v);
2934
+ if (serialized !== void 0) result[k] = serialized;
2935
+ }
2936
+ return result;
2937
+ }
2938
+ return value;
2939
+ }
2940
+ function serializeParticleSystem(config) {
2941
+ const serialized = serializeAny(config);
2942
+ return JSON.stringify({ _version: SERIALIZATION_VERSION, ...serialized });
2943
+ }
2944
+ function deserializeCurve(raw) {
2945
+ const obj = raw;
2946
+ if (Array.isArray(obj["bezierPoints"]) && !obj["type"]) {
2947
+ return { type: "BEZIER" /* BEZIER */, ...obj };
2948
+ }
2949
+ if (obj["type"] === "EASING" /* EASING */) {
2950
+ const id = obj["curveFunctionId"];
2951
+ const fn = getCurveFunction(id);
2952
+ if (!fn) {
2953
+ throw new Error(
2954
+ `Unknown curveFunctionId: "${id}". Use a value from CurveFunctionId.`
2955
+ );
2956
+ }
2957
+ const curve = {
2958
+ type: "EASING" /* EASING */,
2959
+ curveFunction: fn
2960
+ };
2961
+ if (obj["scale"] !== void 0) curve.scale = obj["scale"];
2962
+ return curve;
2963
+ }
2964
+ return obj;
2965
+ }
2966
+ function deserializeCurveOrValue(value) {
2967
+ if (typeof value === "number") return value;
2968
+ if (!value || typeof value !== "object") return value;
2969
+ const obj = value;
2970
+ const looksLikeCurve = Array.isArray(obj["bezierPoints"]) || obj["type"] === "BEZIER" /* BEZIER */ || obj["type"] === "EASING" /* EASING */ || typeof obj["curveFunctionId"] === "string";
2971
+ if (looksLikeCurve) return deserializeCurve(obj);
2972
+ return obj;
2973
+ }
2974
+ function deserializeVector3(raw) {
2975
+ if (!raw || typeof raw !== "object") return void 0;
2976
+ const { x = 0, y = 0, z = 0 } = raw;
2977
+ return new THREE3.Vector3(x, y, z);
2978
+ }
2979
+ function deserializeVector2(raw) {
2980
+ if (!raw || typeof raw !== "object") return void 0;
2981
+ const { x = 1, y = 1 } = raw;
2982
+ return new THREE3.Vector2(x, y);
2983
+ }
2984
+ function deserializeConfig(raw) {
2985
+ const config = {};
2986
+ if (raw["transform"] && typeof raw["transform"] === "object") {
2987
+ const t = raw["transform"];
2988
+ config.transform = {
2989
+ position: deserializeVector3(t["position"]),
2990
+ rotation: deserializeVector3(t["rotation"]),
2991
+ scale: deserializeVector3(t["scale"])
2992
+ };
2993
+ }
2994
+ for (const field of [
2995
+ "duration",
2996
+ "looping",
2997
+ "gravity",
2998
+ "simulationSpace",
2999
+ "simulationBackend",
3000
+ "maxParticles"
3001
+ ]) {
3002
+ if (field in raw) config[field] = raw[field];
3003
+ }
3004
+ for (const field of [
3005
+ "startDelay",
3006
+ "startLifetime",
3007
+ "startSpeed",
3008
+ "startSize",
3009
+ "startOpacity",
3010
+ "startRotation"
3011
+ ]) {
3012
+ if (field in raw)
3013
+ config[field] = deserializeCurveOrValue(raw[field]);
3014
+ }
3015
+ if ("startColor" in raw)
3016
+ config.startColor = raw["startColor"];
3017
+ if (raw["emission"] && typeof raw["emission"] === "object") {
3018
+ const e = raw["emission"];
3019
+ config.emission = {
3020
+ rateOverTime: deserializeCurveOrValue(e["rateOverTime"]),
3021
+ rateOverDistance: deserializeCurveOrValue(
3022
+ e["rateOverDistance"]
3023
+ ),
3024
+ bursts: Array.isArray(e["bursts"]) ? e["bursts"] : []
3025
+ };
3026
+ }
3027
+ if ("shape" in raw)
3028
+ config.shape = raw["shape"];
3029
+ if (raw["renderer"] && typeof raw["renderer"] === "object") {
3030
+ const r = raw["renderer"];
3031
+ const blending = typeof r["blending"] === "string" ? blendingMap[r["blending"]] ?? THREE3.NormalBlending : r["blending"] ?? THREE3.NormalBlending;
3032
+ config.renderer = { ...r, blending };
3033
+ }
3034
+ if (raw["velocityOverLifetime"] && typeof raw["velocityOverLifetime"] === "object") {
3035
+ const vol = raw["velocityOverLifetime"];
3036
+ const deserializeAxis = (axis) => {
3037
+ if (!axis || typeof axis !== "object") return {};
3038
+ const a = axis;
3039
+ return {
3040
+ ...a["x"] !== void 0 ? { x: deserializeCurveOrValue(a["x"]) } : {},
3041
+ ...a["y"] !== void 0 ? { y: deserializeCurveOrValue(a["y"]) } : {},
3042
+ ...a["z"] !== void 0 ? { z: deserializeCurveOrValue(a["z"]) } : {}
3043
+ };
3044
+ };
3045
+ config.velocityOverLifetime = {
3046
+ isActive: vol["isActive"] ?? false,
3047
+ linear: deserializeAxis(vol["linear"]),
3048
+ orbital: deserializeAxis(vol["orbital"])
3049
+ };
3050
+ }
3051
+ for (const field of ["sizeOverLifetime", "opacityOverLifetime"]) {
3052
+ if (raw[field] && typeof raw[field] === "object") {
3053
+ const m = raw[field];
3054
+ config[field] = {
3055
+ isActive: m["isActive"] ?? false,
3056
+ lifetimeCurve: deserializeCurve(m["lifetimeCurve"])
3057
+ };
3058
+ }
3059
+ }
3060
+ if (raw["colorOverLifetime"] && typeof raw["colorOverLifetime"] === "object") {
3061
+ const col = raw["colorOverLifetime"];
3062
+ config.colorOverLifetime = {
3063
+ isActive: col["isActive"] ?? true,
3064
+ r: deserializeCurve(col["r"]),
3065
+ g: deserializeCurve(col["g"]),
3066
+ b: deserializeCurve(col["b"])
3067
+ };
3068
+ }
3069
+ if (raw["rotationOverLifetime"] && typeof raw["rotationOverLifetime"] === "object") {
3070
+ config.rotationOverLifetime = raw["rotationOverLifetime"];
3071
+ }
3072
+ if (raw["noise"] && typeof raw["noise"] === "object") {
3073
+ config.noise = raw["noise"];
3074
+ }
3075
+ if (raw["textureSheetAnimation"] && typeof raw["textureSheetAnimation"] === "object") {
3076
+ const tsa = raw["textureSheetAnimation"];
3077
+ config.textureSheetAnimation = {
3078
+ ...tsa,
3079
+ tiles: deserializeVector2(tsa["tiles"]),
3080
+ startFrame: deserializeCurveOrValue(tsa["startFrame"])
3081
+ };
3082
+ }
3083
+ if (Array.isArray(raw["subEmitters"])) {
3084
+ config.subEmitters = raw["subEmitters"].map((se) => ({
3085
+ ...se,
3086
+ config: deserializeConfig(se["config"])
3087
+ }));
3088
+ }
3089
+ if (Array.isArray(raw["forceFields"])) {
3090
+ config.forceFields = raw["forceFields"].map((ff) => {
3091
+ const result = {};
3092
+ if ("isActive" in ff) result.isActive = ff["isActive"];
3093
+ if ("type" in ff) result.type = ff["type"];
3094
+ if (ff["position"]) result.position = deserializeVector3(ff["position"]);
3095
+ if (ff["direction"])
3096
+ result.direction = deserializeVector3(ff["direction"]);
3097
+ if ("strength" in ff)
3098
+ result.strength = deserializeCurveOrValue(
3099
+ ff["strength"]
3100
+ );
3101
+ if ("range" in ff)
3102
+ result.range = ff["range"] === null ? Infinity : ff["range"];
3103
+ if ("falloff" in ff) result.falloff = ff["falloff"];
3104
+ return result;
3105
+ });
3106
+ }
3107
+ if (Array.isArray(raw["collisionPlanes"])) {
3108
+ config.collisionPlanes = raw["collisionPlanes"].map(
3109
+ (cp) => {
3110
+ const result = {};
3111
+ if ("isActive" in cp) result.isActive = cp["isActive"];
3112
+ if ("mode" in cp) result.mode = cp["mode"];
3113
+ if (cp["position"])
3114
+ result.position = deserializeVector3(cp["position"]);
3115
+ if (cp["normal"]) result.normal = deserializeVector3(cp["normal"]);
3116
+ if ("dampen" in cp) result.dampen = cp["dampen"];
3117
+ if ("lifetimeLoss" in cp)
3118
+ result.lifetimeLoss = cp["lifetimeLoss"];
3119
+ return result;
3120
+ }
3121
+ );
3122
+ }
3123
+ for (const key of Object.keys(raw)) {
3124
+ if (!(key in config) && key !== "_version" && raw[key] !== null) {
3125
+ config[key] = raw[key];
3126
+ }
3127
+ }
3128
+ return config;
3129
+ }
3130
+ function deserializeParticleSystem(json) {
3131
+ const parsed = JSON.parse(json);
3132
+ const { _version: _, ...raw } = parsed;
3133
+ return deserializeConfig(raw);
3134
+ }
3135
+
3136
+ export { CollisionPlaneMode, CurveFunctionId, EmitFrom, ForceFieldFalloff, ForceFieldType, LifeTimeCurve, REVISION, RendererType, SCALAR_STRIDE, S_COLOR_A, S_COLOR_B, S_COLOR_G, S_COLOR_R, S_IS_ACTIVE, S_LIFETIME, S_ROTATION, S_SIZE, S_START_FRAME, S_START_LIFETIME, Shape, SimulationBackend, SimulationSpace, SubEmitterTrigger, TimeMode, applyModifiers, assertNamed, blendingMap, calculateRandomPositionAndVelocityOnBox, calculateRandomPositionAndVelocityOnCircle, calculateRandomPositionAndVelocityOnCone, calculateRandomPositionAndVelocityOnRectangle, calculateRandomPositionAndVelocityOnSphere, calculateValue, createBezierCurveFunction, createDefaultMeshTexture, createDefaultParticleTexture, createParticleSystem, curveFunctionIdMap, deserializeParticleSystem, getBezierCacheSize, getCurveFunction, getCurveFunctionFromConfig, getDefaultParticleSystemConfig, isComputeCapableRenderer, isLifeTimeCurve, linearToSRGB, normalizeBackgroundToVector3, normalizeDepthTextureValue, normalizeTextureValue, normalizeVector2Value, registerTSLMaterialFactory, removeBezierCurveFunction, resolveSimulationBackend, resolveWebGPUEffectiveRendererType, rgbSRGBToLinear, sRGBToLinear, serializeParticleSystem, updateParticleSystems };
3137
+ //# sourceMappingURL=index.js.map
3138
+ //# sourceMappingURL=index.js.map