@newkrok/three-particles 2.15.2 → 2.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/webgpu.js ADDED
@@ -0,0 +1,1664 @@
1
+ import { registerTSLMaterialFactory } from '@newkrok/three-particles';
2
+ import { Fn, mod, float, floor, dot, vec3, step, min, max, vec4, vec2, abs, round, If, texture, screenUV, smoothstep, length, Discard, sRGBTransferEOTF, cross, attribute, modelViewMatrix, positionLocal, varyingProperty, pointUV, cos, sin, normalLocal, cameraProjectionMatrix, uv, uniform, normalize, cameraPosition, cameraViewMatrix, mix, storage, instanceIndex, compute, fract, Loop, Continue } from 'three/tsl';
3
+ import * as THREE from 'three';
4
+ import { DoubleSide, Vector3, NoColorSpace, DataTexture } from 'three';
5
+ import { PointsNodeMaterial, MeshBasicNodeMaterial, StorageBufferAttribute, StorageInstancedBufferAttribute } from 'three/webgpu';
6
+
7
+ // src/webgpu.ts
8
+ var PLANE_STRIDE = 12;
9
+ var MAX_COLLISION_PLANES = 16;
10
+ var COLLISION_PLANE_DATA_SIZE = MAX_COLLISION_PLANES * PLANE_STRIDE;
11
+ var _encodeBuf = null;
12
+ function encodeCollisionPlanesForGPU(planes) {
13
+ if (!_encodeBuf || _encodeBuf.length !== COLLISION_PLANE_DATA_SIZE) {
14
+ _encodeBuf = new Float32Array(COLLISION_PLANE_DATA_SIZE);
15
+ }
16
+ const data = _encodeBuf;
17
+ data.fill(0);
18
+ const count = Math.min(planes.length, MAX_COLLISION_PLANES);
19
+ for (let i = 0; i < count; i++) {
20
+ const cp = planes[i];
21
+ const base = i * PLANE_STRIDE;
22
+ data[base] = cp.isActive ? 1 : 0;
23
+ let modeCode = 0;
24
+ if (cp.mode === "CLAMP" /* CLAMP */) modeCode = 1;
25
+ else if (cp.mode === "BOUNCE" /* BOUNCE */) modeCode = 2;
26
+ data[base + 1] = modeCode;
27
+ data[base + 2] = cp.position.x;
28
+ data[base + 3] = cp.position.y;
29
+ data[base + 4] = cp.position.z;
30
+ data[base + 5] = cp.normal.x;
31
+ data[base + 6] = cp.normal.y;
32
+ data[base + 7] = cp.normal.z;
33
+ data[base + 8] = cp.dampen;
34
+ data[base + 9] = cp.lifetimeLoss;
35
+ data[base + 10] = 0;
36
+ data[base + 11] = 0;
37
+ }
38
+ return data;
39
+ }
40
+ function createCollisionPlaneTSL(sCurveData, collisionPlaneOffset, collisionPlaneCount) {
41
+ const count = Math.min(collisionPlaneCount, MAX_COLLISION_PLANES);
42
+ const uCollisionPlaneCount = uniform(float(count));
43
+ const cpBase = collisionPlaneOffset;
44
+ const applyCollisionPlanesTSL = Fn(
45
+ ({
46
+ pos,
47
+ vel,
48
+ oiaVec,
49
+ sColorNode,
50
+ ps,
51
+ startLife,
52
+ particleIdx,
53
+ sOrbitalIsActiveNode
54
+ }) => {
55
+ Loop(uCollisionPlaneCount, ({ i }) => {
56
+ const base = i.mul(PLANE_STRIDE).add(cpBase);
57
+ const isActive = sCurveData.element(base);
58
+ If(isActive.lessThan(0.5), () => {
59
+ Continue();
60
+ });
61
+ const mode = sCurveData.element(base.add(1));
62
+ const planePos = vec3(
63
+ sCurveData.element(base.add(2)),
64
+ sCurveData.element(base.add(3)),
65
+ sCurveData.element(base.add(4))
66
+ );
67
+ const planeNormal = vec3(
68
+ sCurveData.element(base.add(5)),
69
+ sCurveData.element(base.add(6)),
70
+ sCurveData.element(base.add(7))
71
+ );
72
+ const dampen = sCurveData.element(base.add(8));
73
+ const lifetimeLoss = sCurveData.element(base.add(9));
74
+ const toParticle = pos.sub(planePos);
75
+ const signedDist = dot(toParticle, planeNormal);
76
+ If(signedDist.lessThan(0), () => {
77
+ If(mode.lessThan(0.5), () => {
78
+ ps.x.assign(startLife.add(float(1)));
79
+ });
80
+ If(mode.greaterThanEqual(0.5).and(mode.lessThan(1.5)), () => {
81
+ pos.assign(pos.sub(planeNormal.mul(signedDist)));
82
+ const velDotN = dot(vel, planeNormal);
83
+ If(velDotN.lessThan(0), () => {
84
+ vel.assign(vel.sub(planeNormal.mul(velDotN)));
85
+ });
86
+ });
87
+ If(mode.greaterThanEqual(1.5), () => {
88
+ pos.assign(pos.sub(planeNormal.mul(signedDist)));
89
+ const vDotN = dot(vel, planeNormal);
90
+ const reflected = vel.sub(planeNormal.mul(vDotN.mul(2)));
91
+ vel.assign(reflected.mul(dampen));
92
+ If(lifetimeLoss.greaterThan(0), () => {
93
+ ps.x.assign(ps.x.add(lifetimeLoss.mul(startLife).mul(1e3)));
94
+ });
95
+ });
96
+ });
97
+ });
98
+ },
99
+ "void"
100
+ );
101
+ return {
102
+ /** Uniform for the active collision plane count. */
103
+ countUniform: uCollisionPlaneCount,
104
+ /** TSL function to call in the compute kernel: apply({ pos, vel, ... }) */
105
+ apply: applyCollisionPlanesTSL
106
+ };
107
+ }
108
+
109
+ // src/js/effects/three-particles/three-particles-bezier.ts
110
+ var cache = [];
111
+ var nCr = (n, k) => {
112
+ let z = 1;
113
+ for (let i = 1; i <= k; i++) z *= (n + 1 - i) / i;
114
+ return z;
115
+ };
116
+ var createBezierCurveFunction = (particleSystemId, bezierPoints) => {
117
+ const cacheEntry = cache.find((item) => item.bezierPoints === bezierPoints);
118
+ if (cacheEntry) {
119
+ if (!cacheEntry.referencedBy.includes(particleSystemId))
120
+ cacheEntry.referencedBy.push(particleSystemId);
121
+ return cacheEntry.curveFunction;
122
+ }
123
+ const entry = {
124
+ referencedBy: [particleSystemId],
125
+ bezierPoints,
126
+ curveFunction: (percentage) => {
127
+ if (percentage < 0) return bezierPoints[0].y;
128
+ if (percentage > 1) return bezierPoints[bezierPoints.length - 1].y;
129
+ let start = 0;
130
+ let stop = bezierPoints.length - 1;
131
+ bezierPoints.find((point, index) => {
132
+ const result = percentage < (point.percentage ?? 0);
133
+ if (result) stop = index;
134
+ else if (point.percentage !== void 0) start = index;
135
+ return result;
136
+ });
137
+ const n = stop - start;
138
+ const calculatedPercentage = (percentage - (bezierPoints[start].percentage ?? 0)) / ((bezierPoints[stop].percentage ?? 1) - (bezierPoints[start].percentage ?? 0));
139
+ let value = 0;
140
+ for (let i = 0; i <= n; i++) {
141
+ const p = bezierPoints[start + i];
142
+ const c = nCr(n, i) * Math.pow(1 - calculatedPercentage, n - i) * Math.pow(calculatedPercentage, i);
143
+ value += c * p.y;
144
+ }
145
+ return value;
146
+ }
147
+ };
148
+ cache.push(entry);
149
+ return entry.curveFunction;
150
+ };
151
+
152
+ // src/js/effects/three-particles/three-particles-utils.ts
153
+ var isLifeTimeCurve = (value) => {
154
+ return typeof value !== "number" && "type" in value;
155
+ };
156
+ var getCurveFunctionFromConfig = (particleSystemId, lifetimeCurve) => {
157
+ if (lifetimeCurve.type === "BEZIER" /* BEZIER */) {
158
+ return createBezierCurveFunction(
159
+ particleSystemId,
160
+ lifetimeCurve.bezierPoints
161
+ );
162
+ }
163
+ if (lifetimeCurve.type === "EASING" /* EASING */) {
164
+ return lifetimeCurve.curveFunction;
165
+ }
166
+ throw new Error(`Unsupported value type: ${lifetimeCurve}`);
167
+ };
168
+ var calculateValue = (particleSystemId, value, time = 0) => {
169
+ if (typeof value === "number") {
170
+ return value;
171
+ }
172
+ if ("min" in value && "max" in value) {
173
+ if (value.min === value.max) {
174
+ return value.min ?? 0;
175
+ }
176
+ return THREE.MathUtils.randFloat(value.min ?? 0, value.max ?? 1);
177
+ }
178
+ const lifetimeCurve = value;
179
+ return getCurveFunctionFromConfig(particleSystemId, lifetimeCurve)(time) * (lifetimeCurve.scale ?? 1);
180
+ };
181
+
182
+ // src/js/effects/three-particles/webgpu/compute-force-fields.ts
183
+ var FIELD_STRIDE = 12;
184
+ var MAX_FORCE_FIELDS = 16;
185
+ var FORCE_FIELD_DATA_SIZE = MAX_FORCE_FIELDS * FIELD_STRIDE;
186
+ var GPU_INFINITY = 1e10;
187
+ var _encodeBuf2 = null;
188
+ function encodeForceFieldsForGPU(forceFields, particleSystemId, systemLifetimePercentage) {
189
+ if (!_encodeBuf2 || _encodeBuf2.length !== FORCE_FIELD_DATA_SIZE) {
190
+ _encodeBuf2 = new Float32Array(FORCE_FIELD_DATA_SIZE);
191
+ }
192
+ const data = _encodeBuf2;
193
+ data.fill(0);
194
+ const count = Math.min(forceFields.length, MAX_FORCE_FIELDS);
195
+ for (let i = 0; i < count; i++) {
196
+ const ff = forceFields[i];
197
+ const base = i * FIELD_STRIDE;
198
+ data[base] = ff.isActive ? 1 : 0;
199
+ data[base + 1] = ff.type === "POINT" /* POINT */ ? 0 : 1;
200
+ data[base + 2] = ff.position.x;
201
+ data[base + 3] = ff.position.y;
202
+ data[base + 4] = ff.position.z;
203
+ data[base + 5] = ff.direction.x;
204
+ data[base + 6] = ff.direction.y;
205
+ data[base + 7] = ff.direction.z;
206
+ data[base + 8] = calculateValue(
207
+ particleSystemId,
208
+ ff.strength,
209
+ systemLifetimePercentage
210
+ );
211
+ data[base + 9] = ff.range === Infinity ? GPU_INFINITY : ff.range;
212
+ let falloffCode = 0;
213
+ if (ff.falloff === "LINEAR" /* LINEAR */) falloffCode = 1;
214
+ else if (ff.falloff === "QUADRATIC" /* QUADRATIC */) falloffCode = 2;
215
+ data[base + 10] = falloffCode;
216
+ data[base + 11] = 0;
217
+ }
218
+ return data;
219
+ }
220
+ function createForceFieldTSL(sCurveData, forceFieldOffset, forceFieldCount) {
221
+ const count = Math.min(forceFieldCount, MAX_FORCE_FIELDS);
222
+ const uForceFieldCount = uniform(float(count));
223
+ const ffBase = forceFieldOffset;
224
+ const applyForceFieldsTSL = Fn(
225
+ ({
226
+ pos,
227
+ vel,
228
+ delta
229
+ }) => {
230
+ Loop(uForceFieldCount, ({ i }) => {
231
+ const base = i.mul(FIELD_STRIDE).add(ffBase);
232
+ const isActive = sCurveData.element(base);
233
+ If(isActive.lessThan(0.5), () => {
234
+ Continue();
235
+ });
236
+ const fieldType = sCurveData.element(base.add(1));
237
+ const fieldPos = vec3(
238
+ sCurveData.element(base.add(2)),
239
+ sCurveData.element(base.add(3)),
240
+ sCurveData.element(base.add(4))
241
+ );
242
+ const fieldDir = vec3(
243
+ sCurveData.element(base.add(5)),
244
+ sCurveData.element(base.add(6)),
245
+ sCurveData.element(base.add(7))
246
+ );
247
+ const strength = sCurveData.element(base.add(8));
248
+ const range = sCurveData.element(base.add(9));
249
+ const falloffType = sCurveData.element(base.add(10));
250
+ If(strength.equal(0), () => {
251
+ Continue();
252
+ });
253
+ If(fieldType.greaterThan(0.5), () => {
254
+ const force = strength.mul(delta);
255
+ vel.assign(vel.add(fieldDir.mul(force)));
256
+ });
257
+ If(fieldType.lessThan(0.5), () => {
258
+ const toField = fieldPos.sub(pos);
259
+ const dist = length(toField);
260
+ If(dist.greaterThan(1e-4), () => {
261
+ const inRange = dist.lessThan(range);
262
+ If(inRange, () => {
263
+ const dir = normalize(toField);
264
+ const normDist = dist.div(range);
265
+ const falloffNone = float(1);
266
+ const falloffLinear = float(1).sub(normDist);
267
+ const falloffQuadratic = float(1).sub(normDist.mul(normDist));
268
+ const useLinear = falloffType.greaterThan(0.5);
269
+ const useQuadratic = falloffType.greaterThan(1.5);
270
+ const falloff = useQuadratic.select(
271
+ falloffQuadratic,
272
+ useLinear.select(falloffLinear, falloffNone)
273
+ );
274
+ const force = strength.mul(falloff).mul(delta);
275
+ vel.assign(vel.add(dir.mul(force)));
276
+ });
277
+ });
278
+ });
279
+ });
280
+ },
281
+ "void"
282
+ );
283
+ return {
284
+ /** Uniform for the active force field count. */
285
+ countUniform: uForceFieldCount,
286
+ /** TSL function to call in the compute kernel: apply({ pos, vel, delta }) */
287
+ apply: applyForceFieldsTSL
288
+ };
289
+ }
290
+
291
+ // src/js/effects/three-particles/webgpu/curve-bake.ts
292
+ var CURVE_RESOLUTION = 256;
293
+ function bakeCurveIntoBuffer(buffer, writeOffset, particleSystemId, curve) {
294
+ const curveFn = getCurveFunctionFromConfig(particleSystemId, curve);
295
+ const lastIndex = CURVE_RESOLUTION - 1;
296
+ for (let i = 0; i < CURVE_RESOLUTION; i++) {
297
+ const t = i / lastIndex;
298
+ buffer[writeOffset + i] = curveFn(t);
299
+ }
300
+ return writeOffset + CURVE_RESOLUTION;
301
+ }
302
+ function bakeVelocityAxisIntoBuffer(buffer, writeOffset, particleSystemId, value) {
303
+ if (isLifeTimeCurve(value)) {
304
+ return bakeCurveIntoBuffer(buffer, writeOffset, particleSystemId, value);
305
+ }
306
+ const constantValue = calculateValue(particleSystemId, value, 0.5);
307
+ for (let i = 0; i < CURVE_RESOLUTION; i++) {
308
+ buffer[writeOffset + i] = constantValue;
309
+ }
310
+ return writeOffset + CURVE_RESOLUTION;
311
+ }
312
+ function bakeParticleSystemCurves(normalizedConfig, particleSystemId) {
313
+ let curveCount = 0;
314
+ const {
315
+ sizeOverLifetime,
316
+ opacityOverLifetime,
317
+ colorOverLifetime,
318
+ velocityOverLifetime
319
+ } = normalizedConfig;
320
+ const hasSizeOverLifetime = sizeOverLifetime.isActive;
321
+ const hasOpacityOverLifetime = opacityOverLifetime.isActive;
322
+ const hasColorOverLifetime = colorOverLifetime.isActive;
323
+ const isVelActive = velocityOverLifetime.isActive;
324
+ const hasLinearVelX = isVelActive && velocityOverLifetime.linear.x !== void 0 && velocityOverLifetime.linear.x !== 0;
325
+ const hasLinearVelY = isVelActive && velocityOverLifetime.linear.y !== void 0 && velocityOverLifetime.linear.y !== 0;
326
+ const hasLinearVelZ = isVelActive && velocityOverLifetime.linear.z !== void 0 && velocityOverLifetime.linear.z !== 0;
327
+ const hasOrbitalVelX = isVelActive && velocityOverLifetime.orbital.x !== void 0 && velocityOverLifetime.orbital.x !== 0;
328
+ const hasOrbitalVelY = isVelActive && velocityOverLifetime.orbital.y !== void 0 && velocityOverLifetime.orbital.y !== 0;
329
+ const hasOrbitalVelZ = isVelActive && velocityOverLifetime.orbital.z !== void 0 && velocityOverLifetime.orbital.z !== 0;
330
+ if (hasSizeOverLifetime) curveCount++;
331
+ if (hasOpacityOverLifetime) curveCount++;
332
+ if (hasColorOverLifetime) curveCount += 3;
333
+ if (hasLinearVelX) curveCount++;
334
+ if (hasLinearVelY) curveCount++;
335
+ if (hasLinearVelZ) curveCount++;
336
+ if (hasOrbitalVelX) curveCount++;
337
+ if (hasOrbitalVelY) curveCount++;
338
+ if (hasOrbitalVelZ) curveCount++;
339
+ const data = new Float32Array(curveCount * CURVE_RESOLUTION);
340
+ let writeOffset = 0;
341
+ let nextIndex = 0;
342
+ let sizeOverLifetimeIdx = -1;
343
+ let opacityOverLifetimeIdx = -1;
344
+ let colorRIdx = -1;
345
+ let colorGIdx = -1;
346
+ let colorBIdx = -1;
347
+ let linearVelXIdx = -1;
348
+ let linearVelYIdx = -1;
349
+ let linearVelZIdx = -1;
350
+ let orbitalVelXIdx = -1;
351
+ let orbitalVelYIdx = -1;
352
+ let orbitalVelZIdx = -1;
353
+ if (hasSizeOverLifetime) {
354
+ sizeOverLifetimeIdx = nextIndex++;
355
+ writeOffset = bakeCurveIntoBuffer(
356
+ data,
357
+ writeOffset,
358
+ particleSystemId,
359
+ sizeOverLifetime.lifetimeCurve
360
+ );
361
+ }
362
+ if (hasOpacityOverLifetime) {
363
+ opacityOverLifetimeIdx = nextIndex++;
364
+ writeOffset = bakeCurveIntoBuffer(
365
+ data,
366
+ writeOffset,
367
+ particleSystemId,
368
+ opacityOverLifetime.lifetimeCurve
369
+ );
370
+ }
371
+ if (hasColorOverLifetime) {
372
+ colorRIdx = nextIndex++;
373
+ writeOffset = bakeCurveIntoBuffer(
374
+ data,
375
+ writeOffset,
376
+ particleSystemId,
377
+ colorOverLifetime.r
378
+ );
379
+ colorGIdx = nextIndex++;
380
+ writeOffset = bakeCurveIntoBuffer(
381
+ data,
382
+ writeOffset,
383
+ particleSystemId,
384
+ colorOverLifetime.g
385
+ );
386
+ colorBIdx = nextIndex++;
387
+ writeOffset = bakeCurveIntoBuffer(
388
+ data,
389
+ writeOffset,
390
+ particleSystemId,
391
+ colorOverLifetime.b
392
+ );
393
+ }
394
+ if (hasLinearVelX) {
395
+ linearVelXIdx = nextIndex++;
396
+ writeOffset = bakeVelocityAxisIntoBuffer(
397
+ data,
398
+ writeOffset,
399
+ particleSystemId,
400
+ velocityOverLifetime.linear.x
401
+ );
402
+ }
403
+ if (hasLinearVelY) {
404
+ linearVelYIdx = nextIndex++;
405
+ writeOffset = bakeVelocityAxisIntoBuffer(
406
+ data,
407
+ writeOffset,
408
+ particleSystemId,
409
+ velocityOverLifetime.linear.y
410
+ );
411
+ }
412
+ if (hasLinearVelZ) {
413
+ linearVelZIdx = nextIndex++;
414
+ writeOffset = bakeVelocityAxisIntoBuffer(
415
+ data,
416
+ writeOffset,
417
+ particleSystemId,
418
+ velocityOverLifetime.linear.z
419
+ );
420
+ }
421
+ if (hasOrbitalVelX) {
422
+ orbitalVelXIdx = nextIndex++;
423
+ writeOffset = bakeVelocityAxisIntoBuffer(
424
+ data,
425
+ writeOffset,
426
+ particleSystemId,
427
+ velocityOverLifetime.orbital.x
428
+ );
429
+ }
430
+ if (hasOrbitalVelY) {
431
+ orbitalVelYIdx = nextIndex++;
432
+ writeOffset = bakeVelocityAxisIntoBuffer(
433
+ data,
434
+ writeOffset,
435
+ particleSystemId,
436
+ velocityOverLifetime.orbital.y
437
+ );
438
+ }
439
+ if (hasOrbitalVelZ) {
440
+ orbitalVelZIdx = nextIndex++;
441
+ writeOffset = bakeVelocityAxisIntoBuffer(
442
+ data,
443
+ writeOffset,
444
+ particleSystemId,
445
+ velocityOverLifetime.orbital.z
446
+ );
447
+ }
448
+ return {
449
+ data,
450
+ curveCount,
451
+ sizeOverLifetime: sizeOverLifetimeIdx,
452
+ opacityOverLifetime: opacityOverLifetimeIdx,
453
+ colorR: colorRIdx,
454
+ colorG: colorGIdx,
455
+ colorB: colorBIdx,
456
+ linearVelX: linearVelXIdx,
457
+ linearVelY: linearVelYIdx,
458
+ linearVelZ: linearVelZIdx,
459
+ orbitalVelX: orbitalVelXIdx,
460
+ orbitalVelY: orbitalVelYIdx,
461
+ orbitalVelZ: orbitalVelZIdx
462
+ };
463
+ }
464
+ var permute = Fn(({ x }) => {
465
+ return mod(x.mul(34).add(10).mul(x), float(289));
466
+ });
467
+ var taylorInvSqrt = Fn(({ r }) => {
468
+ return float(1.79284291400159).sub(float(0.85373472095314).mul(r));
469
+ });
470
+ var snoise3D = Fn(
471
+ ({ v }) => {
472
+ const ONE_THIRD = float(1 / 3);
473
+ const ONE_SIXTH = float(1 / 6);
474
+ const i = floor(
475
+ v.add(dot(v, vec3(ONE_THIRD, ONE_THIRD, ONE_THIRD)))
476
+ ).toVar();
477
+ const x0 = v.sub(i).add(dot(i, vec3(ONE_SIXTH, ONE_SIXTH, ONE_SIXTH))).toVar();
478
+ const g = step(x0.yzx, x0.xyz).toVar();
479
+ const l = float(1).sub(g).toVar();
480
+ const i1 = min(g.xyz, l.zxy).toVar();
481
+ const i2 = max(g.xyz, l.zxy).toVar();
482
+ const x1 = x0.sub(i1).add(ONE_SIXTH).toVar();
483
+ const x2 = x0.sub(i2).add(ONE_SIXTH.mul(2)).toVar();
484
+ const x3 = x0.sub(float(1)).add(ONE_SIXTH.mul(3)).toVar();
485
+ const iw = mod(i, float(289)).toVar();
486
+ const p0_yz = permute({
487
+ x: permute({
488
+ x: vec4(
489
+ vec2(iw.z, iw.z.add(i1.z)),
490
+ vec2(iw.z.add(i2.z), iw.z.add(1))
491
+ )
492
+ }).add(
493
+ vec4(vec2(iw.y, iw.y.add(i1.y)), vec2(iw.y.add(i2.y), iw.y.add(1)))
494
+ )
495
+ });
496
+ const p = permute({
497
+ x: p0_yz.add(
498
+ vec4(vec2(iw.x, iw.x.add(i1.x)), vec2(iw.x.add(i2.x), iw.x.add(1)))
499
+ )
500
+ });
501
+ const n_ = float(0.142857142857142);
502
+ const j = p.sub(float(49).mul(floor(p.mul(n_).mul(n_)))).toVar();
503
+ const x_ = floor(j.mul(n_)).toVar();
504
+ const y_ = floor(j.sub(float(7).mul(x_))).toVar();
505
+ const NS_X = float(0.285714285714286);
506
+ const NS_Y = float(-0.928571428571429);
507
+ const gx = x_.mul(NS_X).add(NS_Y);
508
+ const gy = y_.mul(NS_X).add(NS_Y);
509
+ const gz = float(1).sub(abs(gx)).sub(abs(gy)).toVar();
510
+ const gz_neg = step(gz, vec4(0));
511
+ const ox = gz_neg.mul(floor(gx).add(0.5));
512
+ const oy = gz_neg.mul(floor(gy).add(0.5));
513
+ const gx_final = gx.sub(ox);
514
+ const gy_final = gy.sub(oy);
515
+ const g0 = vec3(gx_final.x, gy_final.x, gz.x).toVar();
516
+ const g1 = vec3(gx_final.y, gy_final.y, gz.y).toVar();
517
+ const g2 = vec3(gx_final.z, gy_final.z, gz.z).toVar();
518
+ const g3 = vec3(gx_final.w, gy_final.w, gz.w).toVar();
519
+ const norm = taylorInvSqrt({
520
+ r: vec4(vec2(dot(g0, g0), dot(g1, g1)), vec2(dot(g2, g2), dot(g3, g3)))
521
+ });
522
+ g0.assign(g0.mul(norm.x));
523
+ g1.assign(g1.mul(norm.y));
524
+ g2.assign(g2.mul(norm.z));
525
+ g3.assign(g3.mul(norm.w));
526
+ const m = max(
527
+ vec4(
528
+ vec2(float(0.5).sub(dot(x0, x0)), float(0.5).sub(dot(x1, x1))),
529
+ vec2(float(0.5).sub(dot(x2, x2)), float(0.5).sub(dot(x3, x3)))
530
+ ),
531
+ float(0)
532
+ ).toVar();
533
+ const m2 = m.mul(m).toVar();
534
+ const m4 = m2.mul(m2).toVar();
535
+ const gdot = vec4(
536
+ vec2(dot(g0, x0), dot(g1, x1)),
537
+ vec2(dot(g2, x2), dot(g3, x3))
538
+ );
539
+ return float(42).mul(dot(m4, gdot));
540
+ }
541
+ );
542
+ Fn(
543
+ ({ t }) => {
544
+ const noiseX = snoise3D({ v: vec3(t, float(0), float(0)) });
545
+ const noiseY = snoise3D({ v: vec3(t, t, float(0)) });
546
+ const noiseZ = snoise3D({ v: vec3(t, t, t) });
547
+ return vec3(noiseX, noiseY, noiseZ);
548
+ }
549
+ );
550
+
551
+ // src/js/effects/three-particles/webgpu/compute-modifiers.ts
552
+ var INIT_STRIDE = 28;
553
+ function createModifierStorageBuffers(maxParticles, instanced, curveData, hasForceFields = false, hasCollisionPlanes = false) {
554
+ const Cls = instanced ? StorageInstancedBufferAttribute : StorageBufferAttribute;
555
+ const curveLen = Math.max(curveData.length, 1);
556
+ const ffSize = hasForceFields ? FORCE_FIELD_DATA_SIZE : 0;
557
+ const cpSize = hasCollisionPlanes ? COLLISION_PLANE_DATA_SIZE : 0;
558
+ const totalLen = curveLen + maxParticles * INIT_STRIDE + ffSize + cpSize;
559
+ const combined = new Float32Array(totalLen);
560
+ combined.set(curveData.length > 0 ? curveData : new Float32Array([0]));
561
+ return {
562
+ // Position and velocity use vec4 (w=padding) to avoid WebGPU vec3→vec4
563
+ // storage buffer alignment conversion that breaks itemSize-based type resolution.
564
+ position: new Cls(new Float32Array(maxParticles * 4), 4),
565
+ velocity: new StorageBufferAttribute(new Float32Array(maxParticles * 4), 4),
566
+ color: new Cls(new Float32Array(maxParticles * 4), 4),
567
+ // (lifetime, size, rotation, startFrame)
568
+ particleState: new Cls(new Float32Array(maxParticles * 4), 4),
569
+ // (startLifetime, startSize, startOpacity, startColorR)
570
+ startValues: new Cls(new Float32Array(maxParticles * 4), 4),
571
+ // (startColorG, startColorB, rotationSpeed, noiseOffset)
572
+ startColorsExt: new StorageBufferAttribute(
573
+ new Float32Array(maxParticles * 4),
574
+ 4
575
+ ),
576
+ // (orbitalOffset.x, .y, .z, isActive)
577
+ orbitalIsActive: new StorageBufferAttribute(
578
+ new Float32Array(maxParticles * 4),
579
+ 4
580
+ ),
581
+ // Curve data + emit queue tail (single buffer, 8th binding)
582
+ curveData: new StorageBufferAttribute(combined, 1)
583
+ };
584
+ }
585
+ var _emitCounts = /* @__PURE__ */ new WeakMap();
586
+ var _curveDataLengths = /* @__PURE__ */ new WeakMap();
587
+ var _currentEmitIndices = /* @__PURE__ */ new WeakMap();
588
+ var _previousEmitIndices = /* @__PURE__ */ new WeakMap();
589
+ function writeParticleToModifierBuffers(buffers, index, data) {
590
+ const curveLen = _curveDataLengths.get(buffers.curveData) ?? 0;
591
+ const arr = buffers.curveData.array;
592
+ const base = curveLen + index * INIT_STRIDE;
593
+ arr[base] = data.position.x;
594
+ arr[base + 1] = data.position.y;
595
+ arr[base + 2] = data.position.z;
596
+ arr[base + 3] = 1;
597
+ arr[base + 4] = data.velocity.x;
598
+ arr[base + 5] = data.velocity.y;
599
+ arr[base + 6] = data.velocity.z;
600
+ arr[base + 7] = 0;
601
+ arr[base + 8] = data.colorR;
602
+ arr[base + 9] = data.colorG;
603
+ arr[base + 10] = data.colorB;
604
+ arr[base + 11] = data.colorA;
605
+ arr[base + 12] = 0;
606
+ arr[base + 13] = data.size;
607
+ arr[base + 14] = data.rotation;
608
+ arr[base + 15] = data.startFrame;
609
+ arr[base + 16] = data.orbitalOffset.x;
610
+ arr[base + 17] = data.orbitalOffset.y;
611
+ arr[base + 18] = data.orbitalOffset.z;
612
+ arr[base + 19] = 1;
613
+ arr[base + 20] = data.startLifetime;
614
+ arr[base + 21] = data.startSize;
615
+ arr[base + 22] = data.startOpacity;
616
+ arr[base + 23] = data.startColorR;
617
+ arr[base + 24] = data.startColorG;
618
+ arr[base + 25] = data.startColorB;
619
+ arr[base + 26] = data.rotationSpeed;
620
+ arr[base + 27] = data.noiseOffset;
621
+ _emitCounts.set(
622
+ buffers.curveData,
623
+ (_emitCounts.get(buffers.curveData) ?? 0) + 1
624
+ );
625
+ let indices = _currentEmitIndices.get(buffers.curveData);
626
+ if (!indices) {
627
+ indices = [];
628
+ _currentEmitIndices.set(buffers.curveData, indices);
629
+ }
630
+ indices.push(index);
631
+ const i4 = index * 4;
632
+ const svArr = buffers.startValues.array;
633
+ svArr[i4] = data.startLifetime;
634
+ svArr[i4 + 1] = data.startSize;
635
+ svArr[i4 + 2] = data.startOpacity;
636
+ svArr[i4 + 3] = data.startColorR;
637
+ const sceArr = buffers.startColorsExt.array;
638
+ sceArr[i4] = data.startColorG;
639
+ sceArr[i4 + 1] = data.startColorB;
640
+ sceArr[i4 + 2] = data.rotationSpeed;
641
+ sceArr[i4 + 3] = data.noiseOffset;
642
+ }
643
+ function registerCurveDataLength(buffers, curveDataLength) {
644
+ _curveDataLengths.set(buffers.curveData, curveDataLength);
645
+ }
646
+ function flushEmitQueue(buffers) {
647
+ const count = _emitCounts.get(buffers.curveData) ?? 0;
648
+ const curveLen = _curveDataLengths.get(buffers.curveData) ?? 0;
649
+ const arr = buffers.curveData.array;
650
+ const current = _currentEmitIndices.get(buffers.curveData);
651
+ const previous = _previousEmitIndices.get(buffers.curveData);
652
+ let clearedAny = false;
653
+ if (previous && previous.length > 0) {
654
+ const currentSet = current && current.length > 0 ? new Set(current) : null;
655
+ for (let i = 0; i < previous.length; i++) {
656
+ const p = previous[i];
657
+ if (!currentSet || !currentSet.has(p)) {
658
+ const flagOffset = curveLen + p * INIT_STRIDE + 3;
659
+ if (arr[flagOffset] > 0.5) {
660
+ arr[flagOffset] = 0;
661
+ clearedAny = true;
662
+ }
663
+ }
664
+ }
665
+ }
666
+ if (count > 0 || clearedAny) {
667
+ buffers.curveData.needsUpdate = true;
668
+ }
669
+ if (current && current.length > 0) {
670
+ let prevArr = _previousEmitIndices.get(buffers.curveData);
671
+ if (!prevArr) {
672
+ prevArr = [];
673
+ _previousEmitIndices.set(buffers.curveData, prevArr);
674
+ }
675
+ prevArr.length = current.length;
676
+ for (let i = 0; i < current.length; i++) {
677
+ prevArr[i] = current[i];
678
+ }
679
+ current.length = 0;
680
+ } else {
681
+ const prevArr = _previousEmitIndices.get(buffers.curveData);
682
+ if (prevArr) prevArr.length = 0;
683
+ if (current) current.length = 0;
684
+ }
685
+ _emitCounts.set(buffers.curveData, 0);
686
+ return count;
687
+ }
688
+ function deactivateParticleInModifierBuffers(_buffers, _index) {
689
+ }
690
+ function createCurveLookup(sCurveData) {
691
+ return Fn(
692
+ ({
693
+ curveIndex,
694
+ t
695
+ }) => {
696
+ const clamped = min(t, float(1));
697
+ const pos = clamped.mul(CURVE_RESOLUTION - 1);
698
+ const idx0 = floor(pos);
699
+ const f = fract(pos);
700
+ const base = curveIndex.mul(CURVE_RESOLUTION);
701
+ const v0 = sCurveData.element(base.add(idx0));
702
+ const v1 = sCurveData.element(
703
+ base.add(min(idx0.add(1), float(CURVE_RESOLUTION - 1)))
704
+ );
705
+ return mix(v0, v1, f);
706
+ }
707
+ );
708
+ }
709
+ function createModifierComputeUpdate(buffers, maxParticles, curveMap, flags, forceFieldCount = 0, collisionPlaneCount = 0) {
710
+ const uDelta = uniform(float(0));
711
+ const uDeltaMs = uniform(float(0));
712
+ const uGravityVelocity = uniform(new Vector3(0, 0, 0));
713
+ const uWorldPositionChange = uniform(new Vector3(0, 0, 0));
714
+ const uSimSpaceWorld = uniform(float(0));
715
+ const uNoiseStrength = uniform(float(0));
716
+ const uNoisePower = uniform(float(0));
717
+ const uNoiseFrequency = uniform(float(1));
718
+ const uNoisePosAmount = uniform(float(0));
719
+ const uNoiseRotAmount = uniform(float(0));
720
+ const uNoiseSizeAmount = uniform(float(0));
721
+ const sPosition = storage(buffers.position, "vec4", maxParticles);
722
+ const sVelocity = storage(buffers.velocity, "vec4", maxParticles);
723
+ const sColor = storage(buffers.color, "vec4", maxParticles);
724
+ const sParticleState = storage(buffers.particleState, "vec4", maxParticles);
725
+ const sStartValues = storage(buffers.startValues, "vec4", maxParticles);
726
+ const sStartColorsExt = storage(buffers.startColorsExt, "vec4", maxParticles);
727
+ const sOrbitalIsActive = storage(
728
+ buffers.orbitalIsActive,
729
+ "vec4",
730
+ maxParticles
731
+ );
732
+ const sCurveData = storage(
733
+ buffers.curveData,
734
+ "float",
735
+ buffers.curveData.array.length
736
+ );
737
+ const curveLen = Math.max(curveMap.data.length, 1);
738
+ const lookupCurve = createCurveLookup(sCurveData);
739
+ const forceFieldOffset = curveLen + maxParticles * INIT_STRIDE;
740
+ const forceFieldNodes = flags.forceFields ? createForceFieldTSL(sCurveData, forceFieldOffset, forceFieldCount) : null;
741
+ const ffSize = flags.forceFields ? FORCE_FIELD_DATA_SIZE : 0;
742
+ const collisionPlaneOffset = forceFieldOffset + ffSize;
743
+ const collisionPlaneNodes = flags.collisionPlanes ? createCollisionPlaneTSL(
744
+ sCurveData,
745
+ collisionPlaneOffset,
746
+ collisionPlaneCount
747
+ ) : null;
748
+ const computeKernel = Fn(() => {
749
+ const i = instanceIndex;
750
+ const initBase = i.mul(INIT_STRIDE).add(curveLen);
751
+ const initFlag = sCurveData.element(initBase.add(3));
752
+ If(initFlag.greaterThan(0.5), () => {
753
+ sPosition.element(i).assign(
754
+ vec4(
755
+ sCurveData.element(initBase),
756
+ sCurveData.element(initBase.add(1)),
757
+ sCurveData.element(initBase.add(2)),
758
+ 0
759
+ )
760
+ );
761
+ sVelocity.element(i).assign(
762
+ vec4(
763
+ sCurveData.element(initBase.add(4)),
764
+ sCurveData.element(initBase.add(5)),
765
+ sCurveData.element(initBase.add(6)),
766
+ 0
767
+ )
768
+ );
769
+ sColor.element(i).assign(
770
+ vec4(
771
+ sCurveData.element(initBase.add(8)),
772
+ sCurveData.element(initBase.add(9)),
773
+ sCurveData.element(initBase.add(10)),
774
+ sCurveData.element(initBase.add(11))
775
+ )
776
+ );
777
+ sParticleState.element(i).assign(
778
+ vec4(
779
+ sCurveData.element(initBase.add(12)),
780
+ sCurveData.element(initBase.add(13)),
781
+ sCurveData.element(initBase.add(14)),
782
+ sCurveData.element(initBase.add(15))
783
+ )
784
+ );
785
+ sOrbitalIsActive.element(i).assign(
786
+ vec4(
787
+ sCurveData.element(initBase.add(16)),
788
+ sCurveData.element(initBase.add(17)),
789
+ sCurveData.element(initBase.add(18)),
790
+ sCurveData.element(initBase.add(19))
791
+ )
792
+ );
793
+ sStartValues.element(i).assign(
794
+ vec4(
795
+ sCurveData.element(initBase.add(20)),
796
+ sCurveData.element(initBase.add(21)),
797
+ sCurveData.element(initBase.add(22)),
798
+ sCurveData.element(initBase.add(23))
799
+ )
800
+ );
801
+ sStartColorsExt.element(i).assign(
802
+ vec4(
803
+ sCurveData.element(initBase.add(24)),
804
+ sCurveData.element(initBase.add(25)),
805
+ sCurveData.element(initBase.add(26)),
806
+ sCurveData.element(initBase.add(27))
807
+ )
808
+ );
809
+ sCurveData.element(initBase.add(3)).assign(float(0));
810
+ });
811
+ const oiaVec = sOrbitalIsActive.element(i).toVar();
812
+ If(oiaVec.w.greaterThanEqual(float(0.5)), () => {
813
+ const pos = sPosition.element(i).xyz.toVar();
814
+ const vel = sVelocity.element(i).xyz.toVar();
815
+ const ps = sParticleState.element(i).toVar();
816
+ const sv = sStartValues.element(i);
817
+ ps.x;
818
+ const startLife = sv.x;
819
+ vel.assign(vel.sub(vec3(uGravityVelocity).mul(uDelta)));
820
+ if (forceFieldNodes) {
821
+ forceFieldNodes.apply({ pos, vel, delta: uDelta });
822
+ }
823
+ If(uSimSpaceWorld.greaterThan(0.5), () => {
824
+ pos.assign(pos.sub(vec3(uWorldPositionChange)));
825
+ });
826
+ pos.assign(pos.add(vel.mul(uDelta)));
827
+ if (collisionPlaneNodes) {
828
+ collisionPlaneNodes.apply({
829
+ pos,
830
+ vel,
831
+ oiaVec,
832
+ sColorNode: sColor,
833
+ ps,
834
+ startLife,
835
+ particleIdx: i,
836
+ sOrbitalIsActiveNode: sOrbitalIsActive
837
+ });
838
+ }
839
+ const lifePct = min(ps.x.div(startLife), float(1));
840
+ ps.x.assign(ps.x.add(uDeltaMs));
841
+ if (flags.linearVelocity) {
842
+ const lvx = curveMap.linearVelX >= 0 ? lookupCurve({
843
+ curveIndex: float(curveMap.linearVelX),
844
+ t: lifePct
845
+ }) : float(0);
846
+ const lvy = curveMap.linearVelY >= 0 ? lookupCurve({
847
+ curveIndex: float(curveMap.linearVelY),
848
+ t: lifePct
849
+ }) : float(0);
850
+ const lvz = curveMap.linearVelZ >= 0 ? lookupCurve({
851
+ curveIndex: float(curveMap.linearVelZ),
852
+ t: lifePct
853
+ }) : float(0);
854
+ pos.assign(pos.add(vec3(lvx, lvy, lvz).mul(uDelta)));
855
+ }
856
+ if (flags.orbitalVelocity) {
857
+ const offset = vec3(oiaVec.x, oiaVec.y, oiaVec.z).toVar();
858
+ pos.assign(pos.sub(offset));
859
+ const ovx = curveMap.orbitalVelX >= 0 ? lookupCurve({
860
+ curveIndex: float(curveMap.orbitalVelX),
861
+ t: lifePct
862
+ }) : float(0);
863
+ const ovy = curveMap.orbitalVelY >= 0 ? lookupCurve({
864
+ curveIndex: float(curveMap.orbitalVelY),
865
+ t: lifePct
866
+ }) : float(0);
867
+ const ovz = curveMap.orbitalVelZ >= 0 ? lookupCurve({
868
+ curveIndex: float(curveMap.orbitalVelZ),
869
+ t: lifePct
870
+ }) : float(0);
871
+ const ax = ovx.mul(uDelta);
872
+ const ay = ovz.mul(uDelta);
873
+ const az = ovy.mul(uDelta);
874
+ const cosAz = cos(az);
875
+ const sinAz = sin(az);
876
+ const zx = offset.x.mul(cosAz).sub(offset.y.mul(sinAz));
877
+ const zy = offset.x.mul(sinAz).add(offset.y.mul(cosAz));
878
+ const zz = offset.z;
879
+ const cosAy = cos(ay);
880
+ const sinAy = sin(ay);
881
+ const yx = zx.mul(cosAy).add(zz.mul(sinAy));
882
+ const yy = zy;
883
+ const yz = zx.negate().mul(sinAy).add(zz.mul(cosAy));
884
+ const cosAx = cos(ax);
885
+ const sinAx = sin(ax);
886
+ const fx = yx;
887
+ const fy = yy.mul(cosAx).sub(yz.mul(sinAx));
888
+ const fz = yy.mul(sinAx).add(yz.mul(cosAx));
889
+ offset.assign(vec3(fx, fy, fz));
890
+ oiaVec.x.assign(offset.x);
891
+ oiaVec.y.assign(offset.y);
892
+ oiaVec.z.assign(offset.z);
893
+ pos.assign(pos.add(offset));
894
+ }
895
+ if (flags.sizeOverLifetime && curveMap.sizeOverLifetime >= 0) {
896
+ const multiplier = lookupCurve({
897
+ curveIndex: float(curveMap.sizeOverLifetime),
898
+ t: lifePct
899
+ });
900
+ ps.y.assign(sv.y.mul(multiplier));
901
+ }
902
+ if (flags.opacityOverLifetime && curveMap.opacityOverLifetime >= 0) {
903
+ const multiplier = lookupCurve({
904
+ curveIndex: float(curveMap.opacityOverLifetime),
905
+ t: lifePct
906
+ });
907
+ const col = sColor.element(i).toVar();
908
+ col.w.assign(sv.z.mul(multiplier));
909
+ sColor.element(i).assign(col);
910
+ }
911
+ if (flags.colorOverLifetime) {
912
+ const col = sColor.element(i).toVar();
913
+ const sce = sStartColorsExt.element(i);
914
+ if (curveMap.colorR >= 0) {
915
+ const rMul = lookupCurve({
916
+ curveIndex: float(curveMap.colorR),
917
+ t: lifePct
918
+ });
919
+ col.x.assign(sv.w.mul(rMul));
920
+ }
921
+ if (curveMap.colorG >= 0) {
922
+ const gMul = lookupCurve({
923
+ curveIndex: float(curveMap.colorG),
924
+ t: lifePct
925
+ });
926
+ col.y.assign(sce.x.mul(gMul));
927
+ }
928
+ if (curveMap.colorB >= 0) {
929
+ const bMul = lookupCurve({
930
+ curveIndex: float(curveMap.colorB),
931
+ t: lifePct
932
+ });
933
+ col.z.assign(sce.y.mul(bMul));
934
+ }
935
+ sColor.element(i).assign(col);
936
+ }
937
+ if (flags.rotationOverLifetime) {
938
+ const sce = sStartColorsExt.element(i);
939
+ ps.z.assign(ps.z.add(sce.z.mul(uDelta).mul(float(0.02))));
940
+ }
941
+ if (flags.noise) {
942
+ const sce = sStartColorsExt.element(i);
943
+ const noisePos = lifePct.add(sce.w).mul(10).mul(uNoiseStrength).mul(uNoiseFrequency);
944
+ const noiseX = snoise3D({ v: vec3(noisePos, float(0), float(0)) });
945
+ const noiseY = snoise3D({
946
+ v: vec3(noisePos, noisePos, float(0))
947
+ });
948
+ const noiseZ = snoise3D({
949
+ v: vec3(noisePos, noisePos, noisePos)
950
+ });
951
+ pos.assign(
952
+ pos.add(
953
+ vec3(noiseX, noiseY, noiseZ).mul(uNoisePower).mul(uNoisePosAmount)
954
+ )
955
+ );
956
+ If(uNoiseRotAmount.greaterThan(1e-3), () => {
957
+ ps.z.assign(ps.z.add(noiseX.mul(uNoisePower).mul(uNoiseRotAmount)));
958
+ });
959
+ If(uNoiseSizeAmount.greaterThan(1e-3), () => {
960
+ ps.y.assign(ps.y.add(noiseX.mul(uNoisePower).mul(uNoiseSizeAmount)));
961
+ });
962
+ }
963
+ sPosition.element(i).assign(vec4(pos, 0));
964
+ sVelocity.element(i).assign(vec4(vel, 0));
965
+ sParticleState.element(i).assign(ps);
966
+ sOrbitalIsActive.element(i).assign(oiaVec);
967
+ If(ps.x.greaterThan(startLife), () => {
968
+ const deadOia = sOrbitalIsActive.element(i).toVar();
969
+ deadOia.w.assign(float(0));
970
+ sOrbitalIsActive.element(i).assign(deadOia);
971
+ sColor.element(i).assign(vec4(0));
972
+ });
973
+ });
974
+ });
975
+ const computeNode = compute(computeKernel(), maxParticles);
976
+ return {
977
+ computeNode,
978
+ uniforms: {
979
+ delta: uDelta,
980
+ deltaMs: uDeltaMs,
981
+ gravityVelocity: uGravityVelocity,
982
+ worldPositionChange: uWorldPositionChange,
983
+ simulationSpaceWorld: uSimSpaceWorld,
984
+ noiseStrength: uNoiseStrength,
985
+ noisePower: uNoisePower,
986
+ noiseFrequency: uNoiseFrequency,
987
+ noisePositionAmount: uNoisePosAmount,
988
+ noiseRotationAmount: uNoiseRotAmount,
989
+ noiseSizeAmount: uNoiseSizeAmount
990
+ },
991
+ buffers,
992
+ curveDataLength: curveLen,
993
+ /** Force field offset and count uniform (null if no force fields). */
994
+ forceFieldInfo: forceFieldNodes ? {
995
+ offset: forceFieldOffset,
996
+ countUniform: forceFieldNodes.countUniform
997
+ } : null,
998
+ /** Collision plane offset and count uniform (null if no collision planes). */
999
+ collisionPlaneInfo: collisionPlaneNodes ? {
1000
+ offset: collisionPlaneOffset,
1001
+ countUniform: collisionPlaneNodes.countUniform
1002
+ } : null
1003
+ };
1004
+ }
1005
+
1006
+ // src/js/effects/three-particles/three-particles-constants.ts
1007
+ var POINT_SIZE_SCALE = 100;
1008
+ var ALPHA_DISCARD_THRESHOLD = 1e-3;
1009
+ var _dummyTexture = null;
1010
+ function getDummyTexture() {
1011
+ if (!_dummyTexture) {
1012
+ _dummyTexture = new DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);
1013
+ _dummyTexture.needsUpdate = true;
1014
+ }
1015
+ return _dummyTexture;
1016
+ }
1017
+ function createParticleUniforms(sharedUniforms) {
1018
+ const dummy = getDummyTexture();
1019
+ const map = sharedUniforms.map.value ?? dummy;
1020
+ if (map) map.colorSpace = NoColorSpace;
1021
+ return {
1022
+ uMap: map,
1023
+ uElapsed: uniform(float(sharedUniforms.elapsed.value)),
1024
+ uFps: uniform(float(sharedUniforms.fps.value)),
1025
+ uUseFPSForFrameIndex: uniform(
1026
+ float(sharedUniforms.useFPSForFrameIndex.value ? 1 : 0)
1027
+ ),
1028
+ uTiles: uniform(sharedUniforms.tiles.value),
1029
+ uDiscardBg: uniform(
1030
+ float(sharedUniforms.discardBackgroundColor.value ? 1 : 0)
1031
+ ),
1032
+ uBgColor: uniform(
1033
+ new Vector3(
1034
+ sharedUniforms.backgroundColor.value.r,
1035
+ sharedUniforms.backgroundColor.value.g,
1036
+ sharedUniforms.backgroundColor.value.b
1037
+ )
1038
+ ),
1039
+ uBgTolerance: uniform(float(sharedUniforms.backgroundColorTolerance.value)),
1040
+ uSoftEnabled: uniform(
1041
+ float(sharedUniforms.softParticlesEnabled.value ? 1 : 0)
1042
+ ),
1043
+ uSoftIntensity: uniform(float(sharedUniforms.softParticlesIntensity.value)),
1044
+ uSceneDepthTex: sharedUniforms.sceneDepthTexture.value ?? dummy,
1045
+ uCameraNearFar: uniform(sharedUniforms.cameraNearFar.value)
1046
+ };
1047
+ }
1048
+ var computeFrameIndex = Fn(
1049
+ ({
1050
+ vLifetime,
1051
+ vStartLifetime,
1052
+ vStartFrame,
1053
+ uFps,
1054
+ uUseFPSForFrameIndex,
1055
+ uTiles
1056
+ }) => {
1057
+ const totalFrames = uTiles.x.mul(uTiles.y);
1058
+ const lifePercent = min(vLifetime.div(vStartLifetime), float(1));
1059
+ const fpsBased = max(vLifetime.div(1e3).mul(uFps), float(0));
1060
+ const lifetimeBased = max(
1061
+ min(floor(lifePercent.mul(totalFrames)), totalFrames.sub(1)),
1062
+ float(0)
1063
+ );
1064
+ const fpsResult = uFps.equal(0).select(float(0), fpsBased);
1065
+ const frameOffset = uUseFPSForFrameIndex.greaterThan(0.5).select(fpsResult, lifetimeBased);
1066
+ return round(vStartFrame).add(frameOffset);
1067
+ }
1068
+ );
1069
+ var computeSpriteSheetUV = Fn(
1070
+ ({ baseUV, frameIndex, uTiles }) => {
1071
+ const spriteX = floor(mod(frameIndex, uTiles.x));
1072
+ const spriteY = floor(mod(frameIndex.div(uTiles.x), uTiles.y));
1073
+ return vec2(
1074
+ baseUV.x.div(uTiles.x).add(spriteX.div(uTiles.x)),
1075
+ baseUV.y.div(uTiles.y).add(spriteY.div(uTiles.y))
1076
+ );
1077
+ }
1078
+ );
1079
+ var linearizeDepth = Fn(
1080
+ ({ depthSample, near, far }) => {
1081
+ const zNdc = depthSample.mul(2).sub(1);
1082
+ return near.mul(2).mul(far).div(far.add(near).sub(zNdc.mul(far.sub(near))));
1083
+ }
1084
+ );
1085
+ var computeSoftParticleFade = Fn(
1086
+ ({
1087
+ viewZ,
1088
+ uSoftEnabled,
1089
+ uSoftIntensity,
1090
+ uSceneDepthTex,
1091
+ uCameraNearFar
1092
+ }) => {
1093
+ const softFade = float(1).toVar();
1094
+ If(uSoftEnabled.greaterThan(0.5), () => {
1095
+ const depthSample = texture(uSceneDepthTex, screenUV).x;
1096
+ const sceneDepthLinear = linearizeDepth({
1097
+ depthSample,
1098
+ near: uCameraNearFar.x,
1099
+ far: uCameraNearFar.y
1100
+ });
1101
+ const depthDiff = sceneDepthLinear.sub(viewZ);
1102
+ softFade.assign(smoothstep(float(0), uSoftIntensity, depthDiff));
1103
+ });
1104
+ return softFade;
1105
+ }
1106
+ );
1107
+ var applyBackgroundDiscard = Fn(
1108
+ ({
1109
+ texColor,
1110
+ uDiscardBg,
1111
+ uBgColor,
1112
+ uBgTolerance
1113
+ }) => {
1114
+ If(uDiscardBg.greaterThan(0.5), () => {
1115
+ const diff = vec3(
1116
+ texColor.x.sub(uBgColor.x),
1117
+ texColor.y.sub(uBgColor.y),
1118
+ texColor.z.sub(uBgColor.z)
1119
+ );
1120
+ If(abs(length(diff)).lessThan(uBgTolerance), () => {
1121
+ Discard();
1122
+ });
1123
+ });
1124
+ }
1125
+ );
1126
+ var compensateOutputSRGB = Fn(
1127
+ ({ color }) => {
1128
+ return vec4(sRGBTransferEOTF(color.rgb), color.a);
1129
+ }
1130
+ );
1131
+
1132
+ // src/js/effects/three-particles/webgpu/tsl-instanced-billboard-material.ts
1133
+ function createInstancedBillboardTSLMaterial(sharedUniforms, rendererConfig, gpuCompute = false) {
1134
+ const u = createParticleUniforms(sharedUniforms);
1135
+ const uViewportHeight = uniform(
1136
+ typeof sharedUniforms.viewportHeight?.value === "number" ? sharedUniforms.viewportHeight.value : 1
1137
+ );
1138
+ sharedUniforms.viewportHeight = uViewportHeight;
1139
+ const aInstanceOffset = attribute("instanceOffset");
1140
+ const aColor = attribute("instanceColor");
1141
+ const aParticleState = gpuCompute ? attribute("instanceParticleState") : null;
1142
+ const aStartValues = gpuCompute ? attribute("instanceStartValues") : null;
1143
+ const aSize = gpuCompute ? null : attribute("instanceSize");
1144
+ const aLifetime = gpuCompute ? null : attribute("instanceLifetime");
1145
+ const aStartLifetime = gpuCompute ? null : attribute("instanceStartLifetime");
1146
+ const aRotation = gpuCompute ? null : attribute("instanceRotation");
1147
+ const aStartFrame = gpuCompute ? null : attribute("instanceStartFrame");
1148
+ const vColor = varyingProperty("vec4", "vColor");
1149
+ const vLifetime = varyingProperty("float", "vLifetime");
1150
+ const vStartLifetime = varyingProperty("float", "vStartLifetime");
1151
+ const vRotation = varyingProperty("float", "vRotation");
1152
+ const vStartFrame = varyingProperty("float", "vStartFrame");
1153
+ const vUv = varyingProperty("vec2", "vUv");
1154
+ const vViewZ = varyingProperty("float", "vViewZ");
1155
+ const vertexNode = Fn(() => {
1156
+ const clipPos = vec4(0, 0, 0, 0).toVar();
1157
+ If(aColor.w.greaterThan(0), () => {
1158
+ vColor.assign(aColor.toVar());
1159
+ if (gpuCompute) {
1160
+ vLifetime.assign(aParticleState.x);
1161
+ vStartLifetime.assign(aStartValues.x);
1162
+ vRotation.assign(aParticleState.z);
1163
+ vStartFrame.assign(aParticleState.w);
1164
+ } else {
1165
+ vLifetime.assign(aLifetime);
1166
+ vStartLifetime.assign(aStartLifetime);
1167
+ vRotation.assign(aRotation);
1168
+ vStartFrame.assign(aStartFrame);
1169
+ }
1170
+ vUv.assign(
1171
+ vec2(positionLocal.x.add(0.5), float(0.5).sub(positionLocal.y))
1172
+ );
1173
+ const mvPosition = modelViewMatrix.mul(vec4(aInstanceOffset.xyz, 1)).toVar();
1174
+ const dist = length(mvPosition.xyz);
1175
+ const sizeVal = gpuCompute ? aParticleState.y : aSize;
1176
+ const pointSizePx = sizeVal.mul(POINT_SIZE_SCALE).div(dist);
1177
+ const projY = cameraProjectionMatrix.element(1).element(1);
1178
+ const perspectiveSize = pointSizePx.mul(mvPosition.z.negate()).div(projY.mul(uViewportHeight).mul(0.5));
1179
+ mvPosition.x.addAssign(positionLocal.x.mul(perspectiveSize));
1180
+ mvPosition.y.addAssign(positionLocal.y.mul(perspectiveSize));
1181
+ vViewZ.assign(mvPosition.z.negate());
1182
+ clipPos.assign(cameraProjectionMatrix.mul(mvPosition));
1183
+ });
1184
+ return clipPos;
1185
+ })();
1186
+ const fragmentColor = Fn(() => {
1187
+ const outColor = vColor.toVar();
1188
+ const center = vec2(0.5, 0.5);
1189
+ const centered = vUv.sub(center);
1190
+ const cosR = cos(vRotation);
1191
+ const sinR = sin(vRotation);
1192
+ const rotated = vec2(
1193
+ centered.x.mul(cosR).add(centered.y.mul(sinR)),
1194
+ centered.x.mul(sinR).negate().add(centered.y.mul(cosR))
1195
+ );
1196
+ const rotatedUV = rotated.add(center);
1197
+ const dist = length(rotatedUV.sub(center));
1198
+ If(dist.greaterThan(0.5), () => {
1199
+ Discard();
1200
+ });
1201
+ const frameIndex = computeFrameIndex({
1202
+ vLifetime,
1203
+ vStartLifetime,
1204
+ vStartFrame,
1205
+ uFps: u.uFps,
1206
+ uUseFPSForFrameIndex: u.uUseFPSForFrameIndex,
1207
+ uTiles: u.uTiles
1208
+ });
1209
+ const uvPoint = computeSpriteSheetUV({
1210
+ baseUV: rotatedUV,
1211
+ frameIndex,
1212
+ uTiles: u.uTiles
1213
+ });
1214
+ const texColor = texture(u.uMap, uvPoint);
1215
+ outColor.assign(outColor.mul(texColor));
1216
+ applyBackgroundDiscard({
1217
+ texColor,
1218
+ uDiscardBg: u.uDiscardBg,
1219
+ uBgColor: u.uBgColor,
1220
+ uBgTolerance: u.uBgTolerance
1221
+ });
1222
+ const softFade = computeSoftParticleFade({
1223
+ viewZ: vViewZ,
1224
+ uSoftEnabled: u.uSoftEnabled,
1225
+ uSoftIntensity: u.uSoftIntensity,
1226
+ uSceneDepthTex: u.uSceneDepthTex,
1227
+ uCameraNearFar: u.uCameraNearFar
1228
+ });
1229
+ outColor.assign(vec4(outColor.xyz, outColor.w.mul(softFade)));
1230
+ Discard(outColor.w.lessThan(ALPHA_DISCARD_THRESHOLD));
1231
+ return compensateOutputSRGB({ color: outColor });
1232
+ })();
1233
+ const material = new MeshBasicNodeMaterial();
1234
+ material.transparent = rendererConfig.transparent;
1235
+ material.blending = rendererConfig.blending;
1236
+ material.depthTest = rendererConfig.depthTest;
1237
+ material.depthWrite = rendererConfig.depthWrite;
1238
+ material.toneMapped = false;
1239
+ material.fog = false;
1240
+ material.vertexNode = vertexNode;
1241
+ material.colorNode = fragmentColor;
1242
+ return material;
1243
+ }
1244
+ var applyQuaternion = Fn(
1245
+ ({ v, q }) => {
1246
+ const t = cross(q.xyz, v).mul(2);
1247
+ return v.add(t.mul(q.w)).add(cross(q.xyz, t));
1248
+ }
1249
+ );
1250
+ function createMeshParticleTSLMaterial(sharedUniforms, rendererConfig, gpuCompute = false) {
1251
+ const u = createParticleUniforms(sharedUniforms);
1252
+ const aInstanceOffset = attribute("instanceOffset");
1253
+ const aColor = attribute("instanceColor");
1254
+ const aParticleState = gpuCompute ? attribute("instanceParticleState") : null;
1255
+ const aStartValues = gpuCompute ? attribute("instanceStartValues") : null;
1256
+ const aInstanceQuat = gpuCompute ? null : attribute("instanceQuat");
1257
+ const aSize = gpuCompute ? null : attribute("instanceSize");
1258
+ const aLifetime = gpuCompute ? null : attribute("instanceLifetime");
1259
+ const aStartLifetime = gpuCompute ? null : attribute("instanceStartLifetime");
1260
+ const aRotation = gpuCompute ? null : attribute("instanceRotation");
1261
+ const aStartFrame = gpuCompute ? null : attribute("instanceStartFrame");
1262
+ const vColor = varyingProperty("vec4", "vColor");
1263
+ const vLifetime = varyingProperty("float", "vLifetime");
1264
+ const vStartLifetime = varyingProperty("float", "vStartLifetime");
1265
+ const vStartFrame = varyingProperty("float", "vStartFrame");
1266
+ const vRotation = varyingProperty("float", "vRotation");
1267
+ const vNormal = varyingProperty("vec3", "vNormal");
1268
+ const vViewZ = varyingProperty("float", "vViewZ");
1269
+ const vertexSetup = Fn(() => {
1270
+ const clipPos = vec4(0, 0, 0, 0).toVar();
1271
+ If(aColor.w.greaterThan(0), () => {
1272
+ vColor.assign(aColor.toVar());
1273
+ if (gpuCompute) {
1274
+ vLifetime.assign(aParticleState.x);
1275
+ vStartLifetime.assign(aStartValues.x);
1276
+ vStartFrame.assign(aParticleState.w);
1277
+ vRotation.assign(aParticleState.z);
1278
+ } else {
1279
+ vLifetime.assign(aLifetime);
1280
+ vStartLifetime.assign(aStartLifetime);
1281
+ vStartFrame.assign(aStartFrame);
1282
+ vRotation.assign(aRotation);
1283
+ }
1284
+ let quat;
1285
+ if (gpuCompute) {
1286
+ const halfZ = aParticleState.z.mul(0.5);
1287
+ quat = vec4(0, 0, sin(halfZ), cos(halfZ));
1288
+ } else {
1289
+ quat = aInstanceQuat;
1290
+ }
1291
+ const rotatedPos = applyQuaternion({
1292
+ v: positionLocal,
1293
+ q: quat
1294
+ });
1295
+ const scaledPos = rotatedPos.mul(gpuCompute ? aParticleState.y : aSize);
1296
+ const worldPos = scaledPos.add(aInstanceOffset.xyz);
1297
+ const mvPos = modelViewMatrix.mul(vec4(worldPos, 1));
1298
+ vViewZ.assign(mvPos.z.negate());
1299
+ const rotatedNormal = applyQuaternion({
1300
+ v: normalLocal,
1301
+ q: quat
1302
+ });
1303
+ const mvNormal = modelViewMatrix.mul(vec4(rotatedNormal, 0)).xyz;
1304
+ vNormal.assign(mvNormal.normalize());
1305
+ clipPos.assign(cameraProjectionMatrix.mul(mvPos));
1306
+ });
1307
+ return clipPos;
1308
+ })();
1309
+ const fragmentColor = Fn(() => {
1310
+ const outColor = vColor.toVar();
1311
+ const uvPoint = vec2(uv()).toVar();
1312
+ If(u.uTiles.x.greaterThan(1).or(u.uTiles.y.greaterThan(1)), () => {
1313
+ const frameIndex = computeFrameIndex({
1314
+ vLifetime,
1315
+ vStartLifetime,
1316
+ vStartFrame,
1317
+ uFps: u.uFps,
1318
+ uUseFPSForFrameIndex: u.uUseFPSForFrameIndex,
1319
+ uTiles: u.uTiles
1320
+ });
1321
+ uvPoint.assign(
1322
+ computeSpriteSheetUV({
1323
+ baseUV: uv(),
1324
+ frameIndex,
1325
+ uTiles: u.uTiles
1326
+ })
1327
+ );
1328
+ });
1329
+ const texColor = texture(u.uMap, uvPoint);
1330
+ outColor.assign(outColor.mul(texColor));
1331
+ applyBackgroundDiscard({
1332
+ texColor,
1333
+ uDiscardBg: u.uDiscardBg,
1334
+ uBgColor: u.uBgColor,
1335
+ uBgTolerance: u.uBgTolerance
1336
+ });
1337
+ const lightIntensity = float(0.5).add(
1338
+ float(0.5).mul(max(dot(vNormal, vec3(0, 0, 1)), float(0)))
1339
+ );
1340
+ outColor.assign(vec4(outColor.xyz.mul(lightIntensity), outColor.w));
1341
+ const softFade = computeSoftParticleFade({
1342
+ viewZ: vViewZ,
1343
+ uSoftEnabled: u.uSoftEnabled,
1344
+ uSoftIntensity: u.uSoftIntensity,
1345
+ uSceneDepthTex: u.uSceneDepthTex,
1346
+ uCameraNearFar: u.uCameraNearFar
1347
+ });
1348
+ outColor.assign(vec4(outColor.xyz, outColor.w.mul(softFade)));
1349
+ Discard(outColor.w.lessThan(ALPHA_DISCARD_THRESHOLD));
1350
+ return compensateOutputSRGB({ color: outColor });
1351
+ })();
1352
+ const material = new MeshBasicNodeMaterial();
1353
+ material.transparent = rendererConfig.transparent;
1354
+ material.blending = rendererConfig.blending;
1355
+ material.depthTest = rendererConfig.depthTest;
1356
+ material.depthWrite = rendererConfig.depthWrite;
1357
+ material.toneMapped = false;
1358
+ material.fog = false;
1359
+ material.vertexNode = vertexSetup;
1360
+ material.colorNode = fragmentColor;
1361
+ return material;
1362
+ }
1363
+ function createPointSpriteTSLMaterial(sharedUniforms, rendererConfig, gpuCompute = false) {
1364
+ const u = createParticleUniforms(sharedUniforms);
1365
+ const aColor = attribute("color");
1366
+ const aParticleState = gpuCompute ? attribute("particleState") : null;
1367
+ const aStartValues = gpuCompute ? attribute("startValues") : null;
1368
+ const aSize = gpuCompute ? null : attribute("size");
1369
+ const aLifetime = gpuCompute ? null : attribute("lifetime");
1370
+ const aStartLifetime = gpuCompute ? null : attribute("startLifetime");
1371
+ const aRotation = gpuCompute ? null : attribute("rotation");
1372
+ const aStartFrame = gpuCompute ? null : attribute("startFrame");
1373
+ const mvPos = modelViewMatrix.mul(vec4(positionLocal, 1));
1374
+ const sizeVal = gpuCompute ? aParticleState.y : aSize;
1375
+ const sizeNode = aColor.w.greaterThan(0).select(sizeVal.mul(POINT_SIZE_SCALE).div(length(mvPos.xyz)), float(0));
1376
+ const vColor = varyingProperty("vec4", "vColor");
1377
+ const vLifetime = varyingProperty("float", "vLifetime");
1378
+ const vStartLifetime = varyingProperty("float", "vStartLifetime");
1379
+ const vRotation = varyingProperty("float", "vRotation");
1380
+ const vStartFrame = varyingProperty("float", "vStartFrame");
1381
+ const vViewZ = varyingProperty("float", "vViewZ");
1382
+ const vertexSetup = Fn(() => {
1383
+ If(aColor.w.greaterThan(0), () => {
1384
+ vColor.assign(aColor.toVar());
1385
+ if (gpuCompute) {
1386
+ vLifetime.assign(aParticleState.x);
1387
+ vStartLifetime.assign(aStartValues.x);
1388
+ vRotation.assign(aParticleState.z);
1389
+ vStartFrame.assign(aParticleState.w);
1390
+ } else {
1391
+ vLifetime.assign(aLifetime);
1392
+ vStartLifetime.assign(aStartLifetime);
1393
+ vRotation.assign(aRotation);
1394
+ vStartFrame.assign(aStartFrame);
1395
+ }
1396
+ vViewZ.assign(mvPos.z.negate());
1397
+ });
1398
+ return positionLocal;
1399
+ })();
1400
+ const fragmentColor = Fn(() => {
1401
+ const outColor = vColor.toVar();
1402
+ const frameIndex = computeFrameIndex({
1403
+ vLifetime,
1404
+ vStartLifetime,
1405
+ vStartFrame,
1406
+ uFps: u.uFps,
1407
+ uUseFPSForFrameIndex: u.uUseFPSForFrameIndex,
1408
+ uTiles: u.uTiles
1409
+ });
1410
+ const center = vec2(0.5, 0.5);
1411
+ const centered = pointUV.sub(center);
1412
+ const cosR = cos(vRotation);
1413
+ const sinR = sin(vRotation);
1414
+ const rotated = vec2(
1415
+ centered.x.mul(cosR).add(centered.y.mul(sinR)),
1416
+ centered.x.mul(sinR).negate().add(centered.y.mul(cosR))
1417
+ );
1418
+ const rotatedUV = rotated.add(center);
1419
+ const dist = length(rotatedUV.sub(center));
1420
+ Discard(dist.greaterThan(0.5));
1421
+ const uvPoint = computeSpriteSheetUV({
1422
+ baseUV: rotatedUV,
1423
+ frameIndex,
1424
+ uTiles: u.uTiles
1425
+ });
1426
+ const texColor = texture(u.uMap, uvPoint);
1427
+ outColor.assign(outColor.mul(texColor));
1428
+ applyBackgroundDiscard({
1429
+ texColor,
1430
+ uDiscardBg: u.uDiscardBg,
1431
+ uBgColor: u.uBgColor,
1432
+ uBgTolerance: u.uBgTolerance
1433
+ });
1434
+ const softFade = computeSoftParticleFade({
1435
+ viewZ: vViewZ,
1436
+ uSoftEnabled: u.uSoftEnabled,
1437
+ uSoftIntensity: u.uSoftIntensity,
1438
+ uSceneDepthTex: u.uSceneDepthTex,
1439
+ uCameraNearFar: u.uCameraNearFar
1440
+ });
1441
+ outColor.assign(vec4(outColor.xyz, outColor.w.mul(softFade)));
1442
+ Discard(outColor.w.lessThan(ALPHA_DISCARD_THRESHOLD));
1443
+ return compensateOutputSRGB({ color: outColor });
1444
+ })();
1445
+ const material = new PointsNodeMaterial();
1446
+ material.transparent = rendererConfig.transparent;
1447
+ material.blending = rendererConfig.blending;
1448
+ material.depthTest = rendererConfig.depthTest;
1449
+ material.depthWrite = rendererConfig.depthWrite;
1450
+ material.toneMapped = false;
1451
+ material.fog = false;
1452
+ material.sizeNode = sizeNode;
1453
+ material.positionNode = vertexSetup;
1454
+ material.colorNode = fragmentColor;
1455
+ return material;
1456
+ }
1457
+ function createTrailUniforms(trailUniforms) {
1458
+ const dummy = getDummyTexture();
1459
+ const map = trailUniforms.map.value ?? dummy;
1460
+ if (map) map.colorSpace = NoColorSpace;
1461
+ return {
1462
+ uMap: map,
1463
+ uUseMap: uniform(float(trailUniforms.useMap.value ? 1 : 0)),
1464
+ uDiscardBg: uniform(
1465
+ float(trailUniforms.discardBackgroundColor.value ? 1 : 0)
1466
+ ),
1467
+ uBgColor: uniform(
1468
+ new trailUniforms.cameraNearFar.value.constructor(
1469
+ trailUniforms.backgroundColor.value.r,
1470
+ trailUniforms.backgroundColor.value.g,
1471
+ trailUniforms.backgroundColor.value.b
1472
+ )
1473
+ ),
1474
+ uBgTolerance: uniform(float(trailUniforms.backgroundColorTolerance.value)),
1475
+ uSoftEnabled: uniform(
1476
+ float(trailUniforms.softParticlesEnabled.value ? 1 : 0)
1477
+ ),
1478
+ uSoftIntensity: uniform(float(trailUniforms.softParticlesIntensity.value)),
1479
+ uSceneDepthTex: trailUniforms.sceneDepthTexture.value ?? dummy,
1480
+ uCameraNearFar: uniform(trailUniforms.cameraNearFar.value)
1481
+ };
1482
+ }
1483
+ function createTrailRibbonTSLMaterial(trailUniforms, rendererConfig) {
1484
+ const u = createTrailUniforms(trailUniforms);
1485
+ const aTrailAlpha = attribute("trailAlpha");
1486
+ const aTrailColor = attribute("trailColor", "vec4");
1487
+ const aTrailOffset = attribute("trailOffset");
1488
+ const aTrailHalfWidth = attribute("trailHalfWidth");
1489
+ const aTrailNext = attribute("trailNext", "vec3");
1490
+ const aTrailUV = attribute("trailUV", "vec2");
1491
+ const vAlpha = varyingProperty("float", "vAlpha");
1492
+ const vColor = varyingProperty("vec4", "vColor");
1493
+ const vUv = varyingProperty("vec2", "vUv");
1494
+ const vViewZ = varyingProperty("float", "vViewZ");
1495
+ const positionNode = Fn(() => {
1496
+ vAlpha.assign(aTrailAlpha);
1497
+ vColor.assign(aTrailColor);
1498
+ vUv.assign(aTrailUV);
1499
+ const current = vec3(positionLocal);
1500
+ const next = vec3(aTrailNext);
1501
+ const rawTangent = next.sub(current);
1502
+ const tangentLen = length(rawTangent);
1503
+ const tangent = normalize(
1504
+ tangentLen.lessThan(1e-4).select(vec3(0, 1, 0), rawTangent)
1505
+ );
1506
+ modelViewMatrix.mul(vec4(current, 1));
1507
+ const viewDir = normalize(cameraPosition.sub(current));
1508
+ const rawPerp = cross(tangent, viewDir);
1509
+ const perpLen = length(rawPerp);
1510
+ const camRight = vec3(
1511
+ cameraViewMatrix.element(0).element(0),
1512
+ cameraViewMatrix.element(1).element(0),
1513
+ cameraViewMatrix.element(2).element(0)
1514
+ );
1515
+ const camRightDotTangent = dot(camRight, tangent);
1516
+ const fallbackPerp = normalize(
1517
+ camRight.sub(tangent.mul(camRightDotTangent))
1518
+ );
1519
+ const perp = normalize(
1520
+ perpLen.lessThan(1e-4).select(
1521
+ fallbackPerp,
1522
+ normalize(
1523
+ mix(
1524
+ fallbackPerp,
1525
+ normalize(rawPerp),
1526
+ smoothstep(float(0), float(0.7), perpLen)
1527
+ )
1528
+ )
1529
+ )
1530
+ );
1531
+ const offsetPos = current.add(perp.mul(aTrailOffset).mul(aTrailHalfWidth));
1532
+ const mvOffset = modelViewMatrix.mul(vec4(offsetPos, 1));
1533
+ vViewZ.assign(mvOffset.z.negate());
1534
+ return offsetPos;
1535
+ })();
1536
+ const colorNode = Fn(() => {
1537
+ const outColor = vColor.toVar();
1538
+ const edgeDist = float(1).sub(abs(vUv.x.mul(2).sub(1)));
1539
+ const edgeFade = smoothstep(float(0), float(0.4), edgeDist);
1540
+ If(u.uUseMap.greaterThan(0.5), () => {
1541
+ const texColor = texture(u.uMap, vUv);
1542
+ const texBrightness = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
1543
+ outColor.rgb.assign(
1544
+ outColor.rgb.mul(float(0.5).add(texBrightness.mul(0.5)))
1545
+ );
1546
+ outColor.a.assign(outColor.a.mul(texColor.a));
1547
+ });
1548
+ outColor.a.assign(outColor.a.mul(vAlpha).mul(edgeFade));
1549
+ Discard(outColor.a.lessThan(ALPHA_DISCARD_THRESHOLD));
1550
+ If(u.uSoftEnabled.greaterThan(0.5), () => {
1551
+ const depthSample = texture(u.uSceneDepthTex, screenUV).x;
1552
+ const sceneDepthLinear = linearizeDepth({
1553
+ depthSample,
1554
+ near: u.uCameraNearFar.x,
1555
+ far: u.uCameraNearFar.y
1556
+ });
1557
+ const depthDiff = sceneDepthLinear.sub(vViewZ);
1558
+ const softFade = smoothstep(float(0), u.uSoftIntensity, depthDiff);
1559
+ outColor.a.assign(outColor.a.mul(softFade));
1560
+ });
1561
+ Discard(outColor.a.lessThan(ALPHA_DISCARD_THRESHOLD));
1562
+ const diff = vec3(
1563
+ outColor.r.sub(u.uBgColor.x),
1564
+ outColor.g.sub(u.uBgColor.y),
1565
+ outColor.b.sub(u.uBgColor.z)
1566
+ );
1567
+ Discard(
1568
+ u.uDiscardBg.greaterThan(0.5).and(abs(length(diff)).lessThan(u.uBgTolerance))
1569
+ );
1570
+ return compensateOutputSRGB({ color: outColor });
1571
+ })();
1572
+ const material = new MeshBasicNodeMaterial();
1573
+ material.transparent = rendererConfig.transparent;
1574
+ material.blending = rendererConfig.blending;
1575
+ material.depthTest = rendererConfig.depthTest;
1576
+ material.depthWrite = rendererConfig.depthWrite;
1577
+ material.toneMapped = false;
1578
+ material.fog = false;
1579
+ material.side = DoubleSide;
1580
+ material.positionNode = positionNode;
1581
+ material.colorNode = colorNode;
1582
+ return material;
1583
+ }
1584
+
1585
+ // src/js/effects/three-particles/webgpu/tsl-materials.ts
1586
+ function createTSLParticleMaterial(rendererType, sharedUniforms, rendererConfig, gpuCompute = false) {
1587
+ switch (rendererType) {
1588
+ case "INSTANCED" /* INSTANCED */:
1589
+ return createInstancedBillboardTSLMaterial(
1590
+ sharedUniforms,
1591
+ rendererConfig,
1592
+ gpuCompute
1593
+ );
1594
+ case "MESH" /* MESH */:
1595
+ return createMeshParticleTSLMaterial(
1596
+ sharedUniforms,
1597
+ rendererConfig,
1598
+ gpuCompute
1599
+ );
1600
+ case "POINTS" /* POINTS */:
1601
+ default:
1602
+ return createPointSpriteTSLMaterial(
1603
+ sharedUniforms,
1604
+ rendererConfig,
1605
+ gpuCompute
1606
+ );
1607
+ }
1608
+ }
1609
+ function createTSLTrailMaterial(trailUniforms, rendererConfig) {
1610
+ return createTrailRibbonTSLMaterial(trailUniforms, rendererConfig);
1611
+ }
1612
+ function createComputePipeline(maxParticles, instanced, normalizedConfig, particleSystemId, forceFieldCount, collisionPlaneCount = 0) {
1613
+ const bakedCurves = bakeParticleSystemCurves(
1614
+ normalizedConfig,
1615
+ particleSystemId
1616
+ );
1617
+ const { velocityOverLifetime } = normalizedConfig;
1618
+ const flags = {
1619
+ sizeOverLifetime: normalizedConfig.sizeOverLifetime.isActive,
1620
+ opacityOverLifetime: normalizedConfig.opacityOverLifetime.isActive,
1621
+ colorOverLifetime: normalizedConfig.colorOverLifetime.isActive,
1622
+ rotationOverLifetime: normalizedConfig.rotationOverLifetime.isActive,
1623
+ linearVelocity: velocityOverLifetime.isActive && (isLifeTimeCurve(velocityOverLifetime.linear.x ?? 0) || isLifeTimeCurve(velocityOverLifetime.linear.y ?? 0) || isLifeTimeCurve(velocityOverLifetime.linear.z ?? 0) || velocityOverLifetime.linear.x !== 0 || velocityOverLifetime.linear.y !== 0 || velocityOverLifetime.linear.z !== 0),
1624
+ orbitalVelocity: velocityOverLifetime.isActive && (isLifeTimeCurve(velocityOverLifetime.orbital.x ?? 0) || isLifeTimeCurve(velocityOverLifetime.orbital.y ?? 0) || isLifeTimeCurve(velocityOverLifetime.orbital.z ?? 0) || velocityOverLifetime.orbital.x !== 0 || velocityOverLifetime.orbital.y !== 0 || velocityOverLifetime.orbital.z !== 0),
1625
+ noise: normalizedConfig.noise.isActive,
1626
+ forceFields: forceFieldCount > 0,
1627
+ collisionPlanes: collisionPlaneCount > 0
1628
+ };
1629
+ const buffers = createModifierStorageBuffers(
1630
+ maxParticles,
1631
+ instanced,
1632
+ bakedCurves.data,
1633
+ flags.forceFields,
1634
+ flags.collisionPlanes
1635
+ );
1636
+ return createModifierComputeUpdate(
1637
+ buffers,
1638
+ maxParticles,
1639
+ bakedCurves,
1640
+ flags,
1641
+ forceFieldCount,
1642
+ collisionPlaneCount
1643
+ );
1644
+ }
1645
+
1646
+ // src/webgpu.ts
1647
+ function enableWebGPU() {
1648
+ const factory = {
1649
+ createTSLParticleMaterial,
1650
+ createTSLTrailMaterial,
1651
+ createComputePipeline,
1652
+ writeParticleToModifierBuffers,
1653
+ deactivateParticleInModifierBuffers,
1654
+ flushEmitQueue,
1655
+ registerCurveDataLength,
1656
+ encodeForceFieldsForGPU,
1657
+ encodeCollisionPlanesForGPU
1658
+ };
1659
+ registerTSLMaterialFactory(factory);
1660
+ }
1661
+
1662
+ export { createComputePipeline, createTSLParticleMaterial, createTSLTrailMaterial, deactivateParticleInModifierBuffers, enableWebGPU, encodeCollisionPlanesForGPU, encodeForceFieldsForGPU, flushEmitQueue, registerCurveDataLength, writeParticleToModifierBuffers };
1663
+ //# sourceMappingURL=webgpu.js.map
1664
+ //# sourceMappingURL=webgpu.js.map