@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/LICENSE +21 -0
- package/README.md +197 -0
- package/dist/index.d.ts +2970 -0
- package/dist/index.js +3138 -0
- package/dist/index.js.map +1 -0
- package/dist/three-particles.min.js +1 -0
- package/dist/three-particles.min.js.map +1 -0
- package/dist/webgpu.js +2522 -0
- package/dist/webgpu.js.map +1 -0
- package/llms-full.txt +1005 -0
- package/llms.txt +339 -0
- package/package.json +111 -0
- package/webgpu.d.ts +98 -0
package/dist/webgpu.js
ADDED
|
@@ -0,0 +1,2522 @@
|
|
|
1
|
+
import { registerTSLMaterialFactory } from '@cyberluke/three-particles';
|
|
2
|
+
export { assertNamed, normalizeBackgroundToVector3, normalizeDepthTextureValue, normalizeTextureValue, normalizeVector2Value, resolveWebGPUEffectiveRendererType } from '@cyberluke/three-particles';
|
|
3
|
+
import { Fn, mod, float, floor, dot, vec3, step, min, max, vec4, vec2, abs, uint, round, If, texture, screenUV, smoothstep, cross, uniform, storage, atomicStore, compute, atomicLoad, instanceIndex, atomicAdd, sqrt, mix, buffer, cos, sin, fract, attribute, modelViewMatrix, positionLocal, length, varyingProperty, Discard, normalLocal, cameraProjectionMatrix, uv, normalize, cameraPosition, cameraViewMatrix, Loop, Continue } from 'three/tsl';
|
|
4
|
+
import * as THREE from 'three';
|
|
5
|
+
import { Vector4, Vector3, DoubleSide, DataTexture } from 'three';
|
|
6
|
+
import { StorageBufferAttribute, StorageInstancedBufferAttribute, PointsNodeMaterial, MeshBasicNodeMaterial } from 'three/webgpu';
|
|
7
|
+
|
|
8
|
+
// src/webgpu.ts
|
|
9
|
+
var PLANE_STRIDE = 12;
|
|
10
|
+
var MAX_COLLISION_PLANES = 16;
|
|
11
|
+
var COLLISION_PLANE_DATA_SIZE = MAX_COLLISION_PLANES * PLANE_STRIDE;
|
|
12
|
+
var _encodeBuf = null;
|
|
13
|
+
function encodeCollisionPlanesForGPU(planes) {
|
|
14
|
+
if (!_encodeBuf || _encodeBuf.length !== COLLISION_PLANE_DATA_SIZE) {
|
|
15
|
+
_encodeBuf = new Float32Array(COLLISION_PLANE_DATA_SIZE);
|
|
16
|
+
}
|
|
17
|
+
const data = _encodeBuf;
|
|
18
|
+
data.fill(0);
|
|
19
|
+
const count = Math.min(planes.length, MAX_COLLISION_PLANES);
|
|
20
|
+
for (let i = 0; i < count; i++) {
|
|
21
|
+
const cp = planes[i];
|
|
22
|
+
const base = i * PLANE_STRIDE;
|
|
23
|
+
data[base] = cp.isActive ? 1 : 0;
|
|
24
|
+
let modeCode = 0;
|
|
25
|
+
if (cp.mode === "CLAMP" /* CLAMP */) modeCode = 1;
|
|
26
|
+
else if (cp.mode === "BOUNCE" /* BOUNCE */) modeCode = 2;
|
|
27
|
+
data[base + 1] = modeCode;
|
|
28
|
+
data[base + 2] = cp.position.x;
|
|
29
|
+
data[base + 3] = cp.position.y;
|
|
30
|
+
data[base + 4] = cp.position.z;
|
|
31
|
+
data[base + 5] = cp.normal.x;
|
|
32
|
+
data[base + 6] = cp.normal.y;
|
|
33
|
+
data[base + 7] = cp.normal.z;
|
|
34
|
+
data[base + 8] = cp.dampen;
|
|
35
|
+
data[base + 9] = cp.lifetimeLoss;
|
|
36
|
+
data[base + 10] = 0;
|
|
37
|
+
data[base + 11] = 0;
|
|
38
|
+
}
|
|
39
|
+
return data;
|
|
40
|
+
}
|
|
41
|
+
function createCollisionPlaneTSL(sCurveData, collisionPlaneOffset, collisionPlaneCount) {
|
|
42
|
+
const count = Math.min(collisionPlaneCount, MAX_COLLISION_PLANES);
|
|
43
|
+
const uCollisionPlaneCount = uniform(float(count));
|
|
44
|
+
const cpBase = collisionPlaneOffset;
|
|
45
|
+
const applyCollisionPlanesTSL = Fn(
|
|
46
|
+
({
|
|
47
|
+
pos,
|
|
48
|
+
vel,
|
|
49
|
+
oiaVec,
|
|
50
|
+
sColorNode,
|
|
51
|
+
ps,
|
|
52
|
+
startLife,
|
|
53
|
+
particleIdx,
|
|
54
|
+
sOrbitalIsActiveNode
|
|
55
|
+
}) => {
|
|
56
|
+
Loop(uCollisionPlaneCount, ({ i }) => {
|
|
57
|
+
const base = i.mul(PLANE_STRIDE).add(cpBase);
|
|
58
|
+
const isActive = sCurveData.element(base);
|
|
59
|
+
If(isActive.lessThan(0.5), () => {
|
|
60
|
+
Continue();
|
|
61
|
+
});
|
|
62
|
+
const mode = sCurveData.element(base.add(1));
|
|
63
|
+
const planePos = vec3(
|
|
64
|
+
sCurveData.element(base.add(2)),
|
|
65
|
+
sCurveData.element(base.add(3)),
|
|
66
|
+
sCurveData.element(base.add(4))
|
|
67
|
+
);
|
|
68
|
+
const planeNormal = vec3(
|
|
69
|
+
sCurveData.element(base.add(5)),
|
|
70
|
+
sCurveData.element(base.add(6)),
|
|
71
|
+
sCurveData.element(base.add(7))
|
|
72
|
+
);
|
|
73
|
+
const dampen = sCurveData.element(base.add(8));
|
|
74
|
+
const lifetimeLoss = sCurveData.element(base.add(9));
|
|
75
|
+
const toParticle = pos.sub(planePos);
|
|
76
|
+
const signedDist = dot(toParticle, planeNormal);
|
|
77
|
+
If(signedDist.lessThan(0), () => {
|
|
78
|
+
If(mode.lessThan(0.5), () => {
|
|
79
|
+
ps.x.assign(startLife.add(float(1)));
|
|
80
|
+
}).ElseIf(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
|
+
}).Else(() => {
|
|
87
|
+
pos.assign(pos.sub(planeNormal.mul(signedDist)));
|
|
88
|
+
const vDotN = dot(vel, planeNormal);
|
|
89
|
+
const reflected = vel.sub(planeNormal.mul(vDotN.mul(2)));
|
|
90
|
+
vel.assign(reflected.mul(dampen));
|
|
91
|
+
If(lifetimeLoss.greaterThan(0), () => {
|
|
92
|
+
ps.x.assign(ps.x.add(lifetimeLoss.mul(startLife).mul(1e3)));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
"void"
|
|
99
|
+
);
|
|
100
|
+
return {
|
|
101
|
+
/** Uniform for the active collision plane count. */
|
|
102
|
+
countUniform: uCollisionPlaneCount,
|
|
103
|
+
/** TSL function to call in the compute kernel: apply({ pos, vel, ... }) */
|
|
104
|
+
apply: applyCollisionPlanesTSL
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/js/effects/three-particles/three-particles-bezier.ts
|
|
109
|
+
var cache = [];
|
|
110
|
+
var nCr = (n, k) => {
|
|
111
|
+
let z = 1;
|
|
112
|
+
for (let i = 1; i <= k; i++) z *= (n + 1 - i) / i;
|
|
113
|
+
return z;
|
|
114
|
+
};
|
|
115
|
+
var createBezierCurveFunction = (particleSystemId, bezierPoints) => {
|
|
116
|
+
const cacheEntry = cache.find((item) => item.bezierPoints === bezierPoints);
|
|
117
|
+
if (cacheEntry) {
|
|
118
|
+
if (!cacheEntry.referencedBy.includes(particleSystemId))
|
|
119
|
+
cacheEntry.referencedBy.push(particleSystemId);
|
|
120
|
+
return cacheEntry.curveFunction;
|
|
121
|
+
}
|
|
122
|
+
const entry = {
|
|
123
|
+
referencedBy: [particleSystemId],
|
|
124
|
+
bezierPoints,
|
|
125
|
+
curveFunction: (percentage) => {
|
|
126
|
+
if (percentage < 0) return bezierPoints[0].y;
|
|
127
|
+
if (percentage > 1) return bezierPoints[bezierPoints.length - 1].y;
|
|
128
|
+
let start = 0;
|
|
129
|
+
let stop = bezierPoints.length - 1;
|
|
130
|
+
for (let i = 0; i < bezierPoints.length; i++) {
|
|
131
|
+
const point = bezierPoints[i];
|
|
132
|
+
if (percentage < (point.percentage ?? 0)) {
|
|
133
|
+
stop = i;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
if (point.percentage !== void 0) start = i;
|
|
137
|
+
}
|
|
138
|
+
const n = stop - start;
|
|
139
|
+
const calculatedPercentage = (percentage - (bezierPoints[start].percentage ?? 0)) / ((bezierPoints[stop].percentage ?? 1) - (bezierPoints[start].percentage ?? 0));
|
|
140
|
+
let value = 0;
|
|
141
|
+
for (let i = 0; i <= n; i++) {
|
|
142
|
+
const p = bezierPoints[start + i];
|
|
143
|
+
const c = nCr(n, i) * Math.pow(1 - calculatedPercentage, n - i) * Math.pow(calculatedPercentage, i);
|
|
144
|
+
value += c * p.y;
|
|
145
|
+
}
|
|
146
|
+
return value;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
cache.push(entry);
|
|
150
|
+
return entry.curveFunction;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// src/js/effects/three-particles/three-particles-utils.ts
|
|
154
|
+
var isLifeTimeCurve = (value) => {
|
|
155
|
+
return typeof value !== "number" && "type" in value;
|
|
156
|
+
};
|
|
157
|
+
var getCurveFunctionFromConfig = (particleSystemId, lifetimeCurve) => {
|
|
158
|
+
if (lifetimeCurve.type === "BEZIER" /* BEZIER */) {
|
|
159
|
+
return createBezierCurveFunction(
|
|
160
|
+
particleSystemId,
|
|
161
|
+
lifetimeCurve.bezierPoints
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (lifetimeCurve.type === "EASING" /* EASING */) {
|
|
165
|
+
return lifetimeCurve.curveFunction;
|
|
166
|
+
}
|
|
167
|
+
const raw = lifetimeCurve;
|
|
168
|
+
if (Array.isArray(raw.bezierPoints)) {
|
|
169
|
+
return createBezierCurveFunction(
|
|
170
|
+
particleSystemId,
|
|
171
|
+
raw.bezierPoints
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
if (typeof raw.curveFunction === "function") {
|
|
175
|
+
return raw.curveFunction;
|
|
176
|
+
}
|
|
177
|
+
throw new Error(`Unsupported value type: ${lifetimeCurve}`);
|
|
178
|
+
};
|
|
179
|
+
var calculateValue = (particleSystemId, value, time = 0) => {
|
|
180
|
+
if (typeof value === "number") {
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
if ("min" in value && "max" in value) {
|
|
184
|
+
if (value.min === value.max) {
|
|
185
|
+
return value.min ?? 0;
|
|
186
|
+
}
|
|
187
|
+
return THREE.MathUtils.randFloat(value.min ?? 0, value.max ?? 1);
|
|
188
|
+
}
|
|
189
|
+
const lifetimeCurve = value;
|
|
190
|
+
return getCurveFunctionFromConfig(particleSystemId, lifetimeCurve)(time) * (lifetimeCurve.scale ?? 1);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// src/js/effects/three-particles/webgpu/compute-force-fields.ts
|
|
194
|
+
var FIELD_STRIDE = 12;
|
|
195
|
+
var MAX_FORCE_FIELDS = 16;
|
|
196
|
+
var FORCE_FIELD_DATA_SIZE = MAX_FORCE_FIELDS * FIELD_STRIDE;
|
|
197
|
+
var GPU_INFINITY = 1e10;
|
|
198
|
+
var _encodeBuf2 = null;
|
|
199
|
+
function encodeForceFieldsForGPU(forceFields, particleSystemId, systemLifetimePercentage) {
|
|
200
|
+
if (!_encodeBuf2 || _encodeBuf2.length !== FORCE_FIELD_DATA_SIZE) {
|
|
201
|
+
_encodeBuf2 = new Float32Array(FORCE_FIELD_DATA_SIZE);
|
|
202
|
+
}
|
|
203
|
+
const data = _encodeBuf2;
|
|
204
|
+
data.fill(0);
|
|
205
|
+
const count = Math.min(forceFields.length, MAX_FORCE_FIELDS);
|
|
206
|
+
for (let i = 0; i < count; i++) {
|
|
207
|
+
const ff = forceFields[i];
|
|
208
|
+
const base = i * FIELD_STRIDE;
|
|
209
|
+
data[base] = ff.isActive ? 1 : 0;
|
|
210
|
+
data[base + 1] = ff.type === "POINT" /* POINT */ ? 0 : 1;
|
|
211
|
+
data[base + 2] = ff.position.x;
|
|
212
|
+
data[base + 3] = ff.position.y;
|
|
213
|
+
data[base + 4] = ff.position.z;
|
|
214
|
+
data[base + 5] = ff.direction.x;
|
|
215
|
+
data[base + 6] = ff.direction.y;
|
|
216
|
+
data[base + 7] = ff.direction.z;
|
|
217
|
+
data[base + 8] = calculateValue(
|
|
218
|
+
particleSystemId,
|
|
219
|
+
ff.strength,
|
|
220
|
+
systemLifetimePercentage
|
|
221
|
+
);
|
|
222
|
+
data[base + 9] = ff.range === Infinity ? GPU_INFINITY : ff.range;
|
|
223
|
+
let falloffCode = 0;
|
|
224
|
+
if (ff.falloff === "LINEAR" /* LINEAR */) falloffCode = 1;
|
|
225
|
+
else if (ff.falloff === "QUADRATIC" /* QUADRATIC */) falloffCode = 2;
|
|
226
|
+
data[base + 10] = falloffCode;
|
|
227
|
+
data[base + 11] = 0;
|
|
228
|
+
}
|
|
229
|
+
return data;
|
|
230
|
+
}
|
|
231
|
+
function createForceFieldTSL(sCurveData, forceFieldOffset, forceFieldCount) {
|
|
232
|
+
const count = Math.min(forceFieldCount, MAX_FORCE_FIELDS);
|
|
233
|
+
const uForceFieldCount = uniform(float(count));
|
|
234
|
+
const ffBase = forceFieldOffset;
|
|
235
|
+
const applyForceFieldsTSL = Fn(
|
|
236
|
+
({
|
|
237
|
+
pos,
|
|
238
|
+
vel,
|
|
239
|
+
delta
|
|
240
|
+
}) => {
|
|
241
|
+
Loop(uForceFieldCount, ({ i }) => {
|
|
242
|
+
const base = i.mul(FIELD_STRIDE).add(ffBase);
|
|
243
|
+
const isActive = sCurveData.element(base);
|
|
244
|
+
If(isActive.lessThan(0.5), () => {
|
|
245
|
+
Continue();
|
|
246
|
+
});
|
|
247
|
+
const fieldType = sCurveData.element(base.add(1));
|
|
248
|
+
const fieldPos = vec3(
|
|
249
|
+
sCurveData.element(base.add(2)),
|
|
250
|
+
sCurveData.element(base.add(3)),
|
|
251
|
+
sCurveData.element(base.add(4))
|
|
252
|
+
);
|
|
253
|
+
const fieldDir = vec3(
|
|
254
|
+
sCurveData.element(base.add(5)),
|
|
255
|
+
sCurveData.element(base.add(6)),
|
|
256
|
+
sCurveData.element(base.add(7))
|
|
257
|
+
);
|
|
258
|
+
const strength = sCurveData.element(base.add(8));
|
|
259
|
+
const range = sCurveData.element(base.add(9));
|
|
260
|
+
const falloffType = sCurveData.element(base.add(10));
|
|
261
|
+
If(strength.equal(0), () => {
|
|
262
|
+
Continue();
|
|
263
|
+
});
|
|
264
|
+
If(fieldType.greaterThan(0.5), () => {
|
|
265
|
+
const force = strength.mul(delta);
|
|
266
|
+
vel.assign(vel.add(fieldDir.mul(force)));
|
|
267
|
+
});
|
|
268
|
+
If(fieldType.lessThan(0.5), () => {
|
|
269
|
+
const toField = fieldPos.sub(pos);
|
|
270
|
+
const dist = length(toField);
|
|
271
|
+
If(dist.greaterThan(1e-4), () => {
|
|
272
|
+
const inRange = dist.lessThan(range);
|
|
273
|
+
If(inRange, () => {
|
|
274
|
+
const dir = normalize(toField);
|
|
275
|
+
const normDist = dist.div(range);
|
|
276
|
+
const falloffNone = float(1);
|
|
277
|
+
const falloffLinear = float(1).sub(normDist);
|
|
278
|
+
const falloffQuadratic = float(1).sub(normDist.mul(normDist));
|
|
279
|
+
const useLinear = falloffType.greaterThan(0.5);
|
|
280
|
+
const useQuadratic = falloffType.greaterThan(1.5);
|
|
281
|
+
const falloff = useQuadratic.select(
|
|
282
|
+
falloffQuadratic,
|
|
283
|
+
useLinear.select(falloffLinear, falloffNone)
|
|
284
|
+
);
|
|
285
|
+
const force = strength.mul(falloff).mul(delta);
|
|
286
|
+
vel.assign(vel.add(dir.mul(force)));
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
},
|
|
292
|
+
"void"
|
|
293
|
+
);
|
|
294
|
+
return {
|
|
295
|
+
/** Uniform for the active force field count. */
|
|
296
|
+
countUniform: uForceFieldCount,
|
|
297
|
+
/** TSL function to call in the compute kernel: apply({ pos, vel, delta }) */
|
|
298
|
+
apply: applyForceFieldsTSL
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/js/effects/three-particles/webgpu/curve-bake.ts
|
|
303
|
+
var CURVE_RESOLUTION = 256;
|
|
304
|
+
function bakeCurve(curveFn, resolution = CURVE_RESOLUTION) {
|
|
305
|
+
const samples = new Float32Array(resolution);
|
|
306
|
+
const lastIndex = resolution - 1;
|
|
307
|
+
for (let i = 0; i < resolution; i++) {
|
|
308
|
+
const t = lastIndex === 0 ? 0 : i / lastIndex;
|
|
309
|
+
samples[i] = curveFn(t);
|
|
310
|
+
}
|
|
311
|
+
return samples;
|
|
312
|
+
}
|
|
313
|
+
function bakeCurveIntoBuffer(buffer2, writeOffset, particleSystemId, curve) {
|
|
314
|
+
const curveFn = getCurveFunctionFromConfig(particleSystemId, curve);
|
|
315
|
+
const lastIndex = CURVE_RESOLUTION - 1;
|
|
316
|
+
for (let i = 0; i < CURVE_RESOLUTION; i++) {
|
|
317
|
+
const t = i / lastIndex;
|
|
318
|
+
buffer2[writeOffset + i] = curveFn(t);
|
|
319
|
+
}
|
|
320
|
+
return writeOffset + CURVE_RESOLUTION;
|
|
321
|
+
}
|
|
322
|
+
function bakeVelocityAxisIntoBuffer(buffer2, writeOffset, particleSystemId, value) {
|
|
323
|
+
if (isLifeTimeCurve(value)) {
|
|
324
|
+
return bakeCurveIntoBuffer(buffer2, writeOffset, particleSystemId, value);
|
|
325
|
+
}
|
|
326
|
+
return writeOffset;
|
|
327
|
+
}
|
|
328
|
+
function bakeParticleSystemCurves(normalizedConfig, particleSystemId) {
|
|
329
|
+
let curveCount = 0;
|
|
330
|
+
const {
|
|
331
|
+
sizeOverLifetime,
|
|
332
|
+
opacityOverLifetime,
|
|
333
|
+
colorOverLifetime,
|
|
334
|
+
velocityOverLifetime
|
|
335
|
+
} = normalizedConfig;
|
|
336
|
+
const hasSizeOverLifetime = sizeOverLifetime.isActive;
|
|
337
|
+
const hasOpacityOverLifetime = opacityOverLifetime.isActive;
|
|
338
|
+
const hasColorOverLifetime = colorOverLifetime.isActive;
|
|
339
|
+
const isVelActive = velocityOverLifetime.isActive;
|
|
340
|
+
const isCurveAxis = (v) => isVelActive && v !== void 0 && isLifeTimeCurve(v);
|
|
341
|
+
const hasLinearVelX = isCurveAxis(velocityOverLifetime.linear.x);
|
|
342
|
+
const hasLinearVelY = isCurveAxis(velocityOverLifetime.linear.y);
|
|
343
|
+
const hasLinearVelZ = isCurveAxis(velocityOverLifetime.linear.z);
|
|
344
|
+
const hasOrbitalVelX = isCurveAxis(velocityOverLifetime.orbital.x);
|
|
345
|
+
const hasOrbitalVelY = isCurveAxis(velocityOverLifetime.orbital.y);
|
|
346
|
+
const hasOrbitalVelZ = isCurveAxis(velocityOverLifetime.orbital.z);
|
|
347
|
+
if (hasSizeOverLifetime) curveCount++;
|
|
348
|
+
if (hasOpacityOverLifetime) curveCount++;
|
|
349
|
+
if (hasColorOverLifetime) curveCount += 3;
|
|
350
|
+
if (hasLinearVelX) curveCount++;
|
|
351
|
+
if (hasLinearVelY) curveCount++;
|
|
352
|
+
if (hasLinearVelZ) curveCount++;
|
|
353
|
+
if (hasOrbitalVelX) curveCount++;
|
|
354
|
+
if (hasOrbitalVelY) curveCount++;
|
|
355
|
+
if (hasOrbitalVelZ) curveCount++;
|
|
356
|
+
const data = new Float32Array(curveCount * CURVE_RESOLUTION);
|
|
357
|
+
let writeOffset = 0;
|
|
358
|
+
let nextIndex = 0;
|
|
359
|
+
let sizeOverLifetimeIdx = -1;
|
|
360
|
+
let opacityOverLifetimeIdx = -1;
|
|
361
|
+
let colorRIdx = -1;
|
|
362
|
+
let colorGIdx = -1;
|
|
363
|
+
let colorBIdx = -1;
|
|
364
|
+
let linearVelXIdx = -1;
|
|
365
|
+
let linearVelYIdx = -1;
|
|
366
|
+
let linearVelZIdx = -1;
|
|
367
|
+
let orbitalVelXIdx = -1;
|
|
368
|
+
let orbitalVelYIdx = -1;
|
|
369
|
+
let orbitalVelZIdx = -1;
|
|
370
|
+
if (hasSizeOverLifetime) {
|
|
371
|
+
sizeOverLifetimeIdx = nextIndex++;
|
|
372
|
+
writeOffset = bakeCurveIntoBuffer(
|
|
373
|
+
data,
|
|
374
|
+
writeOffset,
|
|
375
|
+
particleSystemId,
|
|
376
|
+
sizeOverLifetime.lifetimeCurve
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
if (hasOpacityOverLifetime) {
|
|
380
|
+
opacityOverLifetimeIdx = nextIndex++;
|
|
381
|
+
writeOffset = bakeCurveIntoBuffer(
|
|
382
|
+
data,
|
|
383
|
+
writeOffset,
|
|
384
|
+
particleSystemId,
|
|
385
|
+
opacityOverLifetime.lifetimeCurve
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
if (hasColorOverLifetime) {
|
|
389
|
+
colorRIdx = nextIndex++;
|
|
390
|
+
writeOffset = bakeCurveIntoBuffer(
|
|
391
|
+
data,
|
|
392
|
+
writeOffset,
|
|
393
|
+
particleSystemId,
|
|
394
|
+
colorOverLifetime.r
|
|
395
|
+
);
|
|
396
|
+
colorGIdx = nextIndex++;
|
|
397
|
+
writeOffset = bakeCurveIntoBuffer(
|
|
398
|
+
data,
|
|
399
|
+
writeOffset,
|
|
400
|
+
particleSystemId,
|
|
401
|
+
colorOverLifetime.g
|
|
402
|
+
);
|
|
403
|
+
colorBIdx = nextIndex++;
|
|
404
|
+
writeOffset = bakeCurveIntoBuffer(
|
|
405
|
+
data,
|
|
406
|
+
writeOffset,
|
|
407
|
+
particleSystemId,
|
|
408
|
+
colorOverLifetime.b
|
|
409
|
+
);
|
|
410
|
+
}
|
|
411
|
+
if (hasLinearVelX) {
|
|
412
|
+
linearVelXIdx = nextIndex++;
|
|
413
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
414
|
+
data,
|
|
415
|
+
writeOffset,
|
|
416
|
+
particleSystemId,
|
|
417
|
+
velocityOverLifetime.linear.x
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
if (hasLinearVelY) {
|
|
421
|
+
linearVelYIdx = nextIndex++;
|
|
422
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
423
|
+
data,
|
|
424
|
+
writeOffset,
|
|
425
|
+
particleSystemId,
|
|
426
|
+
velocityOverLifetime.linear.y
|
|
427
|
+
);
|
|
428
|
+
}
|
|
429
|
+
if (hasLinearVelZ) {
|
|
430
|
+
linearVelZIdx = nextIndex++;
|
|
431
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
432
|
+
data,
|
|
433
|
+
writeOffset,
|
|
434
|
+
particleSystemId,
|
|
435
|
+
velocityOverLifetime.linear.z
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
if (hasOrbitalVelX) {
|
|
439
|
+
orbitalVelXIdx = nextIndex++;
|
|
440
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
441
|
+
data,
|
|
442
|
+
writeOffset,
|
|
443
|
+
particleSystemId,
|
|
444
|
+
velocityOverLifetime.orbital.x
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
if (hasOrbitalVelY) {
|
|
448
|
+
orbitalVelYIdx = nextIndex++;
|
|
449
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
450
|
+
data,
|
|
451
|
+
writeOffset,
|
|
452
|
+
particleSystemId,
|
|
453
|
+
velocityOverLifetime.orbital.y
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
if (hasOrbitalVelZ) {
|
|
457
|
+
orbitalVelZIdx = nextIndex++;
|
|
458
|
+
writeOffset = bakeVelocityAxisIntoBuffer(
|
|
459
|
+
data,
|
|
460
|
+
writeOffset,
|
|
461
|
+
particleSystemId,
|
|
462
|
+
velocityOverLifetime.orbital.z
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
data,
|
|
467
|
+
curveCount,
|
|
468
|
+
sizeOverLifetime: sizeOverLifetimeIdx,
|
|
469
|
+
opacityOverLifetime: opacityOverLifetimeIdx,
|
|
470
|
+
colorR: colorRIdx,
|
|
471
|
+
colorG: colorGIdx,
|
|
472
|
+
colorB: colorBIdx,
|
|
473
|
+
linearVelX: linearVelXIdx,
|
|
474
|
+
linearVelY: linearVelYIdx,
|
|
475
|
+
linearVelZ: linearVelZIdx,
|
|
476
|
+
orbitalVelX: orbitalVelXIdx,
|
|
477
|
+
orbitalVelY: orbitalVelYIdx,
|
|
478
|
+
orbitalVelZ: orbitalVelZIdx
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
var permute = Fn(({ x }) => {
|
|
482
|
+
return mod(x.mul(34).add(10).mul(x), float(289));
|
|
483
|
+
});
|
|
484
|
+
var taylorInvSqrt = Fn(({ r }) => {
|
|
485
|
+
return float(1.79284291400159).sub(float(0.85373472095314).mul(r));
|
|
486
|
+
});
|
|
487
|
+
var snoise3D = Fn(
|
|
488
|
+
({ v }) => {
|
|
489
|
+
const ONE_THIRD = float(1 / 3);
|
|
490
|
+
const ONE_SIXTH = float(1 / 6);
|
|
491
|
+
const i = floor(
|
|
492
|
+
v.add(dot(v, vec3(ONE_THIRD, ONE_THIRD, ONE_THIRD)))
|
|
493
|
+
).toVar();
|
|
494
|
+
const x0 = v.sub(i).add(dot(i, vec3(ONE_SIXTH, ONE_SIXTH, ONE_SIXTH))).toVar();
|
|
495
|
+
const g = step(x0.yzx, x0.xyz).toVar();
|
|
496
|
+
const l = float(1).sub(g).toVar();
|
|
497
|
+
const i1 = min(g.xyz, l.zxy).toVar();
|
|
498
|
+
const i2 = max(g.xyz, l.zxy).toVar();
|
|
499
|
+
const x1 = x0.sub(i1).add(ONE_SIXTH).toVar();
|
|
500
|
+
const x2 = x0.sub(i2).add(ONE_SIXTH.mul(2)).toVar();
|
|
501
|
+
const x3 = x0.sub(float(1)).add(ONE_SIXTH.mul(3)).toVar();
|
|
502
|
+
const iw = mod(i, float(289)).toVar();
|
|
503
|
+
const p0_yz = permute({
|
|
504
|
+
x: permute({
|
|
505
|
+
x: vec4(
|
|
506
|
+
vec2(iw.z, iw.z.add(i1.z)),
|
|
507
|
+
vec2(iw.z.add(i2.z), iw.z.add(1))
|
|
508
|
+
)
|
|
509
|
+
}).add(
|
|
510
|
+
vec4(vec2(iw.y, iw.y.add(i1.y)), vec2(iw.y.add(i2.y), iw.y.add(1)))
|
|
511
|
+
)
|
|
512
|
+
});
|
|
513
|
+
const p = permute({
|
|
514
|
+
x: p0_yz.add(
|
|
515
|
+
vec4(vec2(iw.x, iw.x.add(i1.x)), vec2(iw.x.add(i2.x), iw.x.add(1)))
|
|
516
|
+
)
|
|
517
|
+
});
|
|
518
|
+
const n_ = float(0.142857142857142);
|
|
519
|
+
const j = p.sub(float(49).mul(floor(p.mul(n_).mul(n_)))).toVar();
|
|
520
|
+
const x_ = floor(j.mul(n_)).toVar();
|
|
521
|
+
const y_ = floor(j.sub(float(7).mul(x_))).toVar();
|
|
522
|
+
const NS_X = float(0.285714285714286);
|
|
523
|
+
const NS_Y = float(-0.928571428571429);
|
|
524
|
+
const gx = x_.mul(NS_X).add(NS_Y);
|
|
525
|
+
const gy = y_.mul(NS_X).add(NS_Y);
|
|
526
|
+
const gz = float(1).sub(abs(gx)).sub(abs(gy)).toVar();
|
|
527
|
+
const gz_neg = step(gz, vec4(0));
|
|
528
|
+
const ox = gz_neg.mul(floor(gx).add(0.5));
|
|
529
|
+
const oy = gz_neg.mul(floor(gy).add(0.5));
|
|
530
|
+
const gx_final = gx.sub(ox);
|
|
531
|
+
const gy_final = gy.sub(oy);
|
|
532
|
+
const g0 = vec3(gx_final.x, gy_final.x, gz.x).toVar();
|
|
533
|
+
const g1 = vec3(gx_final.y, gy_final.y, gz.y).toVar();
|
|
534
|
+
const g2 = vec3(gx_final.z, gy_final.z, gz.z).toVar();
|
|
535
|
+
const g3 = vec3(gx_final.w, gy_final.w, gz.w).toVar();
|
|
536
|
+
const norm = taylorInvSqrt({
|
|
537
|
+
r: vec4(vec2(dot(g0, g0), dot(g1, g1)), vec2(dot(g2, g2), dot(g3, g3)))
|
|
538
|
+
});
|
|
539
|
+
g0.assign(g0.mul(norm.x));
|
|
540
|
+
g1.assign(g1.mul(norm.y));
|
|
541
|
+
g2.assign(g2.mul(norm.z));
|
|
542
|
+
g3.assign(g3.mul(norm.w));
|
|
543
|
+
const m = max(
|
|
544
|
+
vec4(
|
|
545
|
+
vec2(float(0.5).sub(dot(x0, x0)), float(0.5).sub(dot(x1, x1))),
|
|
546
|
+
vec2(float(0.5).sub(dot(x2, x2)), float(0.5).sub(dot(x3, x3)))
|
|
547
|
+
),
|
|
548
|
+
float(0)
|
|
549
|
+
).toVar();
|
|
550
|
+
const m2 = m.mul(m).toVar();
|
|
551
|
+
const m4 = m2.mul(m2).toVar();
|
|
552
|
+
const gdot = vec4(
|
|
553
|
+
vec2(dot(g0, x0), dot(g1, x1)),
|
|
554
|
+
vec2(dot(g2, x2), dot(g3, x3))
|
|
555
|
+
);
|
|
556
|
+
return float(42).mul(dot(m4, gdot));
|
|
557
|
+
}
|
|
558
|
+
);
|
|
559
|
+
Fn(
|
|
560
|
+
({ t }) => {
|
|
561
|
+
const noiseX = snoise3D({ v: vec3(t, float(0), float(0)) });
|
|
562
|
+
const noiseY = snoise3D({ v: vec3(t, t, float(0)) });
|
|
563
|
+
const noiseZ = snoise3D({ v: vec3(t, t, t) });
|
|
564
|
+
return vec3(noiseX, noiseY, noiseZ);
|
|
565
|
+
}
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
// src/js/effects/three-particles/color-utils.ts
|
|
569
|
+
var sRGBToLinear = (c) => c < 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
570
|
+
|
|
571
|
+
// src/js/effects/three-particles/webgpu/compute-modifiers.ts
|
|
572
|
+
var SUB_EMITTER_EVENT_STRIDE = 6;
|
|
573
|
+
var subEmitterWindowSize = (capacity) => SUB_EMITTER_EVENT_STRIDE * Math.max(1, capacity);
|
|
574
|
+
var asU32 = (n) => n.nodeType === "uint" ? n : n.toUint();
|
|
575
|
+
var pcgRawU32 = (seedU) => {
|
|
576
|
+
const stateU = asU32(seedU).mul(uint(747796405)).add(uint(2891336453));
|
|
577
|
+
const wordU = stateU.shiftRight(stateU.shiftRight(uint(28)).add(uint(4))).bitXor(stateU).mul(uint(277803737));
|
|
578
|
+
return wordU.shiftRight(uint(22)).bitXor(wordU);
|
|
579
|
+
};
|
|
580
|
+
var pcg01 = (seedU) => pcgRawU32(seedU).toFloat().mul(float(1 / 4294967296));
|
|
581
|
+
var mixBirthSeed = (birthNoU, systemSeedU, channelU) => asU32(birthNoU).mul(uint(2654435761)).bitXor(asU32(systemSeedU)).bitXor(asU32(channelU));
|
|
582
|
+
var randomChannel = (birthNoU, systemSeedU, channelU) => pcg01(mixBirthSeed(birthNoU, systemSeedU, channelU));
|
|
583
|
+
var stableSeedU32 = (birthNoU, systemSeedU) => pcgRawU32(mixBirthSeed(birthNoU, systemSeedU, CH.STABLE_SEED)).bitAnd(
|
|
584
|
+
uint(16777215)
|
|
585
|
+
);
|
|
586
|
+
var stableSeedFromExt = (extW) => extW.toUint();
|
|
587
|
+
var nextSystemSeed = () => Math.floor(Math.random() * 4294967296) >>> 0;
|
|
588
|
+
var CH = {
|
|
589
|
+
SHAPE_A: uint(1),
|
|
590
|
+
SHAPE_B: uint(2),
|
|
591
|
+
SHAPE_C: uint(3),
|
|
592
|
+
START_FRAME: uint(4),
|
|
593
|
+
SPEED: uint(5),
|
|
594
|
+
SIZE: uint(6),
|
|
595
|
+
ROTATION: uint(7),
|
|
596
|
+
OPACITY: uint(8),
|
|
597
|
+
LIFETIME: uint(9),
|
|
598
|
+
COLOR: uint(10),
|
|
599
|
+
ROTOL: uint(11),
|
|
600
|
+
STABLE_SEED: uint(12),
|
|
601
|
+
LIN_X: uint(13),
|
|
602
|
+
LIN_Y: uint(14),
|
|
603
|
+
LIN_Z: uint(15),
|
|
604
|
+
ORB_X: uint(16),
|
|
605
|
+
ORB_Y: uint(17),
|
|
606
|
+
ORB_Z: uint(18),
|
|
607
|
+
NOISE_PHASE: uint(19)
|
|
608
|
+
};
|
|
609
|
+
var createSubEmitterFifoAttribute = (capacity) => ({
|
|
610
|
+
counter: new StorageBufferAttribute(new Uint32Array(2), 1),
|
|
611
|
+
payload: new StorageBufferAttribute(
|
|
612
|
+
new Float32Array(
|
|
613
|
+
2 * SUB_EMITTER_EVENT_STRIDE * Math.max(1, capacity)
|
|
614
|
+
),
|
|
615
|
+
1
|
|
616
|
+
),
|
|
617
|
+
trigger: 1,
|
|
618
|
+
capacity: Math.max(1, capacity),
|
|
619
|
+
windowSize: subEmitterWindowSize(capacity)
|
|
620
|
+
});
|
|
621
|
+
function createModifierStorageBuffers(maxParticles, instanced, curveData, hasForceFields = false, hasCollisionPlanes = false, trailLength = 0) {
|
|
622
|
+
const Cls = instanced ? StorageInstancedBufferAttribute : StorageBufferAttribute;
|
|
623
|
+
const curveLen = Math.max(curveData.length, 1);
|
|
624
|
+
const ffSize = hasForceFields ? FORCE_FIELD_DATA_SIZE : 0;
|
|
625
|
+
const cpSize = hasCollisionPlanes ? COLLISION_PLANE_DATA_SIZE : 0;
|
|
626
|
+
const packedData = new Float32Array(curveLen + ffSize + cpSize);
|
|
627
|
+
packedData.set(curveData, 0);
|
|
628
|
+
const allocatorCount = maxParticles + 1;
|
|
629
|
+
const allocatorData = new Uint32Array(allocatorCount);
|
|
630
|
+
allocatorData[0] = 0;
|
|
631
|
+
for (let i = 0; i < maxParticles; i++) allocatorData[i + 1] = i;
|
|
632
|
+
const trailMeta = trailLength > 0 ? new StorageBufferAttribute(
|
|
633
|
+
new Uint32Array(Math.max(1, maxParticles) * 2),
|
|
634
|
+
1
|
|
635
|
+
) : null;
|
|
636
|
+
return {
|
|
637
|
+
buffers: {
|
|
638
|
+
position: new Cls(new Float32Array(maxParticles * 4), 4),
|
|
639
|
+
velocity: new StorageBufferAttribute(new Float32Array(maxParticles * 4), 4),
|
|
640
|
+
color: new Cls(new Float32Array(maxParticles * 4), 4),
|
|
641
|
+
particleState: new Cls(new Float32Array(maxParticles * 4), 4),
|
|
642
|
+
startValues: new Cls(new Float32Array(maxParticles * 4), 4),
|
|
643
|
+
startColorsExt: new StorageBufferAttribute(new Float32Array(maxParticles * 4), 4),
|
|
644
|
+
orbitalIsActive: new StorageBufferAttribute(new Float32Array(maxParticles * 4), 4),
|
|
645
|
+
allocator: new StorageBufferAttribute(allocatorData, 1),
|
|
646
|
+
trailMeta,
|
|
647
|
+
packedData
|
|
648
|
+
},
|
|
649
|
+
allocatorCount
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
function createCurveLookup(sCurveData) {
|
|
653
|
+
return Fn(({ curveIndex, t }) => {
|
|
654
|
+
const clamped = min(t, float(1));
|
|
655
|
+
const pos = clamped.mul(CURVE_RESOLUTION - 1);
|
|
656
|
+
const idx0 = floor(pos);
|
|
657
|
+
const f = fract(pos);
|
|
658
|
+
const base = curveIndex.mul(CURVE_RESOLUTION);
|
|
659
|
+
const v0 = sCurveData.element(base.add(idx0));
|
|
660
|
+
const v1 = sCurveData.element(base.add(min(idx0.add(1), float(CURVE_RESOLUTION - 1))));
|
|
661
|
+
return mix(v0, v1, f);
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
function shapeEmitNodes(u, rA, rB, rC, rSpeed) {
|
|
665
|
+
const DEG = float(0.01745329);
|
|
666
|
+
const uRadius = u.radius;
|
|
667
|
+
const uRadiusThickness = u.thickness;
|
|
668
|
+
const thetaS = rA.mul(u.arcDeg).mul(DEG);
|
|
669
|
+
const cosPhi = rB.mul(float(2)).sub(float(1));
|
|
670
|
+
const sinPhi = sqrt(float(1).sub(cosPhi.mul(cosPhi)));
|
|
671
|
+
const dirSx = sinPhi.mul(cos(thetaS));
|
|
672
|
+
const dirSy = sinPhi.mul(sin(thetaS));
|
|
673
|
+
const dirSz = cosPhi;
|
|
674
|
+
const distS = uRadius.mul(float(1).sub(uRadiusThickness)).add(uRadius.mul(uRadiusThickness).mul(rC));
|
|
675
|
+
const pSx = dirSx.mul(distS);
|
|
676
|
+
const pSy = dirSy.mul(distS);
|
|
677
|
+
const pSz = dirSz.mul(distS);
|
|
678
|
+
const spS = mix(u.speedMin, u.speedMax, rSpeed);
|
|
679
|
+
const vSx = dirSx.mul(spS);
|
|
680
|
+
const vSy = dirSy.mul(spS);
|
|
681
|
+
const vSz = dirSz.mul(spS);
|
|
682
|
+
const thetaB = rA.mul(u.arcDeg).mul(DEG);
|
|
683
|
+
const dirBx = cos(thetaB);
|
|
684
|
+
const dirBy = sin(thetaB);
|
|
685
|
+
const distB = uRadius.mul(float(1).sub(uRadiusThickness)).add(uRadius.mul(uRadiusThickness).mul(rB));
|
|
686
|
+
const pBx = dirBx.mul(distB);
|
|
687
|
+
const pBy = dirBy.mul(distB);
|
|
688
|
+
const pBz = float(0);
|
|
689
|
+
const nAngle = distB.div(uRadius.max(float(1e-6))).mul(u.coneAngleDeg.mul(DEG));
|
|
690
|
+
const spB = mix(u.speedMin, u.speedMax, rSpeed);
|
|
691
|
+
const sinNA = sin(nAngle);
|
|
692
|
+
const vCx = dirBx.mul(sinNA).mul(spB);
|
|
693
|
+
const vCy = dirBy.mul(sinNA).mul(spB);
|
|
694
|
+
const vCz = cos(nAngle).mul(spB);
|
|
695
|
+
const vIx = dirBx.mul(spB);
|
|
696
|
+
const vIy = dirBy.mul(spB);
|
|
697
|
+
const vIz = float(0);
|
|
698
|
+
const rxOff = rA.mul(u.rectSX).sub(u.rectSX.mul(float(0.5)));
|
|
699
|
+
const ryOff = rB.mul(u.rectSY).sub(u.rectSY.mul(float(0.5)));
|
|
700
|
+
const rotXr = u.rectRX.mul(DEG);
|
|
701
|
+
const rotYr = u.rectRY.mul(DEG);
|
|
702
|
+
const pRx = rxOff.mul(cos(rotYr));
|
|
703
|
+
const pRy = ryOff.mul(cos(rotXr));
|
|
704
|
+
const pRz = rxOff.mul(sin(rotYr)).sub(ryOff.mul(sin(rotXr)));
|
|
705
|
+
const halfX = u.boxSX.mul(float(0.5));
|
|
706
|
+
const halfY = u.boxSY.mul(float(0.5));
|
|
707
|
+
const halfZ = u.boxSZ.mul(float(0.5));
|
|
708
|
+
const pVx = rA.mul(u.boxSX).sub(halfX);
|
|
709
|
+
const pVy = rB.mul(u.boxSY).sub(halfY);
|
|
710
|
+
const pVz = rC.mul(u.boxSZ).sub(halfZ);
|
|
711
|
+
const side = floor(rA.mul(float(6))).min(float(5));
|
|
712
|
+
const pa = side.sub(floor(side.div(float(3))).mul(float(3)));
|
|
713
|
+
const a0 = side.greaterThan(float(2)).select(float(1), float(0));
|
|
714
|
+
const isPa0 = pa.equal(float(0));
|
|
715
|
+
const isPa1 = pa.equal(float(1));
|
|
716
|
+
const shX = isPa0.select(a0, isPa1.select(rC, rB));
|
|
717
|
+
const shY = isPa0.select(rB, isPa1.select(a0, rC));
|
|
718
|
+
const shZ = isPa0.select(rC, isPa1.select(rB, a0));
|
|
719
|
+
const pShx = shX.mul(u.boxSX).sub(halfX);
|
|
720
|
+
const pShy = shY.mul(u.boxSY).sub(halfY);
|
|
721
|
+
const pShz = shZ.mul(u.boxSZ).sub(halfZ);
|
|
722
|
+
const edge = floor(rB.mul(float(4))).min(float(3));
|
|
723
|
+
const lowEdge = edge.lessThan(float(2));
|
|
724
|
+
const e1 = lowEdge.select(rC, edge.sub(float(2)));
|
|
725
|
+
const e2 = lowEdge.select(edge, rC);
|
|
726
|
+
const edX = isPa0.select(a0, isPa1.select(e2, e1));
|
|
727
|
+
const edY = isPa0.select(e1, isPa1.select(a0, e2));
|
|
728
|
+
const edZ = isPa0.select(e2, isPa1.select(e1, a0));
|
|
729
|
+
const pEdx = edX.mul(u.boxSX).sub(halfX);
|
|
730
|
+
const pEdy = edY.mul(u.boxSY).sub(halfY);
|
|
731
|
+
const pEdz = edZ.mul(u.boxSZ).sub(halfZ);
|
|
732
|
+
const boxFrom = u.boxFrom;
|
|
733
|
+
const pBX = boxFrom.equal(float(0)).select(pVx, boxFrom.equal(float(1)).select(pShx, pEdx));
|
|
734
|
+
const pBY = boxFrom.equal(float(0)).select(pVy, boxFrom.equal(float(1)).select(pShy, pEdy));
|
|
735
|
+
const pBZ = boxFrom.equal(float(0)).select(pVz, boxFrom.equal(float(1)).select(pShz, pEdz));
|
|
736
|
+
const vPlaneZ = mix(u.speedMin, u.speedMax, rSpeed);
|
|
737
|
+
const kind = u.kind;
|
|
738
|
+
const isSphereKind = kind.equal(float(0));
|
|
739
|
+
const isConeKind = kind.equal(float(1));
|
|
740
|
+
const isCircleKind = kind.equal(float(2));
|
|
741
|
+
const isRectKind = kind.equal(float(3));
|
|
742
|
+
const planeX = isRectKind.select(pRx, pBX);
|
|
743
|
+
const planeY = isRectKind.select(pRy, pBY);
|
|
744
|
+
const planeZ = isRectKind.select(pRz, pBZ);
|
|
745
|
+
const isDiscKind = isConeKind.or(isCircleKind);
|
|
746
|
+
const discX = isDiscKind.select(pBx, planeX);
|
|
747
|
+
const discY = isDiscKind.select(pBy, planeY);
|
|
748
|
+
const discZ = isDiscKind.select(pBz, planeZ);
|
|
749
|
+
const px = isSphereKind.select(pSx, discX);
|
|
750
|
+
const py = isSphereKind.select(pSy, discY);
|
|
751
|
+
const pz = isSphereKind.select(pSz, discZ);
|
|
752
|
+
const cOrI_X = isConeKind.select(vCx, vIx);
|
|
753
|
+
const cOrI_Y = isConeKind.select(vCy, vIy);
|
|
754
|
+
const cOrI_Z = isConeKind.select(vCz, vIz);
|
|
755
|
+
const isDisc = isConeKind.or(isCircleKind);
|
|
756
|
+
const nonSphereVX = isDisc.select(cOrI_X, float(0));
|
|
757
|
+
const nonSphereVY = isDisc.select(cOrI_Y, float(0));
|
|
758
|
+
const nonSphereVZ = isDisc.select(cOrI_Z, vPlaneZ);
|
|
759
|
+
const vx = isSphereKind.select(vSx, nonSphereVX);
|
|
760
|
+
const vy = isSphereKind.select(vSy, nonSphereVY);
|
|
761
|
+
const vz = isSphereKind.select(vSz, nonSphereVZ);
|
|
762
|
+
return { px, py, pz, vx, vy, vz };
|
|
763
|
+
}
|
|
764
|
+
function quatRotateNodes(x, y, z, q) {
|
|
765
|
+
const qx = q.x;
|
|
766
|
+
const qy = q.y;
|
|
767
|
+
const qz = q.z;
|
|
768
|
+
const qw = q.w;
|
|
769
|
+
const projD = qx.mul(x).add(qy.mul(y)).add(qz.mul(z)).mul(float(2));
|
|
770
|
+
const scaleV = qw.mul(qw).mul(float(2)).sub(float(1));
|
|
771
|
+
const twoW = qw.mul(float(2));
|
|
772
|
+
return [
|
|
773
|
+
x.mul(scaleV).add(qx.mul(projD)).add(qy.mul(z).sub(qz.mul(y)).mul(twoW)),
|
|
774
|
+
y.mul(scaleV).add(qy.mul(projD)).add(qz.mul(x).sub(qx.mul(z)).mul(twoW)),
|
|
775
|
+
z.mul(scaleV).add(qz.mul(projD)).add(qx.mul(y).sub(qy.mul(x)).mul(twoW))
|
|
776
|
+
];
|
|
777
|
+
}
|
|
778
|
+
function createModifierComputeUpdate(buffers, maxParticles, curveMap, flags, shapeParams, forceFieldCount = 0, collisionPlaneCount = 0, subFifos = [], trailDesc, velocityValues) {
|
|
779
|
+
const uDelta = uniform(float(0));
|
|
780
|
+
const uDeltaMs = uniform(float(0));
|
|
781
|
+
const uNowMs = uniform(float(0));
|
|
782
|
+
const uGravityVelocity = uniform(new Vector3(0, 0, 0));
|
|
783
|
+
const uSystemSeed = uniform(nextSystemSeed(), "uint");
|
|
784
|
+
const uSeed = uSystemSeed;
|
|
785
|
+
const uEmitCount = uniform(0, "uint");
|
|
786
|
+
const uNoiseStrength = uniform(float(0));
|
|
787
|
+
const uNoisePower = uniform(float(0));
|
|
788
|
+
const uNoiseFrequency = uniform(float(1));
|
|
789
|
+
const uNoisePosAmount = uniform(float(0));
|
|
790
|
+
const uNoiseRotAmount = uniform(float(0));
|
|
791
|
+
const uNoiseSizeAmount = uniform(float(0));
|
|
792
|
+
const shapeUniforms = {};
|
|
793
|
+
const sh = (name, v) => {
|
|
794
|
+
const u = uniform(float(v));
|
|
795
|
+
shapeUniforms[name] = u;
|
|
796
|
+
return u;
|
|
797
|
+
};
|
|
798
|
+
const uShape = sh("shapeKind", shapeParams.shapeKind);
|
|
799
|
+
const uRadius = sh("radius", shapeParams.radius);
|
|
800
|
+
const uRadiusThickness = sh("radiusThickness", shapeParams.radiusThickness);
|
|
801
|
+
const uArcDeg = sh("arcDeg", shapeParams.arcDeg);
|
|
802
|
+
const uConeAngleDeg = sh("coneAngleDeg", shapeParams.coneAngleDeg);
|
|
803
|
+
const uRectRX = sh("rectangleRotXDeg", shapeParams.rectangleRotXDeg);
|
|
804
|
+
const uRectRY = sh("rectangleRotYDeg", shapeParams.rectangleRotYDeg);
|
|
805
|
+
const uRectSX = sh("rectangleScaleX", shapeParams.rectangleScaleX);
|
|
806
|
+
const uRectSY = sh("rectangleScaleY", shapeParams.rectangleScaleY);
|
|
807
|
+
const uBoxSX = sh("boxScaleX", shapeParams.boxScaleX);
|
|
808
|
+
const uBoxSY = sh("boxScaleY", shapeParams.boxScaleY);
|
|
809
|
+
const uBoxSZ = sh("boxScaleZ", shapeParams.boxScaleZ);
|
|
810
|
+
const uBoxEmitFrom = sh("boxEmitFrom", shapeParams.boxEmitFrom);
|
|
811
|
+
const uEmitterPos = uniform(new Vector4(0, 0, 0, 0));
|
|
812
|
+
const uWrapperQuat = uniform(new Vector4(0, 0, 0, 1));
|
|
813
|
+
const uWorldScale = uniform(new Vector3(1, 1, 1));
|
|
814
|
+
const uSpeedMin = sh("speedMin", shapeParams.speedMin);
|
|
815
|
+
const uSpeedMax = sh("speedMax", shapeParams.speedMax);
|
|
816
|
+
const uSizeMin = sh("sizeMin", shapeParams.sizeMin);
|
|
817
|
+
const uSizeMax = sh("sizeMax", shapeParams.sizeMax);
|
|
818
|
+
const uRotMin = sh("rotMin", shapeParams.rotMin);
|
|
819
|
+
const uRotMax = sh("rotMax", shapeParams.rotMax);
|
|
820
|
+
const uRotOLMin = sh("rotOverLifeMin", shapeParams.rotOverLifeMin);
|
|
821
|
+
const uRotOLMax = sh("rotOverLifeMax", shapeParams.rotOverLifeMax);
|
|
822
|
+
const uOpMin = sh("opacityMin", shapeParams.opacityMin);
|
|
823
|
+
const uOpMax = sh("opacityMax", shapeParams.opacityMax);
|
|
824
|
+
const uLifeMin = sh("lifeMin", shapeParams.lifeMin);
|
|
825
|
+
const uLifeMax = sh("lifeMax", shapeParams.lifeMax);
|
|
826
|
+
const uCRR = sh("colorRMin", sRGBToLinear(shapeParams.colorRMin));
|
|
827
|
+
const uCRX = sh("colorRMax", sRGBToLinear(shapeParams.colorRMax));
|
|
828
|
+
const uCGR = sh("colorGMin", sRGBToLinear(shapeParams.colorGMin));
|
|
829
|
+
const uCGX = sh("colorGMax", sRGBToLinear(shapeParams.colorGMax));
|
|
830
|
+
const uCBR = sh("colorBMin", sRGBToLinear(shapeParams.colorBMin));
|
|
831
|
+
const uCBX = sh("colorBMax", sRGBToLinear(shapeParams.colorBMax));
|
|
832
|
+
const uFrMin = sh("startFrameMin", shapeParams.startFrameMin);
|
|
833
|
+
const uFrMax = sh("startFrameMax", shapeParams.startFrameMax);
|
|
834
|
+
const sPos = storage(buffers.position, "vec4", maxParticles);
|
|
835
|
+
const sVel = storage(buffers.velocity, "vec4", maxParticles);
|
|
836
|
+
const sCol = storage(buffers.color, "vec4", maxParticles);
|
|
837
|
+
const sPS = storage(buffers.particleState, "vec4", maxParticles);
|
|
838
|
+
const sSV = storage(buffers.startValues, "vec4", maxParticles);
|
|
839
|
+
const sEx = storage(buffers.startColorsExt, "vec4", maxParticles);
|
|
840
|
+
const sOIA = storage(buffers.orbitalIsActive, "vec4", maxParticles);
|
|
841
|
+
const allocatorCount = maxParticles + 1;
|
|
842
|
+
float(maxParticles);
|
|
843
|
+
const ringModU = uint(maxParticles);
|
|
844
|
+
const sAllocator = storage(buffers.allocator, "uint", Math.max(1, allocatorCount)).toAtomic();
|
|
845
|
+
const sCD = buffer(buffers.packedData, "float", buffers.packedData.length);
|
|
846
|
+
const lookupCurve = createCurveLookup(sCD);
|
|
847
|
+
const uFifoBase = uniform(float(0), "uint");
|
|
848
|
+
const fifoNodes = subFifos.map((f) => ({
|
|
849
|
+
trigger: f.trigger,
|
|
850
|
+
capacity: Math.max(1, f.capacity),
|
|
851
|
+
windowSize: subEmitterWindowSize(Math.max(1, f.capacity)),
|
|
852
|
+
count: storage(
|
|
853
|
+
f.counter,
|
|
854
|
+
"uint",
|
|
855
|
+
Math.max(1, f.counter.array.length)
|
|
856
|
+
).toAtomic(),
|
|
857
|
+
payload: storage(
|
|
858
|
+
f.payload,
|
|
859
|
+
"float",
|
|
860
|
+
Math.max(1, f.payload.array.length)
|
|
861
|
+
)
|
|
862
|
+
}));
|
|
863
|
+
const birthFifos = fifoNodes.filter((f) => f.trigger === 0);
|
|
864
|
+
const deathFifos = fifoNodes.filter((f) => f.trigger === 1);
|
|
865
|
+
const hasDeathFifo = deathFifos.length > 0;
|
|
866
|
+
const writeFifoEvent = (f, x, y, z, vx, vy, vz) => {
|
|
867
|
+
const winBase = uFifoBase.mul(float(f.windowSize)).toVar();
|
|
868
|
+
const oldCount = float(
|
|
869
|
+
atomicAdd(f.count.element(uFifoBase), uint(1))
|
|
870
|
+
).toVar();
|
|
871
|
+
If(oldCount.lessThan(float(f.capacity)), () => {
|
|
872
|
+
const b = winBase.add(oldCount.mul(float(SUB_EMITTER_EVENT_STRIDE)));
|
|
873
|
+
f.payload.element(b).assign(x);
|
|
874
|
+
f.payload.element(b.add(float(1))).assign(y);
|
|
875
|
+
f.payload.element(b.add(float(2))).assign(z);
|
|
876
|
+
f.payload.element(b.add(float(3))).assign(vx);
|
|
877
|
+
f.payload.element(b.add(float(4))).assign(vy);
|
|
878
|
+
f.payload.element(b.add(float(5))).assign(vz);
|
|
879
|
+
});
|
|
880
|
+
};
|
|
881
|
+
const trailRows = trailDesc ? trailDesc.length + 1 : 0;
|
|
882
|
+
const sTrail = trailDesc ? storage(trailDesc.attribute, "vec4", trailRows * maxParticles) : null;
|
|
883
|
+
const sTrailMeta = trailDesc && buffers.trailMeta ? storage(buffers.trailMeta, "uint", maxParticles * 2).toAtomic() : null;
|
|
884
|
+
const curveLen = Math.max(curveMap.data.length, 1);
|
|
885
|
+
const forceFieldOffset = curveLen;
|
|
886
|
+
const collisionOffset = forceFieldOffset + (flags.forceFields ? FORCE_FIELD_DATA_SIZE : 0);
|
|
887
|
+
const ffNodes = flags.forceFields ? createForceFieldTSL(sCD, forceFieldOffset, forceFieldCount) : null;
|
|
888
|
+
const cpNodes = flags.collisionPlanes ? createCollisionPlaneTSL(sCD, collisionOffset, collisionPlaneCount) : null;
|
|
889
|
+
const parseAxis = (rawAxis, curveIdx) => {
|
|
890
|
+
if (curveIdx >= 0) {
|
|
891
|
+
return { ci: curveIdx, min: 0, max: 0, isRange: false };
|
|
892
|
+
}
|
|
893
|
+
if (rawAxis && typeof rawAxis === "object" && "min" in rawAxis && "max" in rawAxis) {
|
|
894
|
+
const mn = Number(rawAxis.min) || 0;
|
|
895
|
+
const mx = Number(rawAxis.max) || 0;
|
|
896
|
+
return { ci: -1, min: mn, max: mx, isRange: mn !== mx };
|
|
897
|
+
}
|
|
898
|
+
const c = typeof rawAxis === "number" ? rawAxis : 0;
|
|
899
|
+
return { ci: -1, min: c, max: c, isRange: false };
|
|
900
|
+
};
|
|
901
|
+
const vv = velocityValues ?? {
|
|
902
|
+
linear: [void 0, void 0, void 0],
|
|
903
|
+
orbital: [void 0, void 0, void 0]
|
|
904
|
+
};
|
|
905
|
+
const linAxes = [
|
|
906
|
+
parseAxis(vv.linear[0], curveMap.linearVelX),
|
|
907
|
+
parseAxis(vv.linear[1], curveMap.linearVelY),
|
|
908
|
+
parseAxis(vv.linear[2], curveMap.linearVelZ)
|
|
909
|
+
];
|
|
910
|
+
const orbAxes = [
|
|
911
|
+
parseAxis(vv.orbital[0], curveMap.orbitalVelX),
|
|
912
|
+
parseAxis(vv.orbital[1], curveMap.orbitalVelY),
|
|
913
|
+
parseAxis(vv.orbital[2], curveMap.orbitalVelZ)
|
|
914
|
+
];
|
|
915
|
+
const axisUniforms = /* @__PURE__ */ new Map();
|
|
916
|
+
for (const a of [...linAxes, ...orbAxes]) {
|
|
917
|
+
if (a.isRange) {
|
|
918
|
+
axisUniforms.set(a, [uniform(float(a.min)), uniform(float(a.max))]);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
const simAxis = (a, lifePct, particleSeed, salt) => {
|
|
922
|
+
if (a.ci >= 0) {
|
|
923
|
+
return lookupCurve({
|
|
924
|
+
curveIndex: float(a.ci),
|
|
925
|
+
t: lifePct
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
if (a.isRange) {
|
|
929
|
+
const [mn, mx] = axisUniforms.get(a);
|
|
930
|
+
return mix(
|
|
931
|
+
mn,
|
|
932
|
+
mx,
|
|
933
|
+
pcg01(particleSeed.toUint().mul(uint(2654435761)).bitXor(salt))
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
return float(a.min);
|
|
937
|
+
};
|
|
938
|
+
const emitKernel = Fn(() => {
|
|
939
|
+
const i = instanceIndex;
|
|
940
|
+
If(i.lessThan(uEmitCount), () => {
|
|
941
|
+
const birthNo = atomicAdd(sAllocator.element(0), uint(1)).toVar();
|
|
942
|
+
const slotIdx = birthNo.mod(ringModU).toVar();
|
|
943
|
+
const rcA = randomChannel(birthNo, uSystemSeed, CH.SHAPE_A);
|
|
944
|
+
const rcB = randomChannel(birthNo, uSystemSeed, CH.SHAPE_B);
|
|
945
|
+
const rcC = randomChannel(birthNo, uSystemSeed, CH.SHAPE_C);
|
|
946
|
+
const rcSpeed = randomChannel(birthNo, uSystemSeed, CH.SPEED);
|
|
947
|
+
const rcSize = randomChannel(birthNo, uSystemSeed, CH.SIZE);
|
|
948
|
+
const rcRot = randomChannel(birthNo, uSystemSeed, CH.ROTATION);
|
|
949
|
+
const rcOpacity = randomChannel(birthNo, uSystemSeed, CH.OPACITY);
|
|
950
|
+
const rcSheet = randomChannel(birthNo, uSystemSeed, CH.START_FRAME);
|
|
951
|
+
const rcLife = randomChannel(birthNo, uSystemSeed, CH.LIFETIME);
|
|
952
|
+
const rcColor = randomChannel(birthNo, uSystemSeed, CH.COLOR);
|
|
953
|
+
const rcRotOl = randomChannel(birthNo, uSystemSeed, CH.ROTOL);
|
|
954
|
+
const shE = shapeEmitNodes(
|
|
955
|
+
{
|
|
956
|
+
kind: uShape,
|
|
957
|
+
radius: uRadius,
|
|
958
|
+
thickness: uRadiusThickness,
|
|
959
|
+
arcDeg: uArcDeg,
|
|
960
|
+
coneAngleDeg: uConeAngleDeg,
|
|
961
|
+
rectRX: uRectRX,
|
|
962
|
+
rectRY: uRectRY,
|
|
963
|
+
rectSX: uRectSX,
|
|
964
|
+
rectSY: uRectSY,
|
|
965
|
+
boxSX: uBoxSX,
|
|
966
|
+
boxSY: uBoxSY,
|
|
967
|
+
boxSZ: uBoxSZ,
|
|
968
|
+
boxFrom: uBoxEmitFrom,
|
|
969
|
+
speedMin: uSpeedMin,
|
|
970
|
+
speedMax: uSpeedMax
|
|
971
|
+
},
|
|
972
|
+
rcA,
|
|
973
|
+
rcB,
|
|
974
|
+
rcC,
|
|
975
|
+
rcSpeed
|
|
976
|
+
);
|
|
977
|
+
const pxL = shE.px;
|
|
978
|
+
const pyL = shE.py;
|
|
979
|
+
const pzL = shE.pz;
|
|
980
|
+
const vxL = shE.vx;
|
|
981
|
+
const vyL = shE.vy;
|
|
982
|
+
const vzL = shE.vz;
|
|
983
|
+
const [rotPX, rotPY, rotPZ] = quatRotateNodes(pxL, pyL, pzL, uWrapperQuat);
|
|
984
|
+
const [rotVX, rotVY, rotVZ] = quatRotateNodes(vxL, vyL, vzL, uWrapperQuat);
|
|
985
|
+
const isWorld = uEmitterPos.w.greaterThan(0.5);
|
|
986
|
+
const sxf = isWorld.select(uWorldScale.x, float(1));
|
|
987
|
+
const syf = isWorld.select(uWorldScale.y, float(1));
|
|
988
|
+
const szf = isWorld.select(uWorldScale.z, float(1));
|
|
989
|
+
const ox = rotPX.mul(sxf).add(uEmitterPos.x);
|
|
990
|
+
const oy = rotPY.mul(syf).add(uEmitterPos.y);
|
|
991
|
+
const oz = rotPZ.mul(szf).add(uEmitterPos.z);
|
|
992
|
+
const opac = mix(uOpMin, uOpMax, rcOpacity);
|
|
993
|
+
const clR = mix(uCRR, uCRX, rcColor);
|
|
994
|
+
const clG = mix(uCGR, uCGX, rcColor);
|
|
995
|
+
const clB = mix(uCBR, uCBX, rcColor);
|
|
996
|
+
const slife = mix(uLifeMin, uLifeMax, rcLife).mul(float(1e3));
|
|
997
|
+
const ssize = mix(uSizeMin, uSizeMax, rcSize);
|
|
998
|
+
const srot = mix(uRotMin, uRotMax, rcRot);
|
|
999
|
+
const startFrame = floor(mix(uFrMin, uFrMax, rcSheet)).toVar();
|
|
1000
|
+
const rotSpeed = mix(uRotOLMin, uRotOLMax, rcRotOl);
|
|
1001
|
+
const stableSeedU = stableSeedU32(birthNo, uSystemSeed);
|
|
1002
|
+
sPos.element(slotIdx).assign(vec4(ox, oy, oz, float(0)));
|
|
1003
|
+
sVel.element(slotIdx).assign(vec4(rotVX, rotVY, rotVZ, float(0)));
|
|
1004
|
+
sCol.element(slotIdx).assign(vec4(clR, clG, clB, opac));
|
|
1005
|
+
sPS.element(slotIdx).assign(vec4(float(0), ssize, srot, startFrame));
|
|
1006
|
+
sSV.element(slotIdx).assign(vec4(slife, ssize, opac, clR));
|
|
1007
|
+
sEx.element(slotIdx).assign(
|
|
1008
|
+
vec4(clG, clB, rotSpeed, stableSeedU.toFloat())
|
|
1009
|
+
);
|
|
1010
|
+
sOIA.element(slotIdx).assign(vec4(rotPX, rotPY, rotPZ, float(1)));
|
|
1011
|
+
});
|
|
1012
|
+
});
|
|
1013
|
+
const emitNode = compute(emitKernel(), maxParticles);
|
|
1014
|
+
const noiseOctavesCount = Math.max(1, Math.round(shapeParams.noiseOctaves || 1));
|
|
1015
|
+
const noiseFbmMax = 2 - Math.pow(2, -noiseOctavesCount);
|
|
1016
|
+
if (!Number.isFinite(noiseFbmMax) || noiseFbmMax <= 0) {
|
|
1017
|
+
throw new Error(
|
|
1018
|
+
`three-particles: invalid FBM normalization ${noiseFbmMax}`
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
const simKernel = Fn(() => {
|
|
1022
|
+
const i = instanceIndex;
|
|
1023
|
+
If(float(i).lessThan(float(maxParticles)), () => {
|
|
1024
|
+
const oiaVec = sOIA.element(i).toVar();
|
|
1025
|
+
If(oiaVec.w.greaterThanEqual(float(0.5)), () => {
|
|
1026
|
+
const pos = sPos.element(i).xyz.toVar();
|
|
1027
|
+
const vel = sVel.element(i).xyz.toVar();
|
|
1028
|
+
const ps = sPS.element(i).toVar();
|
|
1029
|
+
const sv = sSV.element(i);
|
|
1030
|
+
const ex = sEx.element(i);
|
|
1031
|
+
const startLife = sv.x;
|
|
1032
|
+
const life = ps.x;
|
|
1033
|
+
const lifePct = min(life.div(startLife), float(1));
|
|
1034
|
+
vel.assign(vel.sub(vec3(uGravityVelocity).mul(uDelta)));
|
|
1035
|
+
if (ffNodes) ffNodes.apply({ pos, vel, delta: uDelta });
|
|
1036
|
+
pos.assign(pos.add(vel.mul(uDelta)));
|
|
1037
|
+
if (cpNodes) cpNodes.apply({
|
|
1038
|
+
pos,
|
|
1039
|
+
vel,
|
|
1040
|
+
oiaVec,
|
|
1041
|
+
sColorNode: sCol,
|
|
1042
|
+
ps,
|
|
1043
|
+
startLife,
|
|
1044
|
+
particleIdx: i,
|
|
1045
|
+
sOrbitalIsActiveNode: sOIA
|
|
1046
|
+
});
|
|
1047
|
+
if (flags.linearVelocity) {
|
|
1048
|
+
const lvx = simAxis(linAxes[0], lifePct, stableSeedFromExt(ex.w), CH.LIN_X);
|
|
1049
|
+
const lvy = simAxis(linAxes[1], lifePct, stableSeedFromExt(ex.w), CH.LIN_Y);
|
|
1050
|
+
const lvz = simAxis(linAxes[2], lifePct, stableSeedFromExt(ex.w), CH.LIN_Z);
|
|
1051
|
+
pos.assign(pos.add(vec3(lvx, lvy, lvz).mul(uDelta)));
|
|
1052
|
+
}
|
|
1053
|
+
if (flags.orbitalVelocity) {
|
|
1054
|
+
const offset = vec3(oiaVec.x, oiaVec.y, oiaVec.z).toVar();
|
|
1055
|
+
pos.assign(pos.sub(offset));
|
|
1056
|
+
const oX = simAxis(orbAxes[0], lifePct, stableSeedFromExt(ex.w), CH.ORB_X);
|
|
1057
|
+
const oY = simAxis(orbAxes[1], lifePct, stableSeedFromExt(ex.w), CH.ORB_Y);
|
|
1058
|
+
const oZ = simAxis(orbAxes[2], lifePct, stableSeedFromExt(ex.w), CH.ORB_Z);
|
|
1059
|
+
const angX = oX.mul(uDelta);
|
|
1060
|
+
const angY = oZ.mul(uDelta);
|
|
1061
|
+
const angZ = oY.mul(uDelta);
|
|
1062
|
+
const c3 = cos(angZ), s3 = sin(angZ);
|
|
1063
|
+
const zx = offset.x.mul(c3).sub(offset.y.mul(s3));
|
|
1064
|
+
const zy = offset.x.mul(s3).add(offset.y.mul(c3));
|
|
1065
|
+
const zz = offset.z;
|
|
1066
|
+
const c2 = cos(angY), s2 = sin(angY);
|
|
1067
|
+
const yx = zx.mul(c2).add(zz.mul(s2));
|
|
1068
|
+
const yz = zx.mul(s2).negate().add(zz.mul(c2));
|
|
1069
|
+
const yy = zy;
|
|
1070
|
+
const c1 = cos(angX), s1 = sin(angX);
|
|
1071
|
+
const fx = yx;
|
|
1072
|
+
const fy = yy.mul(c1).sub(yz.mul(s1));
|
|
1073
|
+
const fz = yy.mul(s1).add(yz.mul(c1));
|
|
1074
|
+
pos.assign(pos.add(vec3(fx, fy, fz)));
|
|
1075
|
+
oiaVec.assign(vec4(fx, fy, fz, oiaVec.w));
|
|
1076
|
+
}
|
|
1077
|
+
if (flags.sizeOverLifetime) {
|
|
1078
|
+
const s = lookupCurve({ curveIndex: float(curveMap.sizeOverLifetime), t: lifePct });
|
|
1079
|
+
ps.y.assign(s.mul(sv.y));
|
|
1080
|
+
}
|
|
1081
|
+
if (flags.opacityOverLifetime) {
|
|
1082
|
+
const op = lookupCurve({ curveIndex: float(curveMap.opacityOverLifetime), t: lifePct });
|
|
1083
|
+
const col = sCol.element(i).toVar();
|
|
1084
|
+
col.w.assign(op.mul(sv.z));
|
|
1085
|
+
sCol.element(i).assign(col);
|
|
1086
|
+
}
|
|
1087
|
+
if (flags.colorOverLifetime) {
|
|
1088
|
+
const col = sCol.element(i).toVar();
|
|
1089
|
+
const sce = sEx.element(i);
|
|
1090
|
+
if (curveMap.colorR >= 0) {
|
|
1091
|
+
col.x.assign(sv.w.mul(lookupCurve({ curveIndex: float(curveMap.colorR), t: lifePct })));
|
|
1092
|
+
}
|
|
1093
|
+
if (curveMap.colorG >= 0) {
|
|
1094
|
+
col.y.assign(sce.x.mul(lookupCurve({ curveIndex: float(curveMap.colorG), t: lifePct })));
|
|
1095
|
+
}
|
|
1096
|
+
if (curveMap.colorB >= 0) {
|
|
1097
|
+
col.z.assign(sce.y.mul(lookupCurve({ curveIndex: float(curveMap.colorB), t: lifePct })));
|
|
1098
|
+
}
|
|
1099
|
+
sCol.element(i).assign(col);
|
|
1100
|
+
}
|
|
1101
|
+
if (flags.rotationOverLifetime) {
|
|
1102
|
+
ps.z.assign(ps.z.add(ex.z.mul(uDelta).mul(float(0.02))));
|
|
1103
|
+
}
|
|
1104
|
+
if (flags.noise) {
|
|
1105
|
+
const noiseOffset = shapeParams.noiseUseRandomOffset ? pcg01(
|
|
1106
|
+
stableSeedFromExt(ex.w).toUint().mul(uint(2654435761)).bitXor(CH.NOISE_PHASE)
|
|
1107
|
+
).mul(float(100)) : float(0);
|
|
1108
|
+
const np = lifePct.add(noiseOffset).mul(float(10)).mul(uNoiseStrength).mul(uNoiseFrequency);
|
|
1109
|
+
let noiseX = float(0).toVar();
|
|
1110
|
+
let noiseY = float(0).toVar();
|
|
1111
|
+
let noiseZ = float(0).toVar();
|
|
1112
|
+
let amp = 1;
|
|
1113
|
+
let lac = 1;
|
|
1114
|
+
for (let o = 0; o < noiseOctavesCount; o++) {
|
|
1115
|
+
const t = np.mul(float(lac));
|
|
1116
|
+
const sc = float(amp / noiseFbmMax);
|
|
1117
|
+
noiseX.assign(noiseX.add(snoise3D({ v: vec3(t, float(0), float(0)) }).mul(sc)));
|
|
1118
|
+
noiseY.assign(noiseY.add(snoise3D({ v: vec3(t, t, float(0)) }).mul(sc)));
|
|
1119
|
+
noiseZ.assign(noiseZ.add(snoise3D({ v: vec3(t, t, t) }).mul(sc)));
|
|
1120
|
+
amp *= 0.5;
|
|
1121
|
+
lac *= 2;
|
|
1122
|
+
}
|
|
1123
|
+
If(uNoisePosAmount.greaterThan(float(1e-3)), () => {
|
|
1124
|
+
pos.assign(
|
|
1125
|
+
pos.add(vec3(noiseX, noiseY, noiseZ).mul(uNoisePower).mul(uNoisePosAmount))
|
|
1126
|
+
);
|
|
1127
|
+
});
|
|
1128
|
+
If(uNoiseRotAmount.greaterThan(float(1e-3)), () => {
|
|
1129
|
+
ps.z.assign(ps.z.add(noiseX.mul(uNoisePower).mul(uNoiseRotAmount)));
|
|
1130
|
+
});
|
|
1131
|
+
If(uNoiseSizeAmount.greaterThan(float(1e-3)), () => {
|
|
1132
|
+
ps.y.assign(ps.y.add(noiseX.mul(uNoisePower).mul(uNoiseSizeAmount)));
|
|
1133
|
+
});
|
|
1134
|
+
}
|
|
1135
|
+
ps.x.assign(ps.x.add(uDeltaMs));
|
|
1136
|
+
sPos.element(i).assign(vec4(pos, float(0)));
|
|
1137
|
+
sVel.element(i).assign(vec4(vel, float(0)));
|
|
1138
|
+
sPS.element(i).assign(ps);
|
|
1139
|
+
sOIA.element(i).assign(oiaVec);
|
|
1140
|
+
If(ps.x.greaterThan(startLife), () => {
|
|
1141
|
+
const inactive = sOIA.element(i).toVar();
|
|
1142
|
+
sOIA.element(i).assign(
|
|
1143
|
+
vec4(
|
|
1144
|
+
inactive.x,
|
|
1145
|
+
inactive.y,
|
|
1146
|
+
inactive.z,
|
|
1147
|
+
hasDeathFifo ? float(-1) : float(0)
|
|
1148
|
+
)
|
|
1149
|
+
);
|
|
1150
|
+
sCol.element(i).assign(vec4(float(0), float(0), float(0), float(0)));
|
|
1151
|
+
});
|
|
1152
|
+
});
|
|
1153
|
+
});
|
|
1154
|
+
});
|
|
1155
|
+
const simNode = compute(simKernel(), maxParticles);
|
|
1156
|
+
let trailHistoryNode = null;
|
|
1157
|
+
if (sTrail && sTrailMeta && trailDesc) {
|
|
1158
|
+
const trailHistoryKernel = Fn(() => {
|
|
1159
|
+
const i = instanceIndex;
|
|
1160
|
+
If(float(i).lessThan(float(maxParticles)), () => {
|
|
1161
|
+
const oiaVec = sOIA.element(i).toVar();
|
|
1162
|
+
const activeNow = oiaVec.w.greaterThanEqual(float(0.5));
|
|
1163
|
+
const pendingDeath = oiaVec.w.lessThan(float(-0.5));
|
|
1164
|
+
If(activeNow.or(pendingDeath), () => {
|
|
1165
|
+
const pos = sPos.element(i).toVar();
|
|
1166
|
+
const L = float(trailDesc.length);
|
|
1167
|
+
float(trailRows);
|
|
1168
|
+
const curIdx = i.mul(uint(2));
|
|
1169
|
+
const cursor = float(atomicLoad(sTrailMeta.element(curIdx))).toVar();
|
|
1170
|
+
const count = float(
|
|
1171
|
+
atomicLoad(sTrailMeta.element(curIdx.add(uint(1))))
|
|
1172
|
+
).toVar();
|
|
1173
|
+
const baseI = i.mul(float(trailRows));
|
|
1174
|
+
const prev = sTrail.element(baseI.add(cursor)).toVar();
|
|
1175
|
+
const ddx = pos.x.sub(prev.x);
|
|
1176
|
+
const ddy = pos.y.sub(prev.y);
|
|
1177
|
+
const ddz = pos.z.sub(prev.z);
|
|
1178
|
+
const dist = sqrt(
|
|
1179
|
+
ddx.mul(ddx).add(ddy.mul(ddy)).add(ddz.mul(ddz))
|
|
1180
|
+
);
|
|
1181
|
+
const firstSample = count.lessThan(float(0.5));
|
|
1182
|
+
const farEnough = dist.greaterThanEqual(
|
|
1183
|
+
float(Math.max(1e-6, trailDesc.minVertexDistance))
|
|
1184
|
+
);
|
|
1185
|
+
If(firstSample.or(farEnough), () => {
|
|
1186
|
+
const newCursor = cursor.greaterThanEqual(L.sub(float(1))).select(float(0), cursor.add(float(1)));
|
|
1187
|
+
sTrail.element(baseI.add(newCursor)).assign(
|
|
1188
|
+
vec4(pos.x, pos.y, pos.z, uNowMs)
|
|
1189
|
+
);
|
|
1190
|
+
atomicStore(
|
|
1191
|
+
sTrailMeta.element(curIdx),
|
|
1192
|
+
newCursor.toUint()
|
|
1193
|
+
);
|
|
1194
|
+
atomicStore(
|
|
1195
|
+
sTrailMeta.element(curIdx.add(uint(1))),
|
|
1196
|
+
min(count.add(float(1)), float(trailDesc.length)).toUint()
|
|
1197
|
+
);
|
|
1198
|
+
});
|
|
1199
|
+
});
|
|
1200
|
+
});
|
|
1201
|
+
});
|
|
1202
|
+
trailHistoryNode = compute(trailHistoryKernel(), maxParticles);
|
|
1203
|
+
}
|
|
1204
|
+
let subBirthEventsNode = null;
|
|
1205
|
+
if (birthFifos.length > 0) {
|
|
1206
|
+
const subBirthKernel = Fn(() => {
|
|
1207
|
+
const i = instanceIndex;
|
|
1208
|
+
If(i.lessThan(uEmitCount), () => {
|
|
1209
|
+
const counterAfter = atomicLoad(sAllocator.element(0)).toVar();
|
|
1210
|
+
const birthNo = counterAfter.sub(uEmitCount).add(i).toVar();
|
|
1211
|
+
const slot = birthNo.mod(ringModU).toVar();
|
|
1212
|
+
const p = sPos.element(slot).toVar();
|
|
1213
|
+
const v = sVel.element(slot).toVar();
|
|
1214
|
+
for (const f of birthFifos) {
|
|
1215
|
+
writeFifoEvent(f, p.x, p.y, p.z, v.x, v.y, v.z);
|
|
1216
|
+
}
|
|
1217
|
+
});
|
|
1218
|
+
});
|
|
1219
|
+
subBirthEventsNode = compute(subBirthKernel(), maxParticles);
|
|
1220
|
+
}
|
|
1221
|
+
let subDeathEventsNode = null;
|
|
1222
|
+
if (deathFifos.length > 0) {
|
|
1223
|
+
const subDeathKernel = Fn(() => {
|
|
1224
|
+
const i = instanceIndex;
|
|
1225
|
+
If(float(i).lessThan(float(maxParticles)), () => {
|
|
1226
|
+
const oiaVec = sOIA.element(i).toVar();
|
|
1227
|
+
If(oiaVec.w.equal(float(-1)), () => {
|
|
1228
|
+
const p = sPos.element(i).toVar();
|
|
1229
|
+
const v = sVel.element(i).toVar();
|
|
1230
|
+
for (const f of deathFifos) {
|
|
1231
|
+
writeFifoEvent(f, p.x, p.y, p.z, v.x, v.y, v.z);
|
|
1232
|
+
}
|
|
1233
|
+
sOIA.element(i).assign(
|
|
1234
|
+
vec4(oiaVec.x, oiaVec.y, oiaVec.z, float(0))
|
|
1235
|
+
);
|
|
1236
|
+
});
|
|
1237
|
+
});
|
|
1238
|
+
});
|
|
1239
|
+
subDeathEventsNode = compute(subDeathKernel(), maxParticles);
|
|
1240
|
+
}
|
|
1241
|
+
const layout = (name, storageNodes, uniformNodes) => ({
|
|
1242
|
+
name,
|
|
1243
|
+
storageBindings: new Set(storageNodes.filter((n) => n != null)).size,
|
|
1244
|
+
uniformBindings: new Set(uniformNodes.filter((n) => n != null)).size
|
|
1245
|
+
});
|
|
1246
|
+
const basePool = [sPos, sVel, sCol, sPS, sSV, sEx, sOIA, sAllocator];
|
|
1247
|
+
const emitUniforms = [
|
|
1248
|
+
uEmitCount,
|
|
1249
|
+
uSeed,
|
|
1250
|
+
uShape,
|
|
1251
|
+
uRadius,
|
|
1252
|
+
uRadiusThickness,
|
|
1253
|
+
uArcDeg,
|
|
1254
|
+
uConeAngleDeg,
|
|
1255
|
+
uRectRX,
|
|
1256
|
+
uRectRY,
|
|
1257
|
+
uRectSX,
|
|
1258
|
+
uRectSY,
|
|
1259
|
+
uBoxSX,
|
|
1260
|
+
uBoxSY,
|
|
1261
|
+
uBoxSZ,
|
|
1262
|
+
uBoxEmitFrom,
|
|
1263
|
+
uSpeedMin,
|
|
1264
|
+
uSpeedMax,
|
|
1265
|
+
uSizeMin,
|
|
1266
|
+
uSizeMax,
|
|
1267
|
+
uRotMin,
|
|
1268
|
+
uRotMax,
|
|
1269
|
+
uOpMin,
|
|
1270
|
+
uOpMax,
|
|
1271
|
+
uLifeMin,
|
|
1272
|
+
uLifeMax,
|
|
1273
|
+
uCRR,
|
|
1274
|
+
uCRX,
|
|
1275
|
+
uCGR,
|
|
1276
|
+
uCGX,
|
|
1277
|
+
uCBR,
|
|
1278
|
+
uCBX,
|
|
1279
|
+
uFrMin,
|
|
1280
|
+
uFrMax,
|
|
1281
|
+
uRotOLMin,
|
|
1282
|
+
uRotOLMax,
|
|
1283
|
+
uWrapperQuat,
|
|
1284
|
+
uEmitterPos,
|
|
1285
|
+
uWorldScale
|
|
1286
|
+
];
|
|
1287
|
+
const simUniforms = [
|
|
1288
|
+
uDelta,
|
|
1289
|
+
uDeltaMs,
|
|
1290
|
+
uGravityVelocity,
|
|
1291
|
+
uNoiseStrength,
|
|
1292
|
+
uNoisePower,
|
|
1293
|
+
uNoiseFrequency,
|
|
1294
|
+
uNoisePosAmount,
|
|
1295
|
+
uNoiseRotAmount,
|
|
1296
|
+
uNoiseSizeAmount,
|
|
1297
|
+
sCD,
|
|
1298
|
+
...Array.from(axisUniforms.values()).flat()
|
|
1299
|
+
];
|
|
1300
|
+
const passLayouts = [
|
|
1301
|
+
layout("emit", basePool, emitUniforms),
|
|
1302
|
+
layout("simulate", basePool, simUniforms)
|
|
1303
|
+
];
|
|
1304
|
+
if (trailHistoryNode) {
|
|
1305
|
+
passLayouts.push(
|
|
1306
|
+
layout("trail-history", [sPos, sOIA, sTrail, sTrailMeta], [uNowMs])
|
|
1307
|
+
);
|
|
1308
|
+
}
|
|
1309
|
+
if (subBirthEventsNode) {
|
|
1310
|
+
passLayouts.push(
|
|
1311
|
+
layout(
|
|
1312
|
+
"sub-birth-events",
|
|
1313
|
+
[
|
|
1314
|
+
sAllocator,
|
|
1315
|
+
sPos,
|
|
1316
|
+
sVel,
|
|
1317
|
+
...birthFifos.flatMap((f) => [f.count, f.payload])
|
|
1318
|
+
],
|
|
1319
|
+
[uEmitCount, uFifoBase]
|
|
1320
|
+
)
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
if (subDeathEventsNode) {
|
|
1324
|
+
passLayouts.push(
|
|
1325
|
+
layout(
|
|
1326
|
+
"sub-death-events",
|
|
1327
|
+
[
|
|
1328
|
+
sPos,
|
|
1329
|
+
sVel,
|
|
1330
|
+
sOIA,
|
|
1331
|
+
...deathFifos.flatMap((f) => [f.count, f.payload])
|
|
1332
|
+
],
|
|
1333
|
+
[uFifoBase]
|
|
1334
|
+
)
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
return {
|
|
1338
|
+
emitNode,
|
|
1339
|
+
simNode,
|
|
1340
|
+
trailHistoryNode,
|
|
1341
|
+
subBirthEventsNode,
|
|
1342
|
+
subDeathEventsNode,
|
|
1343
|
+
computeNodes: [
|
|
1344
|
+
emitNode,
|
|
1345
|
+
...subBirthEventsNode ? [subBirthEventsNode] : [],
|
|
1346
|
+
simNode,
|
|
1347
|
+
...subDeathEventsNode ? [subDeathEventsNode] : [],
|
|
1348
|
+
...trailHistoryNode ? [trailHistoryNode] : []
|
|
1349
|
+
],
|
|
1350
|
+
passLayouts,
|
|
1351
|
+
uniforms: {
|
|
1352
|
+
delta: uDelta,
|
|
1353
|
+
deltaMs: uDeltaMs,
|
|
1354
|
+
nowMs: uNowMs,
|
|
1355
|
+
gravityVelocity: uGravityVelocity,
|
|
1356
|
+
noiseStrength: uNoiseStrength,
|
|
1357
|
+
noisePower: uNoisePower,
|
|
1358
|
+
noiseFrequency: uNoiseFrequency,
|
|
1359
|
+
noisePositionAmount: uNoisePosAmount,
|
|
1360
|
+
noiseRotationAmount: uNoiseRotAmount,
|
|
1361
|
+
noiseSizeAmount: uNoiseSizeAmount,
|
|
1362
|
+
emitCount: uEmitCount,
|
|
1363
|
+
seed: uSeed,
|
|
1364
|
+
/** Ping-pong FIFO window index for this frame (0 or 1). */
|
|
1365
|
+
fifoBase: uFifoBase
|
|
1366
|
+
},
|
|
1367
|
+
shapeUniforms,
|
|
1368
|
+
buffers,
|
|
1369
|
+
allocatorCount,
|
|
1370
|
+
packedDataNode: sCD,
|
|
1371
|
+
passNames: [
|
|
1372
|
+
"emit",
|
|
1373
|
+
...subBirthEventsNode ? ["sub-birth-events"] : [],
|
|
1374
|
+
"simulate",
|
|
1375
|
+
...subDeathEventsNode ? ["sub-death-events"] : [],
|
|
1376
|
+
...trailHistoryNode ? ["trail-history"] : []
|
|
1377
|
+
],
|
|
1378
|
+
trailMeta: buffers.trailMeta,
|
|
1379
|
+
// Emitter-pose uniforms, refreshed once per frame by the CPU (scalar only).
|
|
1380
|
+
emitterPose: {
|
|
1381
|
+
positionW: uEmitterPos,
|
|
1382
|
+
wrapperQuat: uWrapperQuat,
|
|
1383
|
+
worldScale: uWorldScale
|
|
1384
|
+
},
|
|
1385
|
+
forceFieldInfo: ffNodes ? { offset: forceFieldOffset, countUniform: ffNodes.countUniform } : null,
|
|
1386
|
+
collisionPlaneInfo: cpNodes ? { offset: collisionOffset, countUniform: cpNodes.countUniform } : null
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
function createSubEmitterInitUpdate(child, childMax, childParams, parent, parentMax, fifo, inheritVelocity, particlesPerEvent, childVelValues) {
|
|
1390
|
+
const capacity = Math.max(1, fifo.capacity);
|
|
1391
|
+
const perEvent = Math.max(1, particlesPerEvent);
|
|
1392
|
+
const windowSize = subEmitterWindowSize(capacity);
|
|
1393
|
+
const uSystemSeed = uniform(nextSystemSeed(), "uint");
|
|
1394
|
+
const uSeed = uSystemSeed;
|
|
1395
|
+
const uInherit = uniform(float(Math.max(0, inheritVelocity)));
|
|
1396
|
+
const uFifoBase = uniform(float(0), "uint");
|
|
1397
|
+
const uWrapperQuat = uniform(new Vector4(0, 0, 0, 1));
|
|
1398
|
+
const uEmitterPos = uniform(new Vector4(0, 0, 0, 0));
|
|
1399
|
+
const uWorldScale = uniform(new Vector3(1, 1, 1));
|
|
1400
|
+
const cRadius = uniform(float(childParams.radius));
|
|
1401
|
+
const cThickness = uniform(float(childParams.radiusThickness));
|
|
1402
|
+
const cArc = uniform(float(childParams.arcDeg));
|
|
1403
|
+
const cAngle = uniform(float(childParams.coneAngleDeg));
|
|
1404
|
+
const cRRX = uniform(float(childParams.rectangleRotXDeg));
|
|
1405
|
+
const cRRY = uniform(float(childParams.rectangleRotYDeg));
|
|
1406
|
+
const cRSX = uniform(float(childParams.rectangleScaleX));
|
|
1407
|
+
const cRSY = uniform(float(childParams.rectangleScaleY));
|
|
1408
|
+
const cBSX = uniform(float(childParams.boxScaleX));
|
|
1409
|
+
const cBSY = uniform(float(childParams.boxScaleY));
|
|
1410
|
+
const cBSZ = uniform(float(childParams.boxScaleZ));
|
|
1411
|
+
const cBF = uniform(float(childParams.boxEmitFrom));
|
|
1412
|
+
const cKind = uniform(float(childParams.shapeKind));
|
|
1413
|
+
const cSpeedMin = uniform(float(childParams.speedMin));
|
|
1414
|
+
const cSpeedMax = uniform(float(childParams.speedMax));
|
|
1415
|
+
const cSizeMin = uniform(float(childParams.sizeMin));
|
|
1416
|
+
const cSizeMax = uniform(float(childParams.sizeMax));
|
|
1417
|
+
const cRotMin = uniform(float(childParams.rotMin));
|
|
1418
|
+
const cRotMax = uniform(float(childParams.rotMax));
|
|
1419
|
+
const cOpMin = uniform(float(childParams.opacityMin));
|
|
1420
|
+
const cOpMax = uniform(float(childParams.opacityMax));
|
|
1421
|
+
const cLifeMin = uniform(float(childParams.lifeMin));
|
|
1422
|
+
const cLifeMax = uniform(float(childParams.lifeMax));
|
|
1423
|
+
const cCRR = uniform(float(sRGBToLinear(childParams.colorRMin)));
|
|
1424
|
+
const cCRX = uniform(float(sRGBToLinear(childParams.colorRMax)));
|
|
1425
|
+
const cCGR = uniform(float(sRGBToLinear(childParams.colorGMin)));
|
|
1426
|
+
const cCGX = uniform(float(sRGBToLinear(childParams.colorGMax)));
|
|
1427
|
+
const cCBR = uniform(float(sRGBToLinear(childParams.colorBMin)));
|
|
1428
|
+
const cCBX = uniform(float(sRGBToLinear(childParams.colorBMax)));
|
|
1429
|
+
const cFrMin = uniform(float(childParams.startFrameMin));
|
|
1430
|
+
const cFrMax = uniform(float(childParams.startFrameMax));
|
|
1431
|
+
const cPos = storage(child.position, "vec4", childMax);
|
|
1432
|
+
const cVel = storage(child.velocity, "vec4", childMax);
|
|
1433
|
+
const cCol = storage(child.color, "vec4", childMax);
|
|
1434
|
+
const cPS = storage(child.particleState, "vec4", childMax);
|
|
1435
|
+
const cSV = storage(child.startValues, "vec4", childMax);
|
|
1436
|
+
const cEx = storage(child.startColorsExt, "vec4", childMax);
|
|
1437
|
+
const cOIA = storage(child.orbitalIsActive, "vec4", childMax);
|
|
1438
|
+
const cAlloc = storage(
|
|
1439
|
+
child.allocator,
|
|
1440
|
+
"uint",
|
|
1441
|
+
Math.max(1, childMax + 1)
|
|
1442
|
+
).toAtomic();
|
|
1443
|
+
float(childMax);
|
|
1444
|
+
const cRingModU = uint(childMax);
|
|
1445
|
+
const commandBuffer = new StorageBufferAttribute(
|
|
1446
|
+
new Float32Array(4 * (1 + capacity * perEvent)),
|
|
1447
|
+
4
|
|
1448
|
+
);
|
|
1449
|
+
const fifoCounter = storage(
|
|
1450
|
+
fifo.counter,
|
|
1451
|
+
"uint",
|
|
1452
|
+
Math.max(1, fifo.counter.array.length)
|
|
1453
|
+
).toAtomic();
|
|
1454
|
+
const fifoPayload = storage(
|
|
1455
|
+
fifo.payload,
|
|
1456
|
+
"float",
|
|
1457
|
+
Math.max(1, fifo.payload.array.length)
|
|
1458
|
+
);
|
|
1459
|
+
const sCmd = storage(commandBuffer, "vec4", 1 + capacity * perEvent);
|
|
1460
|
+
const cParseAxis = (rawAxis, ci) => {
|
|
1461
|
+
if (rawAxis && typeof rawAxis === "object" && "min" in rawAxis) {
|
|
1462
|
+
const mn = Number(rawAxis.min) || 0;
|
|
1463
|
+
const mx = Number(rawAxis.max) || 0;
|
|
1464
|
+
return { min: mn, max: mx, isRange: mn !== mx };
|
|
1465
|
+
}
|
|
1466
|
+
const c = typeof rawAxis === "number" ? rawAxis : 0;
|
|
1467
|
+
return { min: c, max: c, isRange: false };
|
|
1468
|
+
};
|
|
1469
|
+
const cVv = childVelValues ?? {
|
|
1470
|
+
linear: [void 0, void 0, void 0],
|
|
1471
|
+
orbital: [void 0, void 0, void 0]
|
|
1472
|
+
};
|
|
1473
|
+
[0, 1, 2].map((k) => cParseAxis(cVv.linear[k]));
|
|
1474
|
+
[0, 1, 2].map((k) => cParseAxis(cVv.orbital[k]));
|
|
1475
|
+
const otherIdx = uFifoBase.equal(float(0)).select(uint(1), uint(0));
|
|
1476
|
+
const counterClearKernel = Fn(() => {
|
|
1477
|
+
atomicStore(fifoCounter.element(otherIdx), uint(0));
|
|
1478
|
+
});
|
|
1479
|
+
const counterClearNode = compute(counterClearKernel(), 1);
|
|
1480
|
+
const commandBuildKernel = Fn(() => {
|
|
1481
|
+
const i = instanceIndex;
|
|
1482
|
+
const winBase = uFifoBase.mul(float(windowSize)).toVar();
|
|
1483
|
+
const count = float(atomicLoad(fifoCounter.element(uFifoBase))).toVar();
|
|
1484
|
+
If(float(i).equal(float(0)), () => {
|
|
1485
|
+
sCmd.element(float(0)).assign(vec4(count, float(0), float(0), float(0)));
|
|
1486
|
+
});
|
|
1487
|
+
If(float(i).lessThan(count), () => {
|
|
1488
|
+
const eb = winBase.add(float(i).mul(float(SUB_EMITTER_EVENT_STRIDE)));
|
|
1489
|
+
const eX = fifoPayload.element(eb).toVar();
|
|
1490
|
+
const eY = fifoPayload.element(eb.add(float(1))).toVar();
|
|
1491
|
+
const eZ = fifoPayload.element(eb.add(float(2))).toVar();
|
|
1492
|
+
const vX = fifoPayload.element(eb.add(float(3))).toVar();
|
|
1493
|
+
const vY = fifoPayload.element(eb.add(float(4))).toVar();
|
|
1494
|
+
const vZ = fifoPayload.element(eb.add(float(5))).toVar();
|
|
1495
|
+
for (let jj = 0; jj < perEvent; jj++) {
|
|
1496
|
+
const birthNo = atomicAdd(cAlloc.element(0), uint(1)).toVar();
|
|
1497
|
+
const slot = birthNo.mod(cRingModU).toVar();
|
|
1498
|
+
const m = float(i.mul(float(perEvent)).add(float(jj)));
|
|
1499
|
+
sCmd.element(float(1).add(m.mul(float(2)))).assign(vec4(slot.toFloat(), eX, eY, eZ));
|
|
1500
|
+
sCmd.element(float(2).add(m.mul(float(2)))).assign(vec4(vX, vY, vZ, float(0)));
|
|
1501
|
+
}
|
|
1502
|
+
});
|
|
1503
|
+
});
|
|
1504
|
+
const commandBuildNode = compute(commandBuildKernel(), capacity);
|
|
1505
|
+
const childInitKernel = Fn(() => {
|
|
1506
|
+
const i = instanceIndex;
|
|
1507
|
+
const header = sCmd.element(float(0)).toVar();
|
|
1508
|
+
If(float(i).lessThan(header.x.mul(float(perEvent))), () => {
|
|
1509
|
+
const m = float(i);
|
|
1510
|
+
const mU = i;
|
|
1511
|
+
const c0 = sCmd.element(float(1).add(m.mul(float(2)))).toVar();
|
|
1512
|
+
const c1 = sCmd.element(float(2).add(m.mul(float(2)))).toVar();
|
|
1513
|
+
const slot = c0.x;
|
|
1514
|
+
const eX = c0.y;
|
|
1515
|
+
const eY = c0.z;
|
|
1516
|
+
const eZ = c0.w;
|
|
1517
|
+
const vX = c1.x;
|
|
1518
|
+
const vY = c1.y;
|
|
1519
|
+
const vZ = c1.z;
|
|
1520
|
+
const parentSpeed = sqrt(
|
|
1521
|
+
vX.mul(vX).add(vY.mul(vY)).add(vZ.mul(vZ))
|
|
1522
|
+
).toVar();
|
|
1523
|
+
const spAdd = parentSpeed.mul(uInherit);
|
|
1524
|
+
{
|
|
1525
|
+
const rcA = pcg01(mU.mul(uint(2654435761)).bitXor(uSystemSeed).bitXor(CH.SHAPE_A));
|
|
1526
|
+
const rcB = pcg01(mU.mul(uint(2654435761)).bitXor(uSystemSeed).bitXor(CH.SHAPE_B));
|
|
1527
|
+
const rcC = pcg01(mU.mul(uint(2654435761)).bitXor(uSystemSeed).bitXor(CH.SHAPE_C));
|
|
1528
|
+
const rcSpeed = pcg01(mU.mul(uint(2654435761)).bitXor(uSystemSeed).bitXor(CH.SPEED));
|
|
1529
|
+
const rcSize = pcg01(mU.mul(uint(2654435761)).bitXor(uSystemSeed).bitXor(CH.SIZE));
|
|
1530
|
+
const rcRot = pcg01(mU.mul(uint(2654435761)).bitXor(uSystemSeed).bitXor(CH.ROTATION));
|
|
1531
|
+
const rcOpacity = pcg01(mU.mul(uint(2654435761)).bitXor(uSystemSeed).bitXor(CH.OPACITY));
|
|
1532
|
+
const rcSheet = pcg01(mU.mul(uint(2654435761)).bitXor(uSystemSeed).bitXor(CH.START_FRAME));
|
|
1533
|
+
const rcLife = pcg01(mU.mul(uint(2654435761)).bitXor(uSystemSeed).bitXor(CH.LIFETIME));
|
|
1534
|
+
const rcColor = pcg01(mU.mul(uint(2654435761)).bitXor(uSystemSeed).bitXor(CH.COLOR));
|
|
1535
|
+
const rcRotOl = pcg01(mU.mul(uint(2654435761)).bitXor(uSystemSeed).bitXor(CH.ROTOL));
|
|
1536
|
+
const shE = shapeEmitNodes(
|
|
1537
|
+
{
|
|
1538
|
+
kind: cKind,
|
|
1539
|
+
radius: cRadius,
|
|
1540
|
+
thickness: cThickness,
|
|
1541
|
+
arcDeg: cArc,
|
|
1542
|
+
coneAngleDeg: cAngle,
|
|
1543
|
+
rectRX: cRRX,
|
|
1544
|
+
rectRY: cRRY,
|
|
1545
|
+
rectSX: cRSX,
|
|
1546
|
+
rectSY: cRSY,
|
|
1547
|
+
boxSX: cBSX,
|
|
1548
|
+
boxSY: cBSY,
|
|
1549
|
+
boxSZ: cBSZ,
|
|
1550
|
+
boxFrom: cBF,
|
|
1551
|
+
speedMin: cSpeedMin.add(spAdd),
|
|
1552
|
+
speedMax: cSpeedMax.add(spAdd)
|
|
1553
|
+
},
|
|
1554
|
+
rcA,
|
|
1555
|
+
rcB,
|
|
1556
|
+
rcC,
|
|
1557
|
+
rcSpeed
|
|
1558
|
+
);
|
|
1559
|
+
const [rx, ry, rz] = quatRotateNodes(
|
|
1560
|
+
shE.px,
|
|
1561
|
+
shE.py,
|
|
1562
|
+
shE.pz,
|
|
1563
|
+
uWrapperQuat
|
|
1564
|
+
);
|
|
1565
|
+
const [rvx, rvy, rvz] = quatRotateNodes(
|
|
1566
|
+
shE.vx,
|
|
1567
|
+
shE.vy,
|
|
1568
|
+
shE.vz,
|
|
1569
|
+
uWrapperQuat
|
|
1570
|
+
);
|
|
1571
|
+
const isWorld = uEmitterPos.w.greaterThan(0.5);
|
|
1572
|
+
const sxf = isWorld.select(uWorldScale.x, float(1));
|
|
1573
|
+
const syf = isWorld.select(uWorldScale.y, float(1));
|
|
1574
|
+
const szf = isWorld.select(uWorldScale.z, float(1));
|
|
1575
|
+
const px = rx.mul(sxf).add(eX);
|
|
1576
|
+
const py = ry.mul(syf).add(eY);
|
|
1577
|
+
const pz = rz.mul(szf).add(eZ);
|
|
1578
|
+
const opac = mix(cOpMin, cOpMax, rcOpacity);
|
|
1579
|
+
const clR = mix(cCRR, cCRX, rcColor);
|
|
1580
|
+
const clG = mix(cCGR, cCGX, rcColor);
|
|
1581
|
+
const clB = mix(cCBR, cCBX, rcColor);
|
|
1582
|
+
const slife = mix(cLifeMin, cLifeMax, rcLife).mul(float(1e3));
|
|
1583
|
+
const ssize = mix(cSizeMin, cSizeMax, rcSize);
|
|
1584
|
+
const srot = mix(cRotMin, cRotMax, rcRot);
|
|
1585
|
+
const startFrame = floor(mix(cFrMin, cFrMax, rcSheet)).toVar();
|
|
1586
|
+
const rotSpeed = mix(
|
|
1587
|
+
float(childParams.rotOverLifeMin),
|
|
1588
|
+
float(childParams.rotOverLifeMax),
|
|
1589
|
+
rcRotOl
|
|
1590
|
+
);
|
|
1591
|
+
const stableSeedU = pcgRawU32(
|
|
1592
|
+
mixBirthSeed(mU, uSystemSeed, CH.STABLE_SEED)
|
|
1593
|
+
).bitAnd(uint(16777215)).toVar();
|
|
1594
|
+
cPos.element(slot).assign(vec4(px, py, pz, float(0)));
|
|
1595
|
+
cVel.element(slot).assign(vec4(rvx, rvy, rvz, float(0)));
|
|
1596
|
+
cCol.element(slot).assign(vec4(clR, clG, clB, opac));
|
|
1597
|
+
cPS.element(slot).assign(
|
|
1598
|
+
vec4(float(0), ssize, srot, startFrame)
|
|
1599
|
+
);
|
|
1600
|
+
cSV.element(slot).assign(vec4(slife, ssize, opac, clR));
|
|
1601
|
+
cEx.element(slot).assign(
|
|
1602
|
+
vec4(clG, clB, rotSpeed, stableSeedU.toFloat())
|
|
1603
|
+
);
|
|
1604
|
+
cOIA.element(slot).assign(vec4(rx, ry, rz, float(1)));
|
|
1605
|
+
}
|
|
1606
|
+
});
|
|
1607
|
+
});
|
|
1608
|
+
const childInitNode = compute(
|
|
1609
|
+
childInitKernel(),
|
|
1610
|
+
Math.max(1, capacity * perEvent)
|
|
1611
|
+
);
|
|
1612
|
+
const initPassLayouts = [
|
|
1613
|
+
{
|
|
1614
|
+
name: "sub-command-build",
|
|
1615
|
+
storageBindings: 4,
|
|
1616
|
+
// fifoCounter, fifoPayload, allocator, commands
|
|
1617
|
+
uniformBindings: 1
|
|
1618
|
+
// uFifoBase
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
// command buffer + the 7 child pools; NO allocator here.
|
|
1622
|
+
name: "sub-child-init",
|
|
1623
|
+
storageBindings: 8,
|
|
1624
|
+
uniformBindings: 35
|
|
1625
|
+
// seed/inherit/pose + 30 child shape scalars
|
|
1626
|
+
},
|
|
1627
|
+
{
|
|
1628
|
+
name: "sub-counter-clear",
|
|
1629
|
+
storageBindings: 1,
|
|
1630
|
+
uniformBindings: 1
|
|
1631
|
+
}
|
|
1632
|
+
];
|
|
1633
|
+
return {
|
|
1634
|
+
commandBuildNode,
|
|
1635
|
+
childInitNode,
|
|
1636
|
+
counterClearNode,
|
|
1637
|
+
commandBuffer,
|
|
1638
|
+
passLayouts: initPassLayouts,
|
|
1639
|
+
passName: fifo.trigger === 0 ? "sub-birth" : "sub-death",
|
|
1640
|
+
counterClearPassName: "fifo-counter-clear",
|
|
1641
|
+
uniforms: {
|
|
1642
|
+
seed: uSeed,
|
|
1643
|
+
fifoBase: uFifoBase,
|
|
1644
|
+
inherit: uInherit,
|
|
1645
|
+
positionW: uEmitterPos,
|
|
1646
|
+
wrapperQuat: uWrapperQuat
|
|
1647
|
+
},
|
|
1648
|
+
buffers: child
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
function createTrailRibbonUpdate(desc) {
|
|
1652
|
+
const L = desc.length;
|
|
1653
|
+
const rows = L + 1;
|
|
1654
|
+
const vertexCount = Math.max(1, desc.maxParticles) * Math.max(1, L);
|
|
1655
|
+
const uNowMs = uniform(float(0));
|
|
1656
|
+
const activeFns = [
|
|
1657
|
+
desc.curveFns.width ?? ((t) => t),
|
|
1658
|
+
desc.curveFns.opacity ?? ((t) => t),
|
|
1659
|
+
desc.curveFns.colorR ?? ((t) => t),
|
|
1660
|
+
desc.curveFns.colorG ?? ((t) => t),
|
|
1661
|
+
desc.curveFns.colorB ?? ((t) => t)
|
|
1662
|
+
];
|
|
1663
|
+
const curveData = new Float32Array(activeFns.length * CURVE_RESOLUTION);
|
|
1664
|
+
activeFns.forEach((fn, k) => {
|
|
1665
|
+
curveData.set(bakeCurve(fn, CURVE_RESOLUTION), k * CURVE_RESOLUTION);
|
|
1666
|
+
});
|
|
1667
|
+
const IDX_WIDTH = 0;
|
|
1668
|
+
const IDX_OPACITY = 1;
|
|
1669
|
+
const IDX_CR = 2;
|
|
1670
|
+
const IDX_CG = 3;
|
|
1671
|
+
const IDX_CB = 4;
|
|
1672
|
+
const aPos = storage(desc.position, "vec4", vertexCount * 2);
|
|
1673
|
+
const aNext = storage(desc.next, "vec4", vertexCount * 2);
|
|
1674
|
+
const aUVA = storage(desc.uvColorA, "vec4", vertexCount * 2);
|
|
1675
|
+
const aColB = storage(desc.colorB, "vec4", vertexCount * 2);
|
|
1676
|
+
const hist = storage(desc.history, "vec4", rows * desc.maxParticles);
|
|
1677
|
+
const sMeta = storage(
|
|
1678
|
+
desc.meta,
|
|
1679
|
+
"uint",
|
|
1680
|
+
Math.max(1, desc.meta.array.length)
|
|
1681
|
+
).toAtomic();
|
|
1682
|
+
const pColor = storage(desc.particleColor, "vec4", desc.maxParticles);
|
|
1683
|
+
const sCD = buffer(curveData, "float", curveData.length);
|
|
1684
|
+
const lookupCurve = createCurveLookup(sCD);
|
|
1685
|
+
const halfWidthBase = float(desc.width * 0.5);
|
|
1686
|
+
const kernel = Fn(() => {
|
|
1687
|
+
const idx = instanceIndex;
|
|
1688
|
+
const lf = float(L);
|
|
1689
|
+
const rowF = float(rows);
|
|
1690
|
+
const i = floor(float(idx).div(lf));
|
|
1691
|
+
const s = float(idx).sub(i.mul(lf));
|
|
1692
|
+
If(i.lessThan(float(desc.maxParticles)), () => {
|
|
1693
|
+
const base = i.mul(rowF);
|
|
1694
|
+
const cursor = float(
|
|
1695
|
+
atomicLoad(sMeta.element(i.mul(float(2))))
|
|
1696
|
+
).toVar();
|
|
1697
|
+
const count = float(
|
|
1698
|
+
atomicLoad(sMeta.element(i.mul(float(2)).add(float(1))))
|
|
1699
|
+
).toVar();
|
|
1700
|
+
If(count.greaterThan(float(0.5)), () => {
|
|
1701
|
+
const raw = cursor.sub(s).add(lf);
|
|
1702
|
+
const si = raw.sub(floor(raw.div(lf)).mul(lf));
|
|
1703
|
+
const sample = hist.element(base.add(si)).toVar();
|
|
1704
|
+
const nextRaw = si.add(float(1));
|
|
1705
|
+
const ni = nextRaw.sub(floor(nextRaw.div(lf)).mul(lf));
|
|
1706
|
+
const nextSample = hist.element(base.add(ni)).toVar();
|
|
1707
|
+
const t = count.greaterThan(float(1.5)).select(s.div(max(count.sub(float(1)), float(1))), float(0));
|
|
1708
|
+
const wScale = lookupCurve({
|
|
1709
|
+
curveIndex: float(IDX_WIDTH),
|
|
1710
|
+
t
|
|
1711
|
+
});
|
|
1712
|
+
const oScale = lookupCurve({
|
|
1713
|
+
curveIndex: float(IDX_OPACITY),
|
|
1714
|
+
t
|
|
1715
|
+
});
|
|
1716
|
+
const cr = lookupCurve({ curveIndex: float(IDX_CR), t });
|
|
1717
|
+
const cg = lookupCurve({ curveIndex: float(IDX_CG), t });
|
|
1718
|
+
const cb = lookupCurve({ curveIndex: float(IDX_CB), t });
|
|
1719
|
+
const pcol = pColor.element(i).toVar();
|
|
1720
|
+
const inRange = s.lessThan(count);
|
|
1721
|
+
const ageOk = desc.maxTime > 0 ? uNowMs.sub(sample.w).lessThanEqual(float(desc.maxTime)) : inRange;
|
|
1722
|
+
const alive = inRange.and(ageOk);
|
|
1723
|
+
const hw = alive.select(halfWidthBase.mul(wScale), float(0));
|
|
1724
|
+
const alpha = alive.select(oScale.mul(pcol.w), float(0));
|
|
1725
|
+
const nPos = count.greaterThan(float(1.5)).select(nextSample.xyz, sample.xyz);
|
|
1726
|
+
for (let side = 0; side < 2; side++) {
|
|
1727
|
+
const vi = float(idx).mul(float(2)).add(float(side));
|
|
1728
|
+
aPos.element(vi).assign(
|
|
1729
|
+
vec4(sample.x, sample.y, sample.z, hw)
|
|
1730
|
+
);
|
|
1731
|
+
aNext.element(vi).assign(
|
|
1732
|
+
vec4(nPos.x, nPos.y, nPos.z, alpha)
|
|
1733
|
+
);
|
|
1734
|
+
aUVA.element(vi).assign(
|
|
1735
|
+
vec4(float(side), t, cr.mul(pcol.x), cg.mul(pcol.y))
|
|
1736
|
+
);
|
|
1737
|
+
aColB.element(vi).assign(
|
|
1738
|
+
vec4(cb.mul(pcol.z), alpha, float(0), float(0))
|
|
1739
|
+
);
|
|
1740
|
+
}
|
|
1741
|
+
});
|
|
1742
|
+
});
|
|
1743
|
+
});
|
|
1744
|
+
const ribbonNode = compute(kernel(), vertexCount);
|
|
1745
|
+
return {
|
|
1746
|
+
ribbonNode,
|
|
1747
|
+
// 4 ribbon streams + history + meta + particle color = 7 storage bindings.
|
|
1748
|
+
passLayouts: [
|
|
1749
|
+
{ name: "trail-ribbon", storageBindings: 7, uniformBindings: 2 }
|
|
1750
|
+
],
|
|
1751
|
+
uniforms: { nowMs: uNowMs },
|
|
1752
|
+
buffers: {
|
|
1753
|
+
position: desc.position,
|
|
1754
|
+
next: desc.next,
|
|
1755
|
+
uvColorA: desc.uvColorA,
|
|
1756
|
+
colorB: desc.colorB,
|
|
1757
|
+
history: desc.history
|
|
1758
|
+
}
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// src/js/effects/three-particles/three-particles-constants.ts
|
|
1763
|
+
var POINT_SIZE_SCALE = 100;
|
|
1764
|
+
var ALPHA_DISCARD_THRESHOLD = 1e-3;
|
|
1765
|
+
var _dummyTexture = null;
|
|
1766
|
+
function getDummyTexture() {
|
|
1767
|
+
if (!_dummyTexture) {
|
|
1768
|
+
_dummyTexture = new DataTexture(new Uint8Array([255, 255, 255, 255]), 1, 1);
|
|
1769
|
+
_dummyTexture.needsUpdate = true;
|
|
1770
|
+
}
|
|
1771
|
+
return _dummyTexture;
|
|
1772
|
+
}
|
|
1773
|
+
function createParticleUniforms(sharedUniforms) {
|
|
1774
|
+
const dummy = getDummyTexture();
|
|
1775
|
+
const map = sharedUniforms.map.value ?? dummy;
|
|
1776
|
+
return {
|
|
1777
|
+
uMap: map,
|
|
1778
|
+
uElapsed: uniform(float(sharedUniforms.elapsed.value)),
|
|
1779
|
+
uFps: uniform(float(sharedUniforms.fps.value)),
|
|
1780
|
+
uUseFPSForFrameIndex: uniform(
|
|
1781
|
+
float(sharedUniforms.useFPSForFrameIndex.value ? 1 : 0)
|
|
1782
|
+
),
|
|
1783
|
+
uTiles: uniform(sharedUniforms.tiles.value),
|
|
1784
|
+
uDiscardBg: uniform(
|
|
1785
|
+
float(sharedUniforms.discardBackgroundColor.value ? 1 : 0)
|
|
1786
|
+
),
|
|
1787
|
+
uBgColor: uniform(
|
|
1788
|
+
new Vector3(
|
|
1789
|
+
sharedUniforms.backgroundColor.value.r,
|
|
1790
|
+
sharedUniforms.backgroundColor.value.g,
|
|
1791
|
+
sharedUniforms.backgroundColor.value.b
|
|
1792
|
+
)
|
|
1793
|
+
),
|
|
1794
|
+
uBgTolerance: uniform(float(sharedUniforms.backgroundColorTolerance.value)),
|
|
1795
|
+
uSoftEnabled: uniform(
|
|
1796
|
+
float(sharedUniforms.softParticlesEnabled.value ? 1 : 0)
|
|
1797
|
+
),
|
|
1798
|
+
uSoftIntensity: uniform(float(sharedUniforms.softParticlesIntensity.value)),
|
|
1799
|
+
uSceneDepthTex: sharedUniforms.sceneDepthTexture.value ?? dummy,
|
|
1800
|
+
uCameraNearFar: uniform(sharedUniforms.cameraNearFar.value)
|
|
1801
|
+
};
|
|
1802
|
+
}
|
|
1803
|
+
var computeFrameIndex = Fn(
|
|
1804
|
+
({
|
|
1805
|
+
vLifetime,
|
|
1806
|
+
vStartLifetime,
|
|
1807
|
+
vStartFrame,
|
|
1808
|
+
uFps,
|
|
1809
|
+
uUseFPSForFrameIndex,
|
|
1810
|
+
uTiles
|
|
1811
|
+
}) => {
|
|
1812
|
+
const totalFrames = uTiles.x.mul(uTiles.y);
|
|
1813
|
+
const lifePercent = min(vLifetime.div(vStartLifetime), float(1));
|
|
1814
|
+
const fpsBased = max(vLifetime.div(1e3).mul(uFps), float(0));
|
|
1815
|
+
const lifetimeBased = max(
|
|
1816
|
+
min(floor(lifePercent.mul(totalFrames)), totalFrames.sub(1)),
|
|
1817
|
+
float(0)
|
|
1818
|
+
);
|
|
1819
|
+
const fpsResult = uFps.equal(0).select(float(0), fpsBased);
|
|
1820
|
+
const frameOffset = uUseFPSForFrameIndex.greaterThan(0.5).select(fpsResult, lifetimeBased);
|
|
1821
|
+
return round(vStartFrame).add(frameOffset);
|
|
1822
|
+
}
|
|
1823
|
+
);
|
|
1824
|
+
var computeSpriteSheetUV = Fn(
|
|
1825
|
+
({ baseUV, frameIndex, uTiles }) => {
|
|
1826
|
+
const spriteX = floor(mod(frameIndex, uTiles.x));
|
|
1827
|
+
const spriteY = floor(mod(frameIndex.div(uTiles.x), uTiles.y));
|
|
1828
|
+
return vec2(
|
|
1829
|
+
baseUV.x.div(uTiles.x).add(spriteX.div(uTiles.x)),
|
|
1830
|
+
baseUV.y.div(uTiles.y).add(spriteY.div(uTiles.y))
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
);
|
|
1834
|
+
var linearizeDepth = Fn(
|
|
1835
|
+
({ depthSample, near, far }) => {
|
|
1836
|
+
const zNdc = depthSample.mul(2).sub(1);
|
|
1837
|
+
return near.mul(2).mul(far).div(far.add(near).sub(zNdc.mul(far.sub(near))));
|
|
1838
|
+
}
|
|
1839
|
+
);
|
|
1840
|
+
var computeSoftParticleFade = Fn(
|
|
1841
|
+
({
|
|
1842
|
+
viewZ,
|
|
1843
|
+
uSoftEnabled,
|
|
1844
|
+
uSoftIntensity,
|
|
1845
|
+
uSceneDepthTex,
|
|
1846
|
+
uCameraNearFar
|
|
1847
|
+
}) => {
|
|
1848
|
+
const softFade = float(1).toVar();
|
|
1849
|
+
If(uSoftEnabled.greaterThan(0.5), () => {
|
|
1850
|
+
const depthSample = texture(uSceneDepthTex, screenUV).x;
|
|
1851
|
+
const sceneDepthLinear = linearizeDepth({
|
|
1852
|
+
depthSample,
|
|
1853
|
+
near: uCameraNearFar.x,
|
|
1854
|
+
far: uCameraNearFar.y
|
|
1855
|
+
});
|
|
1856
|
+
const depthDiff = sceneDepthLinear.sub(viewZ);
|
|
1857
|
+
softFade.assign(smoothstep(float(0), uSoftIntensity, depthDiff));
|
|
1858
|
+
});
|
|
1859
|
+
return softFade;
|
|
1860
|
+
}
|
|
1861
|
+
);
|
|
1862
|
+
function applyBackgroundDiscard({
|
|
1863
|
+
texColor,
|
|
1864
|
+
uDiscardBg,
|
|
1865
|
+
uBgColor,
|
|
1866
|
+
uBgTolerance
|
|
1867
|
+
}) {
|
|
1868
|
+
const diff = vec3(
|
|
1869
|
+
texColor.x.sub(uBgColor.x),
|
|
1870
|
+
texColor.y.sub(uBgColor.y),
|
|
1871
|
+
texColor.z.sub(uBgColor.z)
|
|
1872
|
+
);
|
|
1873
|
+
Discard(
|
|
1874
|
+
uDiscardBg.greaterThan(0.5).and(abs(length(diff)).lessThan(uBgTolerance))
|
|
1875
|
+
);
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
// src/js/effects/three-particles/webgpu/tsl-instanced-billboard-material.ts
|
|
1879
|
+
function createInstancedBillboardTSLMaterial(sharedUniforms, rendererConfig, gpuCompute = false) {
|
|
1880
|
+
const u = createParticleUniforms(sharedUniforms);
|
|
1881
|
+
const uViewportHeight = uniform(
|
|
1882
|
+
typeof sharedUniforms.viewportHeight?.value === "number" ? sharedUniforms.viewportHeight.value : 1
|
|
1883
|
+
);
|
|
1884
|
+
sharedUniforms.viewportHeight = uViewportHeight;
|
|
1885
|
+
const aInstanceOffset = attribute("instanceOffset");
|
|
1886
|
+
const aColor = attribute("instanceColor");
|
|
1887
|
+
const aParticleState = gpuCompute ? attribute("instanceParticleState") : null;
|
|
1888
|
+
const aStartValues = gpuCompute ? attribute("instanceStartValues") : null;
|
|
1889
|
+
const aSize = gpuCompute ? null : attribute("instanceSize");
|
|
1890
|
+
const aLifetime = gpuCompute ? null : attribute("instanceLifetime");
|
|
1891
|
+
const aStartLifetime = gpuCompute ? null : attribute("instanceStartLifetime");
|
|
1892
|
+
const aRotation = gpuCompute ? null : attribute("instanceRotation");
|
|
1893
|
+
const aStartFrame = gpuCompute ? null : attribute("instanceStartFrame");
|
|
1894
|
+
const vColor = varyingProperty("vec4", "vColor");
|
|
1895
|
+
const vLifetime = varyingProperty("float", "vLifetime");
|
|
1896
|
+
const vStartLifetime = varyingProperty("float", "vStartLifetime");
|
|
1897
|
+
const vRotation = varyingProperty("float", "vRotation");
|
|
1898
|
+
const vStartFrame = varyingProperty("float", "vStartFrame");
|
|
1899
|
+
const vUv = varyingProperty("vec2", "vUv");
|
|
1900
|
+
const vViewZ = varyingProperty("float", "vViewZ");
|
|
1901
|
+
const vertexNode = Fn(() => {
|
|
1902
|
+
const clipPos = vec4(0, 0, 0, -1).toVar();
|
|
1903
|
+
If(aColor.w.greaterThan(0), () => {
|
|
1904
|
+
vColor.assign(aColor.toVar());
|
|
1905
|
+
if (gpuCompute) {
|
|
1906
|
+
vLifetime.assign(aParticleState.x);
|
|
1907
|
+
vStartLifetime.assign(aStartValues.x);
|
|
1908
|
+
vRotation.assign(aParticleState.z);
|
|
1909
|
+
vStartFrame.assign(aParticleState.w);
|
|
1910
|
+
} else {
|
|
1911
|
+
vLifetime.assign(aLifetime);
|
|
1912
|
+
vStartLifetime.assign(aStartLifetime);
|
|
1913
|
+
vRotation.assign(aRotation);
|
|
1914
|
+
vStartFrame.assign(aStartFrame);
|
|
1915
|
+
}
|
|
1916
|
+
vUv.assign(
|
|
1917
|
+
vec2(positionLocal.x.add(0.5), float(0.5).sub(positionLocal.y))
|
|
1918
|
+
);
|
|
1919
|
+
const mvPosition = modelViewMatrix.mul(vec4(aInstanceOffset.xyz, 1)).toVar();
|
|
1920
|
+
const dist = length(mvPosition.xyz);
|
|
1921
|
+
const sizeVal = gpuCompute ? aParticleState.y : aSize;
|
|
1922
|
+
const pointSizePx = sizeVal.mul(POINT_SIZE_SCALE).div(dist);
|
|
1923
|
+
const projY = cameraProjectionMatrix.element(1).element(1);
|
|
1924
|
+
const perspectiveSize = pointSizePx.mul(mvPosition.z.negate()).div(projY.mul(uViewportHeight).mul(0.5));
|
|
1925
|
+
mvPosition.x.addAssign(positionLocal.x.mul(perspectiveSize));
|
|
1926
|
+
mvPosition.y.addAssign(positionLocal.y.mul(perspectiveSize));
|
|
1927
|
+
vViewZ.assign(mvPosition.z.negate());
|
|
1928
|
+
clipPos.assign(cameraProjectionMatrix.mul(mvPosition));
|
|
1929
|
+
});
|
|
1930
|
+
return clipPos;
|
|
1931
|
+
})();
|
|
1932
|
+
const fragmentColor = Fn(() => {
|
|
1933
|
+
const outColor = vColor.toVar();
|
|
1934
|
+
const center = vec2(0.5, 0.5);
|
|
1935
|
+
const centered = vUv.sub(center);
|
|
1936
|
+
const cosR = cos(vRotation);
|
|
1937
|
+
const sinR = sin(vRotation);
|
|
1938
|
+
const rotated = vec2(
|
|
1939
|
+
centered.x.mul(cosR).add(centered.y.mul(sinR)),
|
|
1940
|
+
centered.x.mul(sinR).negate().add(centered.y.mul(cosR))
|
|
1941
|
+
);
|
|
1942
|
+
const rotatedUV = rotated.add(center);
|
|
1943
|
+
const dist = length(rotatedUV.sub(center));
|
|
1944
|
+
If(dist.greaterThan(0.5), () => {
|
|
1945
|
+
Discard();
|
|
1946
|
+
});
|
|
1947
|
+
const frameIndex = computeFrameIndex({
|
|
1948
|
+
vLifetime,
|
|
1949
|
+
vStartLifetime,
|
|
1950
|
+
vStartFrame,
|
|
1951
|
+
uFps: u.uFps,
|
|
1952
|
+
uUseFPSForFrameIndex: u.uUseFPSForFrameIndex,
|
|
1953
|
+
uTiles: u.uTiles
|
|
1954
|
+
});
|
|
1955
|
+
const uvPoint = computeSpriteSheetUV({
|
|
1956
|
+
baseUV: rotatedUV,
|
|
1957
|
+
frameIndex,
|
|
1958
|
+
uTiles: u.uTiles
|
|
1959
|
+
});
|
|
1960
|
+
const texColor = texture(u.uMap, uvPoint);
|
|
1961
|
+
outColor.assign(outColor.mul(texColor));
|
|
1962
|
+
applyBackgroundDiscard({
|
|
1963
|
+
texColor,
|
|
1964
|
+
uDiscardBg: u.uDiscardBg,
|
|
1965
|
+
uBgColor: u.uBgColor,
|
|
1966
|
+
uBgTolerance: u.uBgTolerance
|
|
1967
|
+
});
|
|
1968
|
+
const softFade = computeSoftParticleFade({
|
|
1969
|
+
viewZ: vViewZ,
|
|
1970
|
+
uSoftEnabled: u.uSoftEnabled,
|
|
1971
|
+
uSoftIntensity: u.uSoftIntensity,
|
|
1972
|
+
uSceneDepthTex: u.uSceneDepthTex,
|
|
1973
|
+
uCameraNearFar: u.uCameraNearFar
|
|
1974
|
+
});
|
|
1975
|
+
outColor.assign(vec4(outColor.xyz, outColor.w.mul(softFade)));
|
|
1976
|
+
Discard(outColor.w.lessThan(ALPHA_DISCARD_THRESHOLD));
|
|
1977
|
+
return outColor;
|
|
1978
|
+
})();
|
|
1979
|
+
const material = new MeshBasicNodeMaterial();
|
|
1980
|
+
material.transparent = rendererConfig.transparent;
|
|
1981
|
+
material.blending = rendererConfig.blending;
|
|
1982
|
+
material.depthTest = rendererConfig.depthTest;
|
|
1983
|
+
material.depthWrite = rendererConfig.depthWrite;
|
|
1984
|
+
material.toneMapped = false;
|
|
1985
|
+
material.fog = false;
|
|
1986
|
+
material.vertexNode = vertexNode;
|
|
1987
|
+
material.colorNode = fragmentColor;
|
|
1988
|
+
return material;
|
|
1989
|
+
}
|
|
1990
|
+
var applyQuaternion = Fn(
|
|
1991
|
+
({ v, q }) => {
|
|
1992
|
+
const t = cross(q.xyz, v).mul(2);
|
|
1993
|
+
return v.add(t.mul(q.w)).add(cross(q.xyz, t));
|
|
1994
|
+
}
|
|
1995
|
+
);
|
|
1996
|
+
function createMeshParticleTSLMaterial(sharedUniforms, rendererConfig, gpuCompute = false) {
|
|
1997
|
+
const u = createParticleUniforms(sharedUniforms);
|
|
1998
|
+
const aInstanceOffset = attribute("instanceOffset");
|
|
1999
|
+
const aColor = attribute("instanceColor");
|
|
2000
|
+
const aParticleState = gpuCompute ? attribute("instanceParticleState") : null;
|
|
2001
|
+
const aStartValues = gpuCompute ? attribute("instanceStartValues") : null;
|
|
2002
|
+
const aInstanceQuat = gpuCompute ? null : attribute("instanceQuat");
|
|
2003
|
+
const aSize = gpuCompute ? null : attribute("instanceSize");
|
|
2004
|
+
const aLifetime = gpuCompute ? null : attribute("instanceLifetime");
|
|
2005
|
+
const aStartLifetime = gpuCompute ? null : attribute("instanceStartLifetime");
|
|
2006
|
+
const aRotation = gpuCompute ? null : attribute("instanceRotation");
|
|
2007
|
+
const aStartFrame = gpuCompute ? null : attribute("instanceStartFrame");
|
|
2008
|
+
const vColor = varyingProperty("vec4", "vColor");
|
|
2009
|
+
const vLifetime = varyingProperty("float", "vLifetime");
|
|
2010
|
+
const vStartLifetime = varyingProperty("float", "vStartLifetime");
|
|
2011
|
+
const vStartFrame = varyingProperty("float", "vStartFrame");
|
|
2012
|
+
const vRotation = varyingProperty("float", "vRotation");
|
|
2013
|
+
const vNormal = varyingProperty("vec3", "vNormal");
|
|
2014
|
+
const vViewZ = varyingProperty("float", "vViewZ");
|
|
2015
|
+
const vertexSetup = Fn(() => {
|
|
2016
|
+
const clipPos = vec4(0, 0, 0, -1).toVar();
|
|
2017
|
+
If(aColor.w.greaterThan(0), () => {
|
|
2018
|
+
vColor.assign(aColor.toVar());
|
|
2019
|
+
if (gpuCompute) {
|
|
2020
|
+
vLifetime.assign(aParticleState.x);
|
|
2021
|
+
vStartLifetime.assign(aStartValues.x);
|
|
2022
|
+
vStartFrame.assign(aParticleState.w);
|
|
2023
|
+
vRotation.assign(aParticleState.z);
|
|
2024
|
+
} else {
|
|
2025
|
+
vLifetime.assign(aLifetime);
|
|
2026
|
+
vStartLifetime.assign(aStartLifetime);
|
|
2027
|
+
vStartFrame.assign(aStartFrame);
|
|
2028
|
+
vRotation.assign(aRotation);
|
|
2029
|
+
}
|
|
2030
|
+
let quat;
|
|
2031
|
+
if (gpuCompute) {
|
|
2032
|
+
const halfZ = aParticleState.z.mul(0.5);
|
|
2033
|
+
quat = vec4(0, 0, sin(halfZ), cos(halfZ));
|
|
2034
|
+
} else {
|
|
2035
|
+
quat = aInstanceQuat;
|
|
2036
|
+
}
|
|
2037
|
+
const rotatedPos = applyQuaternion({
|
|
2038
|
+
v: positionLocal,
|
|
2039
|
+
q: quat
|
|
2040
|
+
});
|
|
2041
|
+
const scaledPos = rotatedPos.mul(gpuCompute ? aParticleState.y : aSize);
|
|
2042
|
+
const worldPos = scaledPos.add(aInstanceOffset.xyz);
|
|
2043
|
+
const mvPos = modelViewMatrix.mul(vec4(worldPos, 1));
|
|
2044
|
+
vViewZ.assign(mvPos.z.negate());
|
|
2045
|
+
const rotatedNormal = applyQuaternion({
|
|
2046
|
+
v: normalLocal,
|
|
2047
|
+
q: quat
|
|
2048
|
+
});
|
|
2049
|
+
const mvNormal = modelViewMatrix.mul(vec4(rotatedNormal, 0)).xyz;
|
|
2050
|
+
vNormal.assign(mvNormal.normalize());
|
|
2051
|
+
clipPos.assign(cameraProjectionMatrix.mul(mvPos));
|
|
2052
|
+
});
|
|
2053
|
+
return clipPos;
|
|
2054
|
+
})();
|
|
2055
|
+
const fragmentColor = Fn(() => {
|
|
2056
|
+
const outColor = vColor.toVar();
|
|
2057
|
+
const uvPoint = vec2(uv()).toVar();
|
|
2058
|
+
If(u.uTiles.x.greaterThan(1).or(u.uTiles.y.greaterThan(1)), () => {
|
|
2059
|
+
const frameIndex = computeFrameIndex({
|
|
2060
|
+
vLifetime,
|
|
2061
|
+
vStartLifetime,
|
|
2062
|
+
vStartFrame,
|
|
2063
|
+
uFps: u.uFps,
|
|
2064
|
+
uUseFPSForFrameIndex: u.uUseFPSForFrameIndex,
|
|
2065
|
+
uTiles: u.uTiles
|
|
2066
|
+
});
|
|
2067
|
+
uvPoint.assign(
|
|
2068
|
+
computeSpriteSheetUV({
|
|
2069
|
+
baseUV: uv(),
|
|
2070
|
+
frameIndex,
|
|
2071
|
+
uTiles: u.uTiles
|
|
2072
|
+
})
|
|
2073
|
+
);
|
|
2074
|
+
});
|
|
2075
|
+
const texColor = texture(u.uMap, uvPoint);
|
|
2076
|
+
outColor.assign(outColor.mul(texColor));
|
|
2077
|
+
applyBackgroundDiscard({
|
|
2078
|
+
texColor,
|
|
2079
|
+
uDiscardBg: u.uDiscardBg,
|
|
2080
|
+
uBgColor: u.uBgColor,
|
|
2081
|
+
uBgTolerance: u.uBgTolerance
|
|
2082
|
+
});
|
|
2083
|
+
const lightIntensity = float(0.5).add(
|
|
2084
|
+
float(0.5).mul(max(dot(vNormal, vec3(0, 0, 1)), float(0)))
|
|
2085
|
+
);
|
|
2086
|
+
outColor.assign(vec4(outColor.xyz.mul(lightIntensity), outColor.w));
|
|
2087
|
+
const softFade = computeSoftParticleFade({
|
|
2088
|
+
viewZ: vViewZ,
|
|
2089
|
+
uSoftEnabled: u.uSoftEnabled,
|
|
2090
|
+
uSoftIntensity: u.uSoftIntensity,
|
|
2091
|
+
uSceneDepthTex: u.uSceneDepthTex,
|
|
2092
|
+
uCameraNearFar: u.uCameraNearFar
|
|
2093
|
+
});
|
|
2094
|
+
outColor.assign(vec4(outColor.xyz, outColor.w.mul(softFade)));
|
|
2095
|
+
Discard(outColor.w.lessThan(ALPHA_DISCARD_THRESHOLD));
|
|
2096
|
+
return outColor;
|
|
2097
|
+
})();
|
|
2098
|
+
const material = new MeshBasicNodeMaterial();
|
|
2099
|
+
material.transparent = rendererConfig.transparent;
|
|
2100
|
+
material.blending = rendererConfig.blending;
|
|
2101
|
+
material.depthTest = rendererConfig.depthTest;
|
|
2102
|
+
material.depthWrite = rendererConfig.depthWrite;
|
|
2103
|
+
material.toneMapped = false;
|
|
2104
|
+
material.fog = false;
|
|
2105
|
+
material.vertexNode = vertexSetup;
|
|
2106
|
+
material.colorNode = fragmentColor;
|
|
2107
|
+
return material;
|
|
2108
|
+
}
|
|
2109
|
+
function createPointSpriteTSLMaterial(sharedUniforms, rendererConfig, gpuCompute = false) {
|
|
2110
|
+
const u = createParticleUniforms(sharedUniforms);
|
|
2111
|
+
const aColor = attribute("color");
|
|
2112
|
+
const aParticleState = gpuCompute ? attribute("particleState") : null;
|
|
2113
|
+
const aStartValues = gpuCompute ? attribute("startValues") : null;
|
|
2114
|
+
const aSize = gpuCompute ? null : attribute("size");
|
|
2115
|
+
const aLifetime = gpuCompute ? null : attribute("lifetime");
|
|
2116
|
+
const aStartLifetime = gpuCompute ? null : attribute("startLifetime");
|
|
2117
|
+
const aRotation = gpuCompute ? null : attribute("rotation");
|
|
2118
|
+
const aStartFrame = gpuCompute ? null : attribute("startFrame");
|
|
2119
|
+
const mvPos = modelViewMatrix.mul(vec4(positionLocal, 1));
|
|
2120
|
+
const sizeVal = gpuCompute ? aParticleState.y : aSize;
|
|
2121
|
+
const sizeNode = aColor.w.greaterThan(0).select(sizeVal.mul(POINT_SIZE_SCALE).div(length(mvPos.xyz)), float(0));
|
|
2122
|
+
const vColor = varyingProperty("vec4", "vColor");
|
|
2123
|
+
const vLifetime = varyingProperty("float", "vLifetime");
|
|
2124
|
+
const vStartLifetime = varyingProperty("float", "vStartLifetime");
|
|
2125
|
+
const vRotation = varyingProperty("float", "vRotation");
|
|
2126
|
+
const vStartFrame = varyingProperty("float", "vStartFrame");
|
|
2127
|
+
const vViewZ = varyingProperty("float", "vViewZ");
|
|
2128
|
+
const vertexSetup = Fn(() => {
|
|
2129
|
+
If(aColor.w.greaterThan(0), () => {
|
|
2130
|
+
vColor.assign(aColor.toVar());
|
|
2131
|
+
if (gpuCompute) {
|
|
2132
|
+
vLifetime.assign(aParticleState.x);
|
|
2133
|
+
vStartLifetime.assign(aStartValues.x);
|
|
2134
|
+
vRotation.assign(aParticleState.z);
|
|
2135
|
+
vStartFrame.assign(aParticleState.w);
|
|
2136
|
+
} else {
|
|
2137
|
+
vLifetime.assign(aLifetime);
|
|
2138
|
+
vStartLifetime.assign(aStartLifetime);
|
|
2139
|
+
vRotation.assign(aRotation);
|
|
2140
|
+
vStartFrame.assign(aStartFrame);
|
|
2141
|
+
}
|
|
2142
|
+
vViewZ.assign(mvPos.z.negate());
|
|
2143
|
+
});
|
|
2144
|
+
return positionLocal;
|
|
2145
|
+
})();
|
|
2146
|
+
const fragmentColor = Fn(() => {
|
|
2147
|
+
const outColor = vColor.toVar();
|
|
2148
|
+
const frameIndex = computeFrameIndex({
|
|
2149
|
+
vLifetime,
|
|
2150
|
+
vStartLifetime,
|
|
2151
|
+
vStartFrame,
|
|
2152
|
+
uFps: u.uFps,
|
|
2153
|
+
uUseFPSForFrameIndex: u.uUseFPSForFrameIndex,
|
|
2154
|
+
uTiles: u.uTiles
|
|
2155
|
+
});
|
|
2156
|
+
const center = vec2(0.5, 0.5);
|
|
2157
|
+
const centered = vec2(0, 0);
|
|
2158
|
+
const cosR = cos(vRotation);
|
|
2159
|
+
const sinR = sin(vRotation);
|
|
2160
|
+
const rotated = vec2(
|
|
2161
|
+
centered.x.mul(cosR).add(centered.y.mul(sinR)),
|
|
2162
|
+
centered.x.mul(sinR).negate().add(centered.y.mul(cosR))
|
|
2163
|
+
);
|
|
2164
|
+
const rotatedUV = rotated.add(center);
|
|
2165
|
+
const dist = length(rotatedUV.sub(center));
|
|
2166
|
+
Discard(dist.greaterThan(0.5));
|
|
2167
|
+
const uvPoint = computeSpriteSheetUV({
|
|
2168
|
+
baseUV: rotatedUV,
|
|
2169
|
+
frameIndex,
|
|
2170
|
+
uTiles: u.uTiles
|
|
2171
|
+
});
|
|
2172
|
+
const texColor = texture(u.uMap, uvPoint);
|
|
2173
|
+
outColor.assign(outColor.mul(texColor));
|
|
2174
|
+
applyBackgroundDiscard({
|
|
2175
|
+
texColor,
|
|
2176
|
+
uDiscardBg: u.uDiscardBg,
|
|
2177
|
+
uBgColor: u.uBgColor,
|
|
2178
|
+
uBgTolerance: u.uBgTolerance
|
|
2179
|
+
});
|
|
2180
|
+
const softFade = computeSoftParticleFade({
|
|
2181
|
+
viewZ: vViewZ,
|
|
2182
|
+
uSoftEnabled: u.uSoftEnabled,
|
|
2183
|
+
uSoftIntensity: u.uSoftIntensity,
|
|
2184
|
+
uSceneDepthTex: u.uSceneDepthTex,
|
|
2185
|
+
uCameraNearFar: u.uCameraNearFar
|
|
2186
|
+
});
|
|
2187
|
+
outColor.assign(vec4(outColor.xyz, outColor.w.mul(softFade)));
|
|
2188
|
+
Discard(outColor.w.lessThan(ALPHA_DISCARD_THRESHOLD));
|
|
2189
|
+
return outColor;
|
|
2190
|
+
})();
|
|
2191
|
+
const material = new PointsNodeMaterial();
|
|
2192
|
+
material.transparent = rendererConfig.transparent;
|
|
2193
|
+
material.blending = rendererConfig.blending;
|
|
2194
|
+
material.depthTest = rendererConfig.depthTest;
|
|
2195
|
+
material.depthWrite = rendererConfig.depthWrite;
|
|
2196
|
+
material.toneMapped = false;
|
|
2197
|
+
material.fog = false;
|
|
2198
|
+
material.sizeNode = sizeNode;
|
|
2199
|
+
material.positionNode = vertexSetup;
|
|
2200
|
+
material.colorNode = fragmentColor;
|
|
2201
|
+
return material;
|
|
2202
|
+
}
|
|
2203
|
+
function createTrailUniforms(trailUniforms) {
|
|
2204
|
+
const dummy = getDummyTexture();
|
|
2205
|
+
const map = trailUniforms.map.value ?? dummy;
|
|
2206
|
+
return {
|
|
2207
|
+
uMap: map,
|
|
2208
|
+
uUseMap: uniform(float(trailUniforms.useMap.value ? 1 : 0)),
|
|
2209
|
+
uDiscardBg: uniform(
|
|
2210
|
+
float(trailUniforms.discardBackgroundColor.value ? 1 : 0)
|
|
2211
|
+
),
|
|
2212
|
+
uBgColor: uniform(
|
|
2213
|
+
new trailUniforms.cameraNearFar.value.constructor(
|
|
2214
|
+
trailUniforms.backgroundColor.value.r,
|
|
2215
|
+
trailUniforms.backgroundColor.value.g,
|
|
2216
|
+
trailUniforms.backgroundColor.value.b
|
|
2217
|
+
)
|
|
2218
|
+
),
|
|
2219
|
+
uBgTolerance: uniform(float(trailUniforms.backgroundColorTolerance.value)),
|
|
2220
|
+
uSoftEnabled: uniform(
|
|
2221
|
+
float(trailUniforms.softParticlesEnabled.value ? 1 : 0)
|
|
2222
|
+
),
|
|
2223
|
+
uSoftIntensity: uniform(float(trailUniforms.softParticlesIntensity.value)),
|
|
2224
|
+
uSceneDepthTex: trailUniforms.sceneDepthTexture.value ?? dummy,
|
|
2225
|
+
uCameraNearFar: uniform(trailUniforms.cameraNearFar.value)
|
|
2226
|
+
};
|
|
2227
|
+
}
|
|
2228
|
+
function createTrailRibbonTSLMaterial(trailUniforms, rendererConfig) {
|
|
2229
|
+
const u = createTrailUniforms(trailUniforms);
|
|
2230
|
+
const aPosPacked = attribute("position", "vec4");
|
|
2231
|
+
const aNextPacked = attribute("trailNext", "vec4");
|
|
2232
|
+
const aUvColorA = attribute("trailUVColor", "vec4");
|
|
2233
|
+
const aColorBA = attribute("trailColorBA", "vec4");
|
|
2234
|
+
const aTrailAlpha = aNextPacked.w;
|
|
2235
|
+
const aTrailColor = vec4(aUvColorA.z, aUvColorA.w, aColorBA.x, aColorBA.y);
|
|
2236
|
+
const aTrailOffset = aUvColorA.x.sub(float(0.5));
|
|
2237
|
+
const aTrailHalfWidth = aPosPacked.w;
|
|
2238
|
+
const aTrailNext = vec3(aNextPacked.x, aNextPacked.y, aNextPacked.z);
|
|
2239
|
+
const aTrailUV = vec2(aUvColorA.x, aUvColorA.y);
|
|
2240
|
+
const vAlpha = varyingProperty("float", "vAlpha");
|
|
2241
|
+
const vColor = varyingProperty("vec4", "vColor");
|
|
2242
|
+
const vUv = varyingProperty("vec2", "vUv");
|
|
2243
|
+
const vViewZ = varyingProperty("float", "vViewZ");
|
|
2244
|
+
const positionNode = Fn(() => {
|
|
2245
|
+
vAlpha.assign(aTrailAlpha);
|
|
2246
|
+
vColor.assign(aTrailColor);
|
|
2247
|
+
vUv.assign(aTrailUV);
|
|
2248
|
+
const current = vec3(aPosPacked.x, aPosPacked.y, aPosPacked.z);
|
|
2249
|
+
const next = vec3(aTrailNext);
|
|
2250
|
+
const rawTangent = next.sub(current);
|
|
2251
|
+
const tangentLen = length(rawTangent);
|
|
2252
|
+
const tangent = normalize(
|
|
2253
|
+
tangentLen.lessThan(1e-4).select(vec3(0, 1, 0), rawTangent)
|
|
2254
|
+
);
|
|
2255
|
+
modelViewMatrix.mul(vec4(current, 1));
|
|
2256
|
+
const viewDir = normalize(cameraPosition.sub(current));
|
|
2257
|
+
const rawPerp = cross(tangent, viewDir);
|
|
2258
|
+
const perpLen = length(rawPerp);
|
|
2259
|
+
const camRight = vec3(
|
|
2260
|
+
cameraViewMatrix.element(0).element(0),
|
|
2261
|
+
cameraViewMatrix.element(1).element(0),
|
|
2262
|
+
cameraViewMatrix.element(2).element(0)
|
|
2263
|
+
);
|
|
2264
|
+
const camRightDotTangent = dot(camRight, tangent);
|
|
2265
|
+
const fallbackPerp = normalize(
|
|
2266
|
+
camRight.sub(tangent.mul(camRightDotTangent))
|
|
2267
|
+
);
|
|
2268
|
+
const perp = normalize(
|
|
2269
|
+
perpLen.lessThan(1e-4).select(
|
|
2270
|
+
fallbackPerp,
|
|
2271
|
+
normalize(
|
|
2272
|
+
mix(
|
|
2273
|
+
fallbackPerp,
|
|
2274
|
+
normalize(rawPerp),
|
|
2275
|
+
smoothstep(float(0), float(0.7), perpLen)
|
|
2276
|
+
)
|
|
2277
|
+
)
|
|
2278
|
+
)
|
|
2279
|
+
);
|
|
2280
|
+
const offsetPos = current.add(perp.mul(aTrailOffset).mul(aTrailHalfWidth));
|
|
2281
|
+
const mvOffset = modelViewMatrix.mul(vec4(offsetPos, 1));
|
|
2282
|
+
vViewZ.assign(mvOffset.z.negate());
|
|
2283
|
+
return offsetPos;
|
|
2284
|
+
})();
|
|
2285
|
+
const colorNode = Fn(() => {
|
|
2286
|
+
const outColor = vColor.toVar();
|
|
2287
|
+
const edgeDist = float(1).sub(abs(vUv.x.mul(2).sub(1)));
|
|
2288
|
+
const edgeFade = smoothstep(float(0), float(0.4), edgeDist);
|
|
2289
|
+
If(u.uUseMap.greaterThan(0.5), () => {
|
|
2290
|
+
const texColor = texture(u.uMap, vUv);
|
|
2291
|
+
const texBrightness = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
|
|
2292
|
+
outColor.rgb.assign(
|
|
2293
|
+
outColor.rgb.mul(float(0.5).add(texBrightness.mul(0.5)))
|
|
2294
|
+
);
|
|
2295
|
+
outColor.a.assign(outColor.a.mul(texColor.a));
|
|
2296
|
+
});
|
|
2297
|
+
outColor.a.assign(outColor.a.mul(vAlpha).mul(edgeFade));
|
|
2298
|
+
Discard(outColor.a.lessThan(ALPHA_DISCARD_THRESHOLD));
|
|
2299
|
+
If(u.uSoftEnabled.greaterThan(0.5), () => {
|
|
2300
|
+
const depthSample = texture(u.uSceneDepthTex, screenUV).x;
|
|
2301
|
+
const sceneDepthLinear = linearizeDepth({
|
|
2302
|
+
depthSample,
|
|
2303
|
+
near: u.uCameraNearFar.x,
|
|
2304
|
+
far: u.uCameraNearFar.y
|
|
2305
|
+
});
|
|
2306
|
+
const depthDiff = sceneDepthLinear.sub(vViewZ);
|
|
2307
|
+
const softFade = smoothstep(float(0), u.uSoftIntensity, depthDiff);
|
|
2308
|
+
outColor.a.assign(outColor.a.mul(softFade));
|
|
2309
|
+
});
|
|
2310
|
+
Discard(outColor.a.lessThan(ALPHA_DISCARD_THRESHOLD));
|
|
2311
|
+
const diff = vec3(
|
|
2312
|
+
outColor.r.sub(u.uBgColor.x),
|
|
2313
|
+
outColor.g.sub(u.uBgColor.y),
|
|
2314
|
+
outColor.b.sub(u.uBgColor.z)
|
|
2315
|
+
);
|
|
2316
|
+
Discard(
|
|
2317
|
+
u.uDiscardBg.greaterThan(0.5).and(abs(length(diff)).lessThan(u.uBgTolerance))
|
|
2318
|
+
);
|
|
2319
|
+
return outColor;
|
|
2320
|
+
})();
|
|
2321
|
+
const material = new MeshBasicNodeMaterial();
|
|
2322
|
+
material.transparent = rendererConfig.transparent;
|
|
2323
|
+
material.blending = rendererConfig.blending;
|
|
2324
|
+
material.depthTest = rendererConfig.depthTest;
|
|
2325
|
+
material.depthWrite = rendererConfig.depthWrite;
|
|
2326
|
+
material.toneMapped = false;
|
|
2327
|
+
material.fog = false;
|
|
2328
|
+
material.side = DoubleSide;
|
|
2329
|
+
material.positionNode = positionNode;
|
|
2330
|
+
material.colorNode = colorNode;
|
|
2331
|
+
return material;
|
|
2332
|
+
}
|
|
2333
|
+
function createTSLParticleMaterial(rendererType, sharedUniforms, rendererConfig, gpuCompute = false) {
|
|
2334
|
+
switch (rendererType) {
|
|
2335
|
+
case "INSTANCED" /* INSTANCED */:
|
|
2336
|
+
return createInstancedBillboardTSLMaterial(sharedUniforms, rendererConfig, gpuCompute);
|
|
2337
|
+
case "MESH" /* MESH */:
|
|
2338
|
+
return createMeshParticleTSLMaterial(sharedUniforms, rendererConfig, gpuCompute);
|
|
2339
|
+
case "POINTS" /* POINTS */:
|
|
2340
|
+
default:
|
|
2341
|
+
return createPointSpriteTSLMaterial(sharedUniforms, rendererConfig, gpuCompute);
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
function createTSLTrailMaterial(trailUniforms, rendererConfig) {
|
|
2345
|
+
return createTrailRibbonTSLMaterial(trailUniforms, rendererConfig);
|
|
2346
|
+
}
|
|
2347
|
+
var pair = (v) => {
|
|
2348
|
+
if (typeof v === "number") return [v, v];
|
|
2349
|
+
if (v && typeof v === "object") {
|
|
2350
|
+
const o = v;
|
|
2351
|
+
return [Number(o.min) || 0, Number(o.max) || 0];
|
|
2352
|
+
}
|
|
2353
|
+
return [0, 0];
|
|
2354
|
+
};
|
|
2355
|
+
var shapeKindOf = (t) => {
|
|
2356
|
+
switch (t) {
|
|
2357
|
+
case "SPHERE":
|
|
2358
|
+
return 0;
|
|
2359
|
+
case "CONE":
|
|
2360
|
+
return 1;
|
|
2361
|
+
case "CIRCLE":
|
|
2362
|
+
return 2;
|
|
2363
|
+
case "RECTANGLE":
|
|
2364
|
+
return 3;
|
|
2365
|
+
case "BOX":
|
|
2366
|
+
return 4;
|
|
2367
|
+
default:
|
|
2368
|
+
return 0;
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
var boxEmitFromOf = (e) => e === "SHELL" ? 1 : e === "EDGE" ? 2 : 0;
|
|
2372
|
+
function encodeShapeEmitParams(normalizedConfig, particleSystemId) {
|
|
2373
|
+
const bakedCurves = bakeParticleSystemCurves(normalizedConfig, particleSystemId);
|
|
2374
|
+
const pairLocal = pair;
|
|
2375
|
+
const [lifeMin, lifeMax] = pairLocal(normalizedConfig.startLifetime);
|
|
2376
|
+
const [spdMin, spdMax] = pairLocal(normalizedConfig.startSpeed);
|
|
2377
|
+
const [szMin, szMax] = pairLocal(normalizedConfig.startSize);
|
|
2378
|
+
const [rotMin, rotMax] = pairLocal(normalizedConfig.startRotation);
|
|
2379
|
+
const [opMin, opMax] = pairLocal(normalizedConfig.startOpacity);
|
|
2380
|
+
const cMin = normalizedConfig.startColor.min || { r: 1, g: 1, b: 1 };
|
|
2381
|
+
const cMax = normalizedConfig.startColor.max || { r: 1, g: 1, b: 1 };
|
|
2382
|
+
const sf = normalizedConfig.textureSheetAnimation && normalizedConfig.textureSheetAnimation.startFrame || 0;
|
|
2383
|
+
const sfPair = pairLocal(sf);
|
|
2384
|
+
const shp = normalizedConfig.shape;
|
|
2385
|
+
const sph = shp.sphere;
|
|
2386
|
+
const cone = shp.cone;
|
|
2387
|
+
const circ = shp.circle;
|
|
2388
|
+
const rect = shp.rectangle;
|
|
2389
|
+
const bx = shp.box;
|
|
2390
|
+
const num = (v, fallback) => typeof v === "number" && Number.isFinite(v) ? v : fallback;
|
|
2391
|
+
const kind = shapeKindOf(shp.shape);
|
|
2392
|
+
return {
|
|
2393
|
+
shapeKind: kind,
|
|
2394
|
+
radius: shp.shape === "CONE" ? num(cone?.radius, 1) : shp.shape === "CIRCLE" ? num(circ?.radius, 1) : num(sph?.radius, 1),
|
|
2395
|
+
radiusThickness: shp.shape === "CONE" ? num(cone?.radiusThickness, 1) : shp.shape === "CIRCLE" ? num(circ?.radiusThickness, 1) : num(sph?.radiusThickness, 1),
|
|
2396
|
+
arcDeg: shp.shape === "CONE" ? num(cone?.arc, 360) : shp.shape === "CIRCLE" ? num(circ?.arc, 360) : num(sph?.arc, 360),
|
|
2397
|
+
coneAngleDeg: num(cone?.angle, 90),
|
|
2398
|
+
rectangleRotXDeg: num(rect?.rotation?.x, 0),
|
|
2399
|
+
rectangleRotYDeg: num(rect?.rotation?.y, 0),
|
|
2400
|
+
rectangleScaleX: num(rect?.scale?.x, 1),
|
|
2401
|
+
rectangleScaleY: num(rect?.scale?.y, 1),
|
|
2402
|
+
boxScaleX: num(bx?.scale?.x, 1),
|
|
2403
|
+
boxScaleY: num(bx?.scale?.y, 1),
|
|
2404
|
+
boxScaleZ: num(bx?.scale?.z, 1),
|
|
2405
|
+
boxEmitFrom: boxEmitFromOf(bx?.emitFrom),
|
|
2406
|
+
speedMin: spdMin,
|
|
2407
|
+
speedMax: spdMax,
|
|
2408
|
+
sizeMin: szMin,
|
|
2409
|
+
sizeMax: szMax,
|
|
2410
|
+
rotMin,
|
|
2411
|
+
rotMax,
|
|
2412
|
+
opacityMin: opMin,
|
|
2413
|
+
opacityMax: opMax,
|
|
2414
|
+
lifeMin,
|
|
2415
|
+
lifeMax,
|
|
2416
|
+
colorRMin: cMin.r,
|
|
2417
|
+
colorRMax: cMax.r,
|
|
2418
|
+
colorGMin: cMin.g,
|
|
2419
|
+
colorGMax: cMax.g,
|
|
2420
|
+
colorBMin: cMin.b,
|
|
2421
|
+
colorBMax: cMax.b,
|
|
2422
|
+
startFrameMin: sfPair[0],
|
|
2423
|
+
startFrameMax: sfPair[1],
|
|
2424
|
+
// Separate rotationOverLifetime range (never the startRotation pair).
|
|
2425
|
+
rotOverLifeMin: (() => {
|
|
2426
|
+
const rol = normalizedConfig.rotationOverLifetime;
|
|
2427
|
+
return typeof rol?.min === "number" && Number.isFinite(rol.min) ? rol.min : 0;
|
|
2428
|
+
})(),
|
|
2429
|
+
rotOverLifeMax: (() => {
|
|
2430
|
+
const rol = normalizedConfig.rotationOverLifetime;
|
|
2431
|
+
return typeof rol?.max === "number" && Number.isFinite(rol.max) ? rol.max : 0;
|
|
2432
|
+
})(),
|
|
2433
|
+
noiseOctaves: num(normalizedConfig.noise?.octaves, 1),
|
|
2434
|
+
noiseUseRandomOffset: !!normalizedConfig.noise?.useRandomOffset,
|
|
2435
|
+
rotationCurveActive: normalizedConfig.rotationOverLifetime.isActive,
|
|
2436
|
+
rotationalXCurve: bakedCurves.orbitalVelX ?? -1,
|
|
2437
|
+
rotationalYCurve: bakedCurves.orbitalVelY ?? -1,
|
|
2438
|
+
rotationalZCurve: bakedCurves.orbitalVelZ ?? -1,
|
|
2439
|
+
linearXCurve: bakedCurves.linearVelX ?? -1,
|
|
2440
|
+
linearYCurve: bakedCurves.linearVelY ?? -1,
|
|
2441
|
+
linearZCurve: bakedCurves.linearVelZ ?? -1
|
|
2442
|
+
};
|
|
2443
|
+
}
|
|
2444
|
+
function createComputePipeline(maxParticles, instanced, normalizedConfig, particleSystemId, forceFieldCount, collisionPlaneCount = 0, subFifos, trailDesc) {
|
|
2445
|
+
const bakedCurves = bakeParticleSystemCurves(normalizedConfig, particleSystemId);
|
|
2446
|
+
const v = normalizedConfig.velocityOverLifetime;
|
|
2447
|
+
const flags = {
|
|
2448
|
+
sizeOverLifetime: normalizedConfig.sizeOverLifetime.isActive,
|
|
2449
|
+
opacityOverLifetime: normalizedConfig.opacityOverLifetime.isActive,
|
|
2450
|
+
colorOverLifetime: normalizedConfig.colorOverLifetime.isActive,
|
|
2451
|
+
rotationOverLifetime: normalizedConfig.rotationOverLifetime.isActive,
|
|
2452
|
+
linearVelocity: v.isActive && (isLifeTimeCurve(v.linear.x ?? 0) || isLifeTimeCurve(v.linear.y ?? 0) || isLifeTimeCurve(v.linear.z ?? 0) || v.linear.x !== 0 || v.linear.y !== 0 || v.linear.z !== 0),
|
|
2453
|
+
orbitalVelocity: v.isActive && (isLifeTimeCurve(v.orbital.x ?? 0) || isLifeTimeCurve(v.orbital.y ?? 0) || isLifeTimeCurve(v.orbital.z ?? 0) || v.orbital.x !== 0 || v.orbital.y !== 0 || v.orbital.z !== 0),
|
|
2454
|
+
noise: normalizedConfig.noise.isActive,
|
|
2455
|
+
forceFields: forceFieldCount > 0,
|
|
2456
|
+
collisionPlanes: collisionPlaneCount > 0
|
|
2457
|
+
};
|
|
2458
|
+
const shapeParams = encodeShapeEmitParams(
|
|
2459
|
+
normalizedConfig,
|
|
2460
|
+
particleSystemId
|
|
2461
|
+
);
|
|
2462
|
+
const velocityValues = {
|
|
2463
|
+
linear: [v.linear.x, v.linear.y, v.linear.z],
|
|
2464
|
+
orbital: [
|
|
2465
|
+
v.orbital.x,
|
|
2466
|
+
v.orbital.y,
|
|
2467
|
+
v.orbital.z
|
|
2468
|
+
]
|
|
2469
|
+
};
|
|
2470
|
+
if (trailDesc && !trailDesc.meta) {
|
|
2471
|
+
trailDesc.meta = new StorageBufferAttribute(
|
|
2472
|
+
new Uint32Array(Math.max(1, maxParticles) * 2),
|
|
2473
|
+
1
|
|
2474
|
+
);
|
|
2475
|
+
}
|
|
2476
|
+
const built = createModifierStorageBuffers(
|
|
2477
|
+
maxParticles,
|
|
2478
|
+
instanced,
|
|
2479
|
+
bakedCurves.data,
|
|
2480
|
+
flags.forceFields,
|
|
2481
|
+
flags.collisionPlanes,
|
|
2482
|
+
trailDesc ? trailDesc.length : 0
|
|
2483
|
+
);
|
|
2484
|
+
if (trailDesc && built.buffers.trailMeta) {
|
|
2485
|
+
trailDesc.meta = built.buffers.trailMeta;
|
|
2486
|
+
}
|
|
2487
|
+
return createModifierComputeUpdate(
|
|
2488
|
+
built.buffers,
|
|
2489
|
+
maxParticles,
|
|
2490
|
+
bakedCurves,
|
|
2491
|
+
flags,
|
|
2492
|
+
shapeParams,
|
|
2493
|
+
forceFieldCount,
|
|
2494
|
+
collisionPlaneCount,
|
|
2495
|
+
subFifos ?? [],
|
|
2496
|
+
trailDesc,
|
|
2497
|
+
velocityValues
|
|
2498
|
+
);
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
// src/webgpu.ts
|
|
2502
|
+
function enableWebGPU(renderer) {
|
|
2503
|
+
const factory = {
|
|
2504
|
+
createTSLParticleMaterial,
|
|
2505
|
+
createTSLTrailMaterial,
|
|
2506
|
+
createComputePipeline,
|
|
2507
|
+
encodeForceFieldsForGPU,
|
|
2508
|
+
encodeCollisionPlanesForGPU,
|
|
2509
|
+
createSubEmitterFifoAttribute,
|
|
2510
|
+
createSubEmitterInitUpdate,
|
|
2511
|
+
createTrailRibbonUpdate,
|
|
2512
|
+
encodeShapeEmitParams
|
|
2513
|
+
};
|
|
2514
|
+
return registerTSLMaterialFactory(
|
|
2515
|
+
factory,
|
|
2516
|
+
renderer !== void 0 ? { renderer } : void 0
|
|
2517
|
+
);
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
export { CH, createComputePipeline, createModifierStorageBuffers, createSubEmitterFifoAttribute, createSubEmitterInitUpdate, createTSLParticleMaterial, createTSLTrailMaterial, createTrailRibbonUpdate, enableWebGPU, encodeCollisionPlanesForGPU, encodeForceFieldsForGPU, encodeShapeEmitParams, mixBirthSeed, nextSystemSeed, pcg01, pcgRawU32, randomChannel, subEmitterWindowSize };
|
|
2521
|
+
//# sourceMappingURL=webgpu.js.map
|
|
2522
|
+
//# sourceMappingURL=webgpu.js.map
|