@newkrok/three-particles 1.0.3 → 2.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.
Files changed (44) hide show
  1. package/README.md +28 -12
  2. package/dist/bundle-report.json +1 -0
  3. package/dist/index.d.ts +7 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +6 -0
  6. package/dist/js/effects/three-particles/index.d.ts +7 -0
  7. package/dist/js/effects/three-particles/index.d.ts.map +1 -0
  8. package/dist/js/effects/three-particles/index.js +6 -0
  9. package/dist/js/effects/three-particles/shaders/particle-system-fragment-shader.glsl.d.ts +3 -0
  10. package/dist/js/effects/three-particles/shaders/particle-system-fragment-shader.glsl.d.ts.map +1 -0
  11. package/{src → dist}/js/effects/three-particles/shaders/particle-system-fragment-shader.glsl.js +66 -67
  12. package/dist/js/effects/three-particles/shaders/particle-system-vertex-shader.glsl.d.ts +3 -0
  13. package/dist/js/effects/three-particles/shaders/particle-system-vertex-shader.glsl.d.ts.map +1 -0
  14. package/{src → dist}/js/effects/three-particles/shaders/particle-system-vertex-shader.glsl.js +32 -33
  15. package/dist/js/effects/three-particles/three-particles-bezier.d.ts +5 -0
  16. package/dist/js/effects/three-particles/three-particles-bezier.d.ts.map +1 -0
  17. package/dist/js/effects/three-particles/three-particles-bezier.js +62 -0
  18. package/dist/js/effects/three-particles/three-particles-curves.d.ts +37 -0
  19. package/dist/js/effects/three-particles/three-particles-curves.d.ts.map +1 -0
  20. package/dist/js/effects/three-particles/three-particles-curves.js +37 -0
  21. package/dist/js/effects/three-particles/three-particles-enums.d.ts +25 -0
  22. package/dist/js/effects/three-particles/three-particles-enums.d.ts.map +1 -0
  23. package/dist/js/effects/three-particles/three-particles-enums.js +1 -0
  24. package/dist/js/effects/three-particles/three-particles-modifiers.d.ts +11 -0
  25. package/dist/js/effects/three-particles/three-particles-modifiers.d.ts.map +1 -0
  26. package/dist/js/effects/three-particles/three-particles-modifiers.js +93 -0
  27. package/dist/js/effects/three-particles/three-particles-utils.d.ts +36 -0
  28. package/dist/js/effects/three-particles/three-particles-utils.d.ts.map +1 -0
  29. package/dist/js/effects/three-particles/three-particles-utils.js +179 -0
  30. package/dist/js/effects/three-particles/three-particles.d.ts +13 -0
  31. package/dist/js/effects/three-particles/three-particles.d.ts.map +1 -0
  32. package/dist/js/effects/three-particles/three-particles.js +643 -0
  33. package/dist/js/effects/three-particles/types.d.ts +1037 -0
  34. package/dist/js/effects/three-particles/types.d.ts.map +1 -0
  35. package/dist/js/effects/three-particles/types.js +1 -0
  36. package/dist/three-particles.min.js +1 -0
  37. package/package.json +87 -37
  38. package/src/js/effects/three-particles/three-particles-bezier.js +0 -36
  39. package/src/js/effects/three-particles/three-particles-curves.js +0 -75
  40. package/src/js/effects/three-particles/three-particles-enums.js +0 -23
  41. package/src/js/effects/three-particles/three-particles-modifiers.js +0 -133
  42. package/src/js/effects/three-particles/three-particles-utils.js +0 -212
  43. package/src/js/effects/three-particles.d.ts +0 -138
  44. package/src/js/effects/three-particles.js +0 -931
@@ -0,0 +1,643 @@
1
+ import { ObjectUtils } from '@newkrok/three-utils';
2
+ import * as THREE from 'three';
3
+ import { Gyroscope } from 'three/examples/jsm/misc/Gyroscope.js';
4
+ import { FBM } from 'three-noise/build/three-noise.module.js';
5
+ import ParticleSystemFragmentShader from './shaders/particle-system-fragment-shader.glsl.js';
6
+ import ParticleSystemVertexShader from './shaders/particle-system-vertex-shader.glsl.js';
7
+ import { removeBezierCurveFunction } from './three-particles-bezier.js';
8
+ import { applyModifiers } from './three-particles-modifiers.js';
9
+ import { calculateRandomPositionAndVelocityOnBox, calculateRandomPositionAndVelocityOnCircle, calculateRandomPositionAndVelocityOnCone, calculateRandomPositionAndVelocityOnRectangle, calculateRandomPositionAndVelocityOnSphere, calculateValue, getCurveFunctionFromConfig, isLifeTimeCurve, createDefaultParticleTexture, } from './three-particles-utils.js';
10
+ export * from './types.js';
11
+ let _particleSystemId = 0;
12
+ let createdParticleSystems = [];
13
+ export const blendingMap = {
14
+ 'THREE.NoBlending': THREE.NoBlending,
15
+ 'THREE.NormalBlending': THREE.NormalBlending,
16
+ 'THREE.AdditiveBlending': THREE.AdditiveBlending,
17
+ 'THREE.SubtractiveBlending': THREE.SubtractiveBlending,
18
+ 'THREE.MultiplyBlending': THREE.MultiplyBlending,
19
+ };
20
+ export const getDefaultParticleSystemConfig = () => JSON.parse(JSON.stringify(DEFAULT_PARTICLE_SYSTEM_CONFIG));
21
+ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
22
+ transform: {
23
+ position: new THREE.Vector3(),
24
+ rotation: new THREE.Vector3(),
25
+ scale: new THREE.Vector3(1, 1, 1),
26
+ },
27
+ duration: 5.0,
28
+ looping: true,
29
+ startDelay: 0,
30
+ startLifetime: 5.0,
31
+ startSpeed: 1.0,
32
+ startSize: 1.0,
33
+ startOpacity: 1.0,
34
+ startRotation: 0.0,
35
+ startColor: {
36
+ min: { r: 1.0, g: 1.0, b: 1.0 },
37
+ max: { r: 1.0, g: 1.0, b: 1.0 },
38
+ },
39
+ gravity: 0.0,
40
+ simulationSpace: "LOCAL" /* SimulationSpace.LOCAL */,
41
+ maxParticles: 100.0,
42
+ emission: {
43
+ rateOverTime: 10.0,
44
+ rateOverDistance: 0.0,
45
+ },
46
+ shape: {
47
+ shape: "SPHERE" /* Shape.SPHERE */,
48
+ sphere: {
49
+ radius: 1.0,
50
+ radiusThickness: 1.0,
51
+ arc: 360.0,
52
+ },
53
+ cone: {
54
+ angle: 25.0,
55
+ radius: 1.0,
56
+ radiusThickness: 1.0,
57
+ arc: 360.0,
58
+ },
59
+ circle: {
60
+ radius: 1.0,
61
+ radiusThickness: 1.0,
62
+ arc: 360.0,
63
+ },
64
+ rectangle: {
65
+ rotation: { x: 0.0, y: 0.0 }, // TODO: add z rotation
66
+ scale: { x: 1.0, y: 1.0 },
67
+ },
68
+ box: {
69
+ scale: { x: 1.0, y: 1.0, z: 1.0 },
70
+ emitFrom: "VOLUME" /* EmitFrom.VOLUME */,
71
+ },
72
+ },
73
+ map: undefined,
74
+ renderer: {
75
+ blending: THREE.NormalBlending,
76
+ discardBackgroundColor: false,
77
+ backgroundColorTolerance: 1.0,
78
+ backgroundColor: { r: 1.0, g: 1.0, b: 1.0 },
79
+ transparent: true,
80
+ depthTest: true,
81
+ depthWrite: false,
82
+ },
83
+ velocityOverLifetime: {
84
+ isActive: false,
85
+ linear: {
86
+ x: 0,
87
+ y: 0,
88
+ z: 0,
89
+ },
90
+ orbital: {
91
+ x: 0,
92
+ y: 0,
93
+ z: 0,
94
+ },
95
+ },
96
+ sizeOverLifetime: {
97
+ isActive: false,
98
+ lifetimeCurve: {
99
+ type: "BEZIER" /* LifeTimeCurve.BEZIER */,
100
+ scale: 1,
101
+ bezierPoints: [
102
+ { x: 0, y: 0, percentage: 0 },
103
+ { x: 1, y: 1, percentage: 1 },
104
+ ],
105
+ },
106
+ },
107
+ /* colorOverLifetime: {
108
+ isActive: false,
109
+ lifetimeCurve: {
110
+ type: LifeTimeCurve.EASING,
111
+ scale: 1,
112
+ curveFunction: CurveFunctionId.LINEAR,
113
+ },
114
+ }, */
115
+ opacityOverLifetime: {
116
+ isActive: false,
117
+ lifetimeCurve: {
118
+ type: "BEZIER" /* LifeTimeCurve.BEZIER */,
119
+ scale: 1,
120
+ bezierPoints: [
121
+ { x: 0, y: 0, percentage: 0 },
122
+ { x: 1, y: 1, percentage: 1 },
123
+ ],
124
+ },
125
+ },
126
+ rotationOverLifetime: {
127
+ isActive: false,
128
+ min: 0.0,
129
+ max: 0.0,
130
+ },
131
+ noise: {
132
+ isActive: false,
133
+ useRandomOffset: false,
134
+ strength: 1.0,
135
+ frequency: 0.5,
136
+ octaves: 1,
137
+ positionAmount: 1.0,
138
+ rotationAmount: 0.0,
139
+ sizeAmount: 0.0,
140
+ },
141
+ textureSheetAnimation: {
142
+ tiles: new THREE.Vector2(1.0, 1.0),
143
+ timeMode: "LIFETIME" /* TimeMode.LIFETIME */,
144
+ fps: 30.0,
145
+ startFrame: 0,
146
+ },
147
+ };
148
+ const createFloat32Attributes = ({ geometry, propertyName, maxParticles, factory, }) => {
149
+ geometry.setAttribute(propertyName, new THREE.BufferAttribute(new Float32Array(Array.from({ length: maxParticles }, typeof factory === 'function' ? factory : () => factory)), 1));
150
+ };
151
+ const calculatePositionAndVelocity = (generalData, { shape, sphere, cone, circle, rectangle, box }, startSpeed, position, velocity) => {
152
+ const calculatedStartSpeed = calculateValue(generalData.particleSystemId, startSpeed, generalData.normalizedLifetimePercentage);
153
+ switch (shape) {
154
+ case "SPHERE" /* Shape.SPHERE */:
155
+ calculateRandomPositionAndVelocityOnSphere(position, generalData.wrapperQuaternion, velocity, calculatedStartSpeed, sphere);
156
+ break;
157
+ case "CONE" /* Shape.CONE */:
158
+ calculateRandomPositionAndVelocityOnCone(position, generalData.wrapperQuaternion, velocity, calculatedStartSpeed, cone);
159
+ break;
160
+ case "CIRCLE" /* Shape.CIRCLE */:
161
+ calculateRandomPositionAndVelocityOnCircle(position, generalData.wrapperQuaternion, velocity, calculatedStartSpeed, circle);
162
+ break;
163
+ case "RECTANGLE" /* Shape.RECTANGLE */:
164
+ calculateRandomPositionAndVelocityOnRectangle(position, generalData.wrapperQuaternion, velocity, calculatedStartSpeed, rectangle);
165
+ break;
166
+ case "BOX" /* Shape.BOX */:
167
+ calculateRandomPositionAndVelocityOnBox(position, generalData.wrapperQuaternion, velocity, calculatedStartSpeed, box);
168
+ break;
169
+ }
170
+ };
171
+ const destroyParticleSystem = (particleSystem) => {
172
+ createdParticleSystems = createdParticleSystems.filter(({ particleSystem: savedParticleSystem, wrapper, generalData: { particleSystemId }, }) => {
173
+ if (savedParticleSystem !== particleSystem &&
174
+ wrapper !== particleSystem) {
175
+ return true;
176
+ }
177
+ removeBezierCurveFunction(particleSystemId);
178
+ savedParticleSystem.geometry.dispose();
179
+ if (Array.isArray(savedParticleSystem.material))
180
+ savedParticleSystem.material.forEach((material) => material.dispose());
181
+ else
182
+ savedParticleSystem.material.dispose();
183
+ if (savedParticleSystem.parent)
184
+ savedParticleSystem.parent.remove(savedParticleSystem);
185
+ return false;
186
+ });
187
+ };
188
+ export const createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow) => {
189
+ const now = externalNow || Date.now();
190
+ const generalData = {
191
+ particleSystemId: _particleSystemId++,
192
+ normalizedLifetimePercentage: 0,
193
+ distanceFromLastEmitByDistance: 0,
194
+ lastWorldPosition: new THREE.Vector3(-99999),
195
+ currentWorldPosition: new THREE.Vector3(-99999),
196
+ worldPositionChange: new THREE.Vector3(),
197
+ worldQuaternion: new THREE.Quaternion(),
198
+ wrapperQuaternion: new THREE.Quaternion(),
199
+ lastWorldQuaternion: new THREE.Quaternion(-99999),
200
+ worldEuler: new THREE.Euler(),
201
+ gravityVelocity: new THREE.Vector3(0, 0, 0),
202
+ startValues: {},
203
+ linearVelocityData: undefined,
204
+ orbitalVelocityData: undefined,
205
+ lifetimeValues: {},
206
+ creationTimes: [],
207
+ noise: {
208
+ isActive: false,
209
+ strength: 0,
210
+ positionAmount: 0,
211
+ rotationAmount: 0,
212
+ sizeAmount: 0,
213
+ },
214
+ isEnabled: true,
215
+ };
216
+ const normalizedConfig = ObjectUtils.patchObject(DEFAULT_PARTICLE_SYSTEM_CONFIG, config);
217
+ let particleMap = normalizedConfig.map || createDefaultParticleTexture();
218
+ const { transform, duration, looping, startDelay, startLifetime, startSpeed, startSize, startRotation, startColor, startOpacity, gravity, simulationSpace, maxParticles, emission, shape, renderer, noise, velocityOverLifetime, onUpdate, onComplete, textureSheetAnimation, } = normalizedConfig;
219
+ if (typeof renderer?.blending === 'string')
220
+ renderer.blending = blendingMap[renderer.blending];
221
+ const startPositions = Array.from({ length: maxParticles }, () => new THREE.Vector3());
222
+ const velocities = Array.from({ length: maxParticles }, () => new THREE.Vector3());
223
+ generalData.creationTimes = Array.from({ length: maxParticles }, () => 0);
224
+ if (velocityOverLifetime.isActive) {
225
+ generalData.linearVelocityData = Array.from({ length: maxParticles }, () => ({
226
+ speed: new THREE.Vector3(velocityOverLifetime.linear.x
227
+ ? calculateValue(generalData.particleSystemId, velocityOverLifetime.linear.x, 0)
228
+ : 0, velocityOverLifetime.linear.y
229
+ ? calculateValue(generalData.particleSystemId, velocityOverLifetime.linear.y, 0)
230
+ : 0, velocityOverLifetime.linear.z
231
+ ? calculateValue(generalData.particleSystemId, velocityOverLifetime.linear.z, 0)
232
+ : 0),
233
+ valueModifiers: {
234
+ x: isLifeTimeCurve(velocityOverLifetime.linear.x || 0)
235
+ ? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.linear.x)
236
+ : undefined,
237
+ y: isLifeTimeCurve(velocityOverLifetime.linear.y || 0)
238
+ ? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.linear.y)
239
+ : undefined,
240
+ z: isLifeTimeCurve(velocityOverLifetime.linear.z || 0)
241
+ ? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.linear.z)
242
+ : undefined,
243
+ },
244
+ }));
245
+ generalData.orbitalVelocityData = Array.from({ length: maxParticles }, () => ({
246
+ speed: new THREE.Vector3(velocityOverLifetime.orbital.x
247
+ ? calculateValue(generalData.particleSystemId, velocityOverLifetime.orbital.x, 0)
248
+ : 0, velocityOverLifetime.orbital.y
249
+ ? calculateValue(generalData.particleSystemId, velocityOverLifetime.orbital.y, 0)
250
+ : 0, velocityOverLifetime.orbital.z
251
+ ? calculateValue(generalData.particleSystemId, velocityOverLifetime.orbital.z, 0)
252
+ : 0),
253
+ valueModifiers: {
254
+ x: isLifeTimeCurve(velocityOverLifetime.orbital.x || 0)
255
+ ? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.orbital.x)
256
+ : undefined,
257
+ y: isLifeTimeCurve(velocityOverLifetime.orbital.y || 0)
258
+ ? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.orbital.y)
259
+ : undefined,
260
+ z: isLifeTimeCurve(velocityOverLifetime.orbital.z || 0)
261
+ ? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.orbital.z)
262
+ : undefined,
263
+ },
264
+ positionOffset: new THREE.Vector3(),
265
+ }));
266
+ }
267
+ const startValueKeys = [
268
+ 'startSize',
269
+ 'startOpacity',
270
+ ];
271
+ startValueKeys.forEach((key) => {
272
+ generalData.startValues[key] = Array.from({ length: maxParticles }, () => calculateValue(generalData.particleSystemId, normalizedConfig[key], 0));
273
+ });
274
+ const lifetimeValueKeys = [
275
+ 'rotationOverLifetime',
276
+ ];
277
+ lifetimeValueKeys.forEach((key) => {
278
+ const value = normalizedConfig[key];
279
+ if (value.isActive)
280
+ generalData.lifetimeValues[key] = Array.from({ length: maxParticles }, () => THREE.MathUtils.randFloat(value.min, value.max));
281
+ });
282
+ generalData.noise = {
283
+ isActive: noise.isActive,
284
+ strength: noise.strength,
285
+ positionAmount: noise.positionAmount,
286
+ rotationAmount: noise.rotationAmount,
287
+ sizeAmount: noise.sizeAmount,
288
+ sampler: noise.isActive
289
+ ? new FBM({
290
+ seed: Math.random(),
291
+ scale: noise.frequency,
292
+ octaves: noise.octaves,
293
+ })
294
+ : undefined,
295
+ offsets: noise.useRandomOffset
296
+ ? Array.from({ length: maxParticles }, () => Math.random() * 100)
297
+ : undefined,
298
+ };
299
+ const material = new THREE.ShaderMaterial({
300
+ uniforms: {
301
+ elapsed: {
302
+ value: 0.0,
303
+ },
304
+ map: {
305
+ value: particleMap,
306
+ },
307
+ tiles: {
308
+ value: textureSheetAnimation.tiles,
309
+ },
310
+ fps: {
311
+ value: textureSheetAnimation.fps,
312
+ },
313
+ useFPSForFrameIndex: {
314
+ value: textureSheetAnimation.timeMode === "FPS" /* TimeMode.FPS */,
315
+ },
316
+ backgroundColor: {
317
+ value: renderer.backgroundColor,
318
+ },
319
+ discardBackgroundColor: {
320
+ value: renderer.discardBackgroundColor,
321
+ },
322
+ backgroundColorTolerance: {
323
+ value: renderer.backgroundColorTolerance,
324
+ },
325
+ },
326
+ vertexShader: ParticleSystemVertexShader,
327
+ fragmentShader: ParticleSystemFragmentShader,
328
+ transparent: renderer.transparent,
329
+ blending: renderer.blending,
330
+ depthTest: renderer.depthTest,
331
+ depthWrite: renderer.depthWrite,
332
+ });
333
+ const geometry = new THREE.BufferGeometry();
334
+ for (let i = 0; i < maxParticles; i++)
335
+ calculatePositionAndVelocity(generalData, shape, startSpeed, startPositions[i], velocities[i]);
336
+ geometry.setFromPoints(Array.from({ length: maxParticles }, (_, index) => startPositions[index].clone()));
337
+ const createFloat32AttributesRequest = (propertyName, factory) => {
338
+ createFloat32Attributes({
339
+ geometry,
340
+ propertyName,
341
+ maxParticles,
342
+ factory,
343
+ });
344
+ };
345
+ createFloat32AttributesRequest('isActive', 0);
346
+ createFloat32AttributesRequest('lifetime', 0);
347
+ createFloat32AttributesRequest('startLifetime', () => calculateValue(generalData.particleSystemId, startLifetime, 0) * 1000);
348
+ createFloat32AttributesRequest('startFrame', () => textureSheetAnimation.startFrame
349
+ ? calculateValue(generalData.particleSystemId, textureSheetAnimation.startFrame, 0)
350
+ : 0);
351
+ createFloat32AttributesRequest('opacity', () => calculateValue(generalData.particleSystemId, startOpacity, 0));
352
+ createFloat32AttributesRequest('rotation', () => calculateValue(generalData.particleSystemId, startRotation, 0));
353
+ createFloat32AttributesRequest('size', (_, index) => generalData.startValues.startSize[index]);
354
+ createFloat32AttributesRequest('rotation', 0);
355
+ const colorRandomRatio = Math.random();
356
+ createFloat32AttributesRequest('colorR', () => startColor.min.r +
357
+ colorRandomRatio * (startColor.max.r - startColor.min.r));
358
+ createFloat32AttributesRequest('colorG', () => startColor.min.g +
359
+ colorRandomRatio * (startColor.max.g - startColor.min.g));
360
+ createFloat32AttributesRequest('colorB', () => startColor.min.b +
361
+ colorRandomRatio * (startColor.max.b - startColor.min.b));
362
+ createFloat32AttributesRequest('colorA', 0);
363
+ const deactivateParticle = (particleIndex) => {
364
+ geometry.attributes.isActive.array[particleIndex] = 0;
365
+ geometry.attributes.colorA.array[particleIndex] = 0;
366
+ geometry.attributes.colorA.needsUpdate = true;
367
+ };
368
+ const activateParticle = ({ particleIndex, activationTime, position, }) => {
369
+ geometry.attributes.isActive.array[particleIndex] = 1;
370
+ generalData.creationTimes[particleIndex] = activationTime;
371
+ if (generalData.noise.offsets)
372
+ generalData.noise.offsets[particleIndex] = Math.random() * 100;
373
+ const colorRandomRatio = Math.random();
374
+ geometry.attributes.colorR.array[particleIndex] =
375
+ startColor.min.r +
376
+ colorRandomRatio * (startColor.max.r - startColor.min.r);
377
+ geometry.attributes.colorR.needsUpdate = true;
378
+ geometry.attributes.colorG.array[particleIndex] =
379
+ startColor.min.g +
380
+ colorRandomRatio * (startColor.max.g - startColor.min.g);
381
+ geometry.attributes.colorG.needsUpdate = true;
382
+ geometry.attributes.colorB.array[particleIndex] =
383
+ startColor.min.b +
384
+ colorRandomRatio * (startColor.max.b - startColor.min.b);
385
+ geometry.attributes.colorB.needsUpdate = true;
386
+ geometry.attributes.startFrame.array[particleIndex] =
387
+ textureSheetAnimation.startFrame
388
+ ? calculateValue(generalData.particleSystemId, textureSheetAnimation.startFrame, 0)
389
+ : 0;
390
+ geometry.attributes.startFrame.needsUpdate = true;
391
+ geometry.attributes.startLifetime.array[particleIndex] =
392
+ calculateValue(generalData.particleSystemId, startLifetime, generalData.normalizedLifetimePercentage) * 1000;
393
+ geometry.attributes.startLifetime.needsUpdate = true;
394
+ generalData.startValues.startSize[particleIndex] = calculateValue(generalData.particleSystemId, startSize, generalData.normalizedLifetimePercentage);
395
+ geometry.attributes.size.array[particleIndex] =
396
+ generalData.startValues.startSize[particleIndex];
397
+ geometry.attributes.size.needsUpdate = true;
398
+ generalData.startValues.startOpacity[particleIndex] = calculateValue(generalData.particleSystemId, startOpacity, generalData.normalizedLifetimePercentage);
399
+ geometry.attributes.colorA.array[particleIndex] =
400
+ generalData.startValues.startOpacity[particleIndex];
401
+ geometry.attributes.colorA.needsUpdate = true;
402
+ geometry.attributes.rotation.array[particleIndex] = calculateValue(generalData.particleSystemId, startRotation, generalData.normalizedLifetimePercentage);
403
+ geometry.attributes.rotation.needsUpdate = true;
404
+ if (normalizedConfig.rotationOverLifetime.isActive)
405
+ generalData.lifetimeValues.rotationOverLifetime[particleIndex] =
406
+ THREE.MathUtils.randFloat(normalizedConfig.rotationOverLifetime.min, normalizedConfig.rotationOverLifetime.max);
407
+ calculatePositionAndVelocity(generalData, shape, startSpeed, startPositions[particleIndex], velocities[particleIndex]);
408
+ const positionIndex = Math.floor(particleIndex * 3);
409
+ geometry.attributes.position.array[positionIndex] =
410
+ position.x + startPositions[particleIndex].x;
411
+ geometry.attributes.position.array[positionIndex + 1] =
412
+ position.y + startPositions[particleIndex].y;
413
+ geometry.attributes.position.array[positionIndex + 2] =
414
+ position.z + startPositions[particleIndex].z;
415
+ geometry.attributes.position.needsUpdate = true;
416
+ if (generalData.linearVelocityData) {
417
+ generalData.linearVelocityData[particleIndex].speed.set(normalizedConfig.velocityOverLifetime.linear.x
418
+ ? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.linear.x, 0)
419
+ : 0, normalizedConfig.velocityOverLifetime.linear.y
420
+ ? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.linear.y, 0)
421
+ : 0, normalizedConfig.velocityOverLifetime.linear.z
422
+ ? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.linear.z, 0)
423
+ : 0);
424
+ }
425
+ if (generalData.orbitalVelocityData) {
426
+ generalData.orbitalVelocityData[particleIndex].speed.set(normalizedConfig.velocityOverLifetime.orbital.x
427
+ ? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.orbital.x, 0)
428
+ : 0, normalizedConfig.velocityOverLifetime.orbital.y
429
+ ? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.orbital.y, 0)
430
+ : 0, normalizedConfig.velocityOverLifetime.orbital.z
431
+ ? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.orbital.z, 0)
432
+ : 0);
433
+ generalData.orbitalVelocityData[particleIndex].positionOffset.set(startPositions[particleIndex].x, startPositions[particleIndex].y, startPositions[particleIndex].z);
434
+ }
435
+ geometry.attributes.lifetime.array[particleIndex] = 0;
436
+ geometry.attributes.lifetime.needsUpdate = true;
437
+ applyModifiers({
438
+ delta: 0,
439
+ generalData,
440
+ normalizedConfig,
441
+ attributes: particleSystem.geometry.attributes,
442
+ particleLifetimePercentage: 0,
443
+ particleIndex,
444
+ });
445
+ };
446
+ let particleSystem = new THREE.Points(geometry, material);
447
+ particleSystem.position.copy(transform.position);
448
+ particleSystem.rotation.x = THREE.MathUtils.degToRad(transform.rotation.x);
449
+ particleSystem.rotation.y = THREE.MathUtils.degToRad(transform.rotation.y);
450
+ particleSystem.rotation.z = THREE.MathUtils.degToRad(transform.rotation.z);
451
+ particleSystem.scale.copy(transform.scale);
452
+ const calculatedCreationTime = now + calculateValue(generalData.particleSystemId, startDelay) * 1000;
453
+ let wrapper;
454
+ if (normalizedConfig.simulationSpace === "WORLD" /* SimulationSpace.WORLD */) {
455
+ wrapper = new Gyroscope();
456
+ wrapper.add(particleSystem);
457
+ }
458
+ createdParticleSystems.push({
459
+ particleSystem,
460
+ wrapper,
461
+ generalData,
462
+ onUpdate,
463
+ onComplete,
464
+ creationTime: calculatedCreationTime,
465
+ lastEmissionTime: calculatedCreationTime,
466
+ duration,
467
+ looping,
468
+ simulationSpace,
469
+ gravity,
470
+ emission,
471
+ normalizedConfig,
472
+ iterationCount: 0,
473
+ velocities,
474
+ deactivateParticle,
475
+ activateParticle,
476
+ });
477
+ const resumeEmitter = () => (generalData.isEnabled = true);
478
+ const pauseEmitter = () => (generalData.isEnabled = false);
479
+ const dispose = () => destroyParticleSystem(particleSystem);
480
+ return {
481
+ instance: wrapper || particleSystem,
482
+ resumeEmitter,
483
+ pauseEmitter,
484
+ dispose,
485
+ };
486
+ };
487
+ export const updateParticleSystems = ({ now, delta, elapsed }) => {
488
+ createdParticleSystems.forEach((props) => {
489
+ const { onUpdate, generalData, onComplete, particleSystem, wrapper, creationTime, lastEmissionTime, duration, looping, emission, normalizedConfig, iterationCount, velocities, deactivateParticle, activateParticle, simulationSpace, gravity, } = props;
490
+ const lifetime = now - creationTime;
491
+ const normalizedLifetime = lifetime % (duration * 1000);
492
+ generalData.normalizedLifetimePercentage = Math.max(Math.min(normalizedLifetime / (duration * 1000), 1), 0);
493
+ const { lastWorldPosition, currentWorldPosition, worldPositionChange, lastWorldQuaternion, worldQuaternion, worldEuler, gravityVelocity, isEnabled, } = generalData;
494
+ if (wrapper?.parent)
495
+ generalData.wrapperQuaternion.copy(wrapper.parent.quaternion);
496
+ const lastWorldPositionSnapshot = { ...lastWorldPosition };
497
+ if (Array.isArray(particleSystem.material))
498
+ particleSystem.material.forEach((material) => {
499
+ if (material instanceof THREE.ShaderMaterial)
500
+ material.uniforms.elapsed.value = elapsed;
501
+ });
502
+ else {
503
+ if (particleSystem.material instanceof THREE.ShaderMaterial)
504
+ particleSystem.material.uniforms.elapsed.value = elapsed;
505
+ }
506
+ particleSystem.getWorldPosition(currentWorldPosition);
507
+ if (lastWorldPosition.x !== -99999) {
508
+ worldPositionChange.set(currentWorldPosition.x - lastWorldPosition.x, currentWorldPosition.y - lastWorldPosition.y, currentWorldPosition.z - lastWorldPosition.z);
509
+ }
510
+ generalData.distanceFromLastEmitByDistance += worldPositionChange.length();
511
+ particleSystem.getWorldPosition(lastWorldPosition);
512
+ particleSystem.getWorldQuaternion(worldQuaternion);
513
+ if (lastWorldQuaternion.x === -99999 ||
514
+ lastWorldQuaternion.x !== worldQuaternion.x ||
515
+ lastWorldQuaternion.y !== worldQuaternion.y ||
516
+ lastWorldQuaternion.z !== worldQuaternion.z) {
517
+ worldEuler.setFromQuaternion(worldQuaternion);
518
+ lastWorldQuaternion.copy(worldQuaternion);
519
+ gravityVelocity.set(lastWorldPosition.x, lastWorldPosition.y + gravity, lastWorldPosition.z);
520
+ particleSystem.worldToLocal(gravityVelocity);
521
+ }
522
+ generalData.creationTimes.forEach((entry, index) => {
523
+ if (particleSystem.geometry.attributes.isActive.array[index]) {
524
+ const particleLifetime = now - entry;
525
+ if (particleLifetime >
526
+ particleSystem.geometry.attributes.startLifetime.array[index])
527
+ deactivateParticle(index);
528
+ else {
529
+ const velocity = velocities[index];
530
+ velocity.x -= gravityVelocity.x * delta;
531
+ velocity.y -= gravityVelocity.y * delta;
532
+ velocity.z -= gravityVelocity.z * delta;
533
+ if (gravity !== 0 ||
534
+ velocity.x !== 0 ||
535
+ velocity.y !== 0 ||
536
+ velocity.z !== 0 ||
537
+ worldPositionChange.x !== 0 ||
538
+ worldPositionChange.y !== 0 ||
539
+ worldPositionChange.z !== 0) {
540
+ const positionIndex = index * 3;
541
+ const positionArr = particleSystem.geometry.attributes.position.array;
542
+ if (simulationSpace === "WORLD" /* SimulationSpace.WORLD */) {
543
+ positionArr[positionIndex] -= worldPositionChange.x;
544
+ positionArr[positionIndex + 1] -= worldPositionChange.y;
545
+ positionArr[positionIndex + 2] -= worldPositionChange.z;
546
+ }
547
+ positionArr[positionIndex] += velocity.x * delta;
548
+ positionArr[positionIndex + 1] += velocity.y * delta;
549
+ positionArr[positionIndex + 2] += velocity.z * delta;
550
+ particleSystem.geometry.attributes.position.needsUpdate = true;
551
+ }
552
+ particleSystem.geometry.attributes.lifetime.array[index] =
553
+ particleLifetime;
554
+ particleSystem.geometry.attributes.lifetime.needsUpdate = true;
555
+ const particleLifetimePercentage = particleLifetime /
556
+ particleSystem.geometry.attributes.startLifetime.array[index];
557
+ applyModifiers({
558
+ delta,
559
+ generalData,
560
+ normalizedConfig,
561
+ attributes: particleSystem.geometry.attributes,
562
+ particleLifetimePercentage,
563
+ particleIndex: index,
564
+ });
565
+ }
566
+ }
567
+ });
568
+ if (isEnabled && (looping || lifetime < duration * 1000)) {
569
+ const emissionDelta = now - lastEmissionTime;
570
+ const neededParticlesByTime = emission.rateOverTime
571
+ ? Math.floor(calculateValue(generalData.particleSystemId, emission.rateOverTime, generalData.normalizedLifetimePercentage) *
572
+ (emissionDelta / 1000))
573
+ : 0;
574
+ const rateOverDistance = emission.rateOverDistance
575
+ ? calculateValue(generalData.particleSystemId, emission.rateOverDistance, generalData.normalizedLifetimePercentage)
576
+ : 0;
577
+ const neededParticlesByDistance = rateOverDistance > 0 && generalData.distanceFromLastEmitByDistance > 0
578
+ ? Math.floor(generalData.distanceFromLastEmitByDistance /
579
+ (1 / rateOverDistance))
580
+ : 0;
581
+ const distanceStep = neededParticlesByDistance > 0
582
+ ? {
583
+ x: (currentWorldPosition.x - lastWorldPositionSnapshot.x) /
584
+ neededParticlesByDistance,
585
+ y: (currentWorldPosition.y - lastWorldPositionSnapshot.y) /
586
+ neededParticlesByDistance,
587
+ z: (currentWorldPosition.z - lastWorldPositionSnapshot.z) /
588
+ neededParticlesByDistance,
589
+ }
590
+ : null;
591
+ const neededParticles = neededParticlesByTime + neededParticlesByDistance;
592
+ if (rateOverDistance > 0 && neededParticlesByDistance >= 1) {
593
+ generalData.distanceFromLastEmitByDistance = 0;
594
+ }
595
+ if (neededParticles > 0) {
596
+ let generatedParticlesByDistanceNeeds = 0;
597
+ for (let i = 0; i < neededParticles; i++) {
598
+ let particleIndex = -1;
599
+ particleSystem.geometry.attributes.isActive.array.find((isActive, index) => {
600
+ if (!isActive) {
601
+ particleIndex = index;
602
+ return true;
603
+ }
604
+ return false;
605
+ });
606
+ if (particleIndex !== -1 &&
607
+ particleIndex <
608
+ particleSystem.geometry.attributes.isActive.array.length) {
609
+ let position = { x: 0, y: 0, z: 0 };
610
+ if (distanceStep &&
611
+ generatedParticlesByDistanceNeeds < neededParticlesByDistance) {
612
+ position = {
613
+ x: distanceStep.x * generatedParticlesByDistanceNeeds,
614
+ y: distanceStep.y * generatedParticlesByDistanceNeeds,
615
+ z: distanceStep.z * generatedParticlesByDistanceNeeds,
616
+ };
617
+ generatedParticlesByDistanceNeeds++;
618
+ }
619
+ activateParticle({
620
+ particleIndex,
621
+ activationTime: now,
622
+ position,
623
+ });
624
+ props.lastEmissionTime = now;
625
+ }
626
+ }
627
+ }
628
+ if (onUpdate)
629
+ onUpdate({
630
+ particleSystem,
631
+ delta,
632
+ elapsed,
633
+ lifetime,
634
+ normalizedLifetime,
635
+ iterationCount: iterationCount + 1,
636
+ });
637
+ }
638
+ else if (onComplete)
639
+ onComplete({
640
+ particleSystem,
641
+ });
642
+ });
643
+ };