@newkrok/three-particles 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,4 +18,4 @@ Install with npm
18
18
  `npm i @newkrok/three-particles`
19
19
 
20
20
  Add as a package.json dependency
21
- `"dependencies": { ... "@newkrok/three-particles": "0.2.2" ... }, `
21
+ `"dependencies": { ... "@newkrok/three-particles": "0.5.0" ... }, `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newkrok/three-particles",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "description": "Particle system for ThreeJS",
5
5
  "main": "src/js/three-particles.js",
6
6
  "bin": {
@@ -23,8 +23,9 @@
23
23
  },
24
24
  "homepage": "https://github.com/NewKrok/three-particles#readme",
25
25
  "dependencies": {
26
- "three": "0.135.0",
27
- "easing-functions": "1.0.1"
26
+ "easing-functions": "1.0.1",
27
+ "three": "0.136.0",
28
+ "three-noise": "^1.1.1"
28
29
  },
29
30
  "scripts": {
30
31
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -0,0 +1,36 @@
1
+ const nCr = (n, k) => {
2
+ let z = 1;
3
+ for (let i = 1; i <= k; i++) z *= (n + 1 - i) / i;
4
+ return z;
5
+ };
6
+
7
+ export const createBezierCurveFunction = (bezierPoints) => (percentage) => {
8
+ if (percentage < 0) return bezierPoints[0];
9
+ if (percentage > 1) return bezierPoints[bezierPoints.length - 1];
10
+
11
+ let start = 0;
12
+ let stop = bezierPoints.length - 1;
13
+
14
+ bezierPoints.find((point, index) => {
15
+ const result = percentage < point.percentage;
16
+ if (result) stop = index;
17
+ else if (point.percentage !== undefined) start = index;
18
+ return result;
19
+ });
20
+
21
+ const n = stop - start;
22
+ const calculatedPercentage =
23
+ (percentage - bezierPoints[start].percentage) /
24
+ (bezierPoints[stop].percentage - bezierPoints[start].percentage);
25
+
26
+ let value = 0;
27
+ for (let i = 0; i <= n; i++) {
28
+ const p = bezierPoints[start + i];
29
+ const c =
30
+ nCr(n, i) *
31
+ Math.pow(1 - calculatedPercentage, n - i) *
32
+ Math.pow(calculatedPercentage, i);
33
+ value += c * p.y;
34
+ }
35
+ return value;
36
+ };
@@ -1,6 +1,7 @@
1
1
  import Easing from "easing-functions";
2
2
 
3
3
  export const CurveFunction = {
4
+ BEZIER: "BEZIER",
4
5
  LINEAR: "LINEAR",
5
6
  QUADRATIC_IN: "QUADRATIC_IN",
6
7
  QUADRATIC_OUT: "QUADRATIC_OUT",
@@ -1,6 +1,10 @@
1
+ import * as THREE from "three/build/three.module.js";
2
+
1
3
  import { getCurveFunction } from "./three-particles-curves.js";
2
4
 
3
- const modifiers = [
5
+ const noiseInput = new THREE.Vector3(0, 0, 0);
6
+
7
+ const curveModifiers = [
4
8
  // {key:"colorOverLifetime", attributeKeys:["colorR", "colorG", "colorB"]},
5
9
  {
6
10
  key: "opacityOverLifetime",
@@ -15,17 +19,20 @@ const modifiers = [
15
19
  ];
16
20
 
17
21
  export const applyModifiers = ({
22
+ delta,
23
+ noise,
18
24
  startValues,
25
+ lifetimeValues,
19
26
  normalizedConfig,
20
27
  attributes,
21
28
  particleLifetimePercentage,
22
29
  particleIndex,
23
30
  forceUpdate = false,
24
31
  }) => {
25
- modifiers.forEach(({ key, attributeKeys, startValueKeys }) => {
26
- const modifier = normalizedConfig[key];
27
- if (modifier.isActive) {
28
- const multiplier = getCurveFunction(modifier.curveFunction)(
32
+ curveModifiers.forEach(({ key, attributeKeys, startValueKeys }) => {
33
+ const curveModifier = normalizedConfig[key];
34
+ if (curveModifier.isActive) {
35
+ const multiplier = getCurveFunction(curveModifier.curveFunction)(
29
36
  particleLifetimePercentage
30
37
  );
31
38
  attributeKeys.forEach((attributeKey, index) => {
@@ -41,4 +48,58 @@ export const applyModifiers = ({
41
48
  });
42
49
  }
43
50
  });
51
+
52
+ if (lifetimeValues.rotationOverLifetime) {
53
+ attributes.rotation.array[particleIndex] +=
54
+ lifetimeValues.rotationOverLifetime[particleIndex] * delta * 0.02;
55
+ attributes.rotation.needsUpdate = true;
56
+ }
57
+
58
+ if (noise.isActive) {
59
+ const {
60
+ sampler,
61
+ strength,
62
+ offsets,
63
+ positionAmount,
64
+ rotationAmount,
65
+ sizeAmount,
66
+ } = noise;
67
+ const positionIndex = particleIndex * 3;
68
+ const positionArr = attributes.position.array;
69
+ let noiseOnPosition;
70
+
71
+ const noisePosition =
72
+ (particleLifetimePercentage + (offsets ? offsets[particleIndex] : 0)) *
73
+ 10 *
74
+ strength;
75
+ const noisePower = 0.15 * strength;
76
+
77
+ noiseInput.set(noisePosition, 0, 0);
78
+ noiseOnPosition = sampler.get3(noiseInput);
79
+ positionArr[positionIndex] += noiseOnPosition * noisePower * positionAmount;
80
+
81
+ if (rotationAmount !== 0) {
82
+ attributes.rotation.array[particleIndex] +=
83
+ noiseOnPosition * noisePower * rotationAmount;
84
+ attributes.rotation.needsUpdate = true;
85
+ }
86
+
87
+ if (sizeAmount !== 0) {
88
+ attributes.size.array[particleIndex] +=
89
+ noiseOnPosition * noisePower * sizeAmount;
90
+ attributes.size.needsUpdate = true;
91
+ }
92
+
93
+ noiseInput.set(noisePosition, noisePosition, 0);
94
+ noiseOnPosition = sampler.get3(noiseInput);
95
+ positionArr[positionIndex + 1] +=
96
+ noiseOnPosition * noisePower * positionAmount;
97
+
98
+ noiseInput.set(noisePosition, noisePosition, noisePosition);
99
+ noiseOnPosition = sampler.get3(noiseInput);
100
+ positionArr[positionIndex + 2] +=
101
+ noiseOnPosition * noisePower * positionAmount;
102
+
103
+ attributes.position.needsUpdate = true;
104
+ }
44
105
  };
@@ -8,10 +8,20 @@ export const patchObject = (
8
8
  const result = {};
9
9
  Object.keys(objectA).forEach((key) => {
10
10
  if (!config.skippedProperties || !config.skippedProperties.includes(key)) {
11
- if (typeof objectA[key] === "object" && objectA[key] && objectB[key]) {
11
+ if (
12
+ typeof objectA[key] === "object" &&
13
+ objectA[key] &&
14
+ objectB[key] &&
15
+ !Array.isArray(objectA[key])
16
+ ) {
12
17
  result[key] = patchObject(objectA[key], objectB[key], config);
13
18
  } else {
14
- result[key] = objectB[key] === 0 ? 0 : objectB[key] || objectA[key];
19
+ result[key] =
20
+ objectB[key] === 0
21
+ ? 0
22
+ : objectB[key] === false
23
+ ? false
24
+ : objectB[key] || objectA[key];
15
25
  if (config.applyToFirstObject) objectA[key] = result[key];
16
26
  }
17
27
  }
@@ -9,12 +9,11 @@ import {
9
9
  } from "./three-particles/three-particles-utils.js";
10
10
 
11
11
  import { CurveFunction } from "./three-particles/three-particles-curves.js";
12
+ import { FBM } from "three-noise/build/three-noise.module.js";
12
13
  import ParticleSystemFragmentShader from "./three-particles/shaders/particle-system-fragment-shader.glsl.js";
13
14
  import ParticleSystemVertexShader from "./three-particles/shaders/particle-system-vertex-shader.glsl.js";
14
15
  import { applyModifiers } from "./three-particles/three-particles-modifiers.js";
15
-
16
- // Float32Array is not enough accurate when we are storing timestamp in it so we just remove unnecessary time
17
- const float32Helper = 1638200000000;
16
+ import { createBezierCurveFunction } from "@newkrok/three-particles/src/js/effects/three-particles/three-particles-bezier";
18
17
 
19
18
  let createdParticleSystems = [];
20
19
 
@@ -36,6 +35,14 @@ export const TimeMode = {
36
35
  FPS: "FPS",
37
36
  };
38
37
 
38
+ export const blendingMap = {
39
+ "THREE.NoBlending": THREE.NoBlending,
40
+ "THREE.NormalBlending": THREE.NormalBlending,
41
+ "THREE.AdditiveBlending": THREE.AdditiveBlending,
42
+ "THREE.SubtractiveBlending": THREE.SubtractiveBlending,
43
+ "THREE.MultiplyBlending": THREE.MultiplyBlending,
44
+ };
45
+
39
46
  export const getDefaultParticleSystemConfig = () =>
40
47
  JSON.parse(JSON.stringify(DEFAULT_PARTICLE_SYSTEM_CONFIG));
41
48
 
@@ -88,9 +95,32 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
88
95
  },
89
96
  },
90
97
  map: null,
98
+ renderer: {
99
+ blending: THREE.NormalBlending,
100
+ transparent: true,
101
+ depthTest: true,
102
+ depthWrite: false,
103
+ },
104
+ velocityOverLifetime: {
105
+ isActive: false,
106
+ linear: {
107
+ x: { min: 0, max: 0 },
108
+ y: { min: 0, max: 0 },
109
+ z: { min: 0, max: 0 },
110
+ },
111
+ orbital: {
112
+ x: { min: 0, max: 0 },
113
+ y: { min: 0, max: 0 },
114
+ z: { min: 0, max: 0 },
115
+ },
116
+ },
91
117
  sizeOverLifetime: {
92
118
  isActive: false,
93
- curveFunction: CurveFunction.LINEAR,
119
+ curveFunction: CurveFunction.BEZIER,
120
+ bezierPoints: [
121
+ { x: 0, y: 0, percentage: 0 },
122
+ { x: 1, y: 1, percentage: 1 },
123
+ ],
94
124
  },
95
125
  /* colorOverLifetime: {
96
126
  isActive: false,
@@ -98,7 +128,26 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
98
128
  }, */
99
129
  opacityOverLifetime: {
100
130
  isActive: false,
101
- curveFunction: CurveFunction.LINEAR,
131
+ curveFunction: CurveFunction.BEZIER,
132
+ bezierPoints: [
133
+ { x: 0, y: 0, percentage: 0 },
134
+ { x: 1, y: 1, percentage: 1 },
135
+ ],
136
+ },
137
+ rotationOverLifetime: {
138
+ isActive: false,
139
+ min: 0.0,
140
+ max: 0.0,
141
+ },
142
+ noise: {
143
+ isActive: false,
144
+ useRandomOffset: false,
145
+ strength: 1.0,
146
+ frequency: 0.5,
147
+ octaves: 1,
148
+ positionAmount: 1.0,
149
+ rotationAmount: 0.0,
150
+ sizeAmount: 0.0,
102
151
  },
103
152
  textureSheetAnimation: {
104
153
  tiles: new THREE.Vector2(1.0, 1.0),
@@ -132,7 +181,8 @@ const calculatePositionAndVelocity = (
132
181
  { shape, sphere, cone, circle, rectangle },
133
182
  startSpeed,
134
183
  position,
135
- velocity
184
+ velocity,
185
+ velocityOverLifetime
136
186
  ) => {
137
187
  switch (shape) {
138
188
  case Shape.SPHERE:
@@ -171,6 +221,21 @@ const calculatePositionAndVelocity = (
171
221
  );
172
222
  break;
173
223
  }
224
+
225
+ if (velocityOverLifetime.isActive) {
226
+ velocity.x += THREE.MathUtils.randFloat(
227
+ velocityOverLifetime.linear.x.min,
228
+ velocityOverLifetime.linear.x.max
229
+ );
230
+ velocity.y += THREE.MathUtils.randFloat(
231
+ velocityOverLifetime.linear.y.min,
232
+ velocityOverLifetime.linear.y.max
233
+ );
234
+ velocity.z += THREE.MathUtils.randFloat(
235
+ velocityOverLifetime.linear.z.min,
236
+ velocityOverLifetime.linear.z.max
237
+ );
238
+ }
174
239
  };
175
240
 
176
241
  export const createParticleSystem = (
@@ -187,9 +252,28 @@ export const createParticleSystem = (
187
252
  worldEuler: new THREE.Euler(),
188
253
  gravityVelocity: new THREE.Vector3(0, 0, 0),
189
254
  startValues: {},
255
+ lifetimeValues: {},
256
+ creationTimes: [],
257
+ noise: null,
190
258
  };
191
259
 
192
260
  const normalizedConfig = patchObject(DEFAULT_PARTICLE_SYSTEM_CONFIG, config);
261
+
262
+ const bezierCompatibleProperties = [
263
+ "sizeOverLifetime",
264
+ "opacityOverLifetime",
265
+ ];
266
+ bezierCompatibleProperties.forEach((key) => {
267
+ if (
268
+ normalizedConfig[key].isActive &&
269
+ normalizedConfig[key].curveFunction === CurveFunction.BEZIER &&
270
+ normalizedConfig[key].bezierPoints
271
+ )
272
+ normalizedConfig[key].curveFunction = createBezierCurveFunction(
273
+ normalizedConfig[key].bezierPoints
274
+ );
275
+ });
276
+
193
277
  const {
194
278
  transform,
195
279
  duration,
@@ -207,11 +291,17 @@ export const createParticleSystem = (
207
291
  emission,
208
292
  shape,
209
293
  map,
294
+ renderer,
295
+ noise,
296
+ velocityOverLifetime,
210
297
  onUpdate,
211
298
  onComplete,
212
299
  textureSheetAnimation,
213
300
  } = normalizedConfig;
214
301
 
302
+ if (typeof renderer.blending === "string")
303
+ renderer.blending = blendingMap[renderer.blending];
304
+
215
305
  const startPositions = Array.from(
216
306
  { length: maxParticles },
217
307
  () => new THREE.Vector3()
@@ -221,6 +311,8 @@ export const createParticleSystem = (
221
311
  () => new THREE.Vector3()
222
312
  );
223
313
 
314
+ generalData.creationTimes = Array.from({ length: maxParticles }, () => 0);
315
+
224
316
  const startValueKeys = ["startSize", "startOpacity"];
225
317
  startValueKeys.forEach((key) => {
226
318
  generalData.startValues[key] = Array.from({ length: maxParticles }, () =>
@@ -231,6 +323,37 @@ export const createParticleSystem = (
231
323
  );
232
324
  });
233
325
 
326
+ const lifetimeValueKeys = ["rotationOverLifetime"];
327
+ lifetimeValueKeys.forEach((key) => {
328
+ if (normalizedConfig[key].isActive)
329
+ generalData.lifetimeValues[key] = Array.from(
330
+ { length: maxParticles },
331
+ () =>
332
+ THREE.MathUtils.randFloat(
333
+ normalizedConfig[key].min,
334
+ normalizedConfig[key].max
335
+ )
336
+ );
337
+ });
338
+
339
+ generalData.noise = {
340
+ isActive: noise.isActive,
341
+ strength: noise.strength,
342
+ positionAmount: noise.positionAmount,
343
+ rotationAmount: noise.rotationAmount,
344
+ sizeAmount: noise.sizeAmount,
345
+ sampler: noise.isActive
346
+ ? new FBM({
347
+ seed: Math.random(),
348
+ scale: noise.frequency,
349
+ octaves: noise.octaves,
350
+ })
351
+ : null,
352
+ offsets: noise.useRandomOffset
353
+ ? Array.from({ length: maxParticles }, () => Math.random() * 100)
354
+ : null,
355
+ };
356
+
234
357
  const material = new THREE.ShaderMaterial({
235
358
  uniforms: {
236
359
  elapsed: {
@@ -251,10 +374,10 @@ export const createParticleSystem = (
251
374
  },
252
375
  vertexShader: ParticleSystemVertexShader,
253
376
  fragmentShader: ParticleSystemFragmentShader,
254
- transparent: true,
255
- blending: THREE.AdditiveBlending,
256
- depthTest: true,
257
- depthWrite: false,
377
+ transparent: renderer.transparent,
378
+ blending: renderer.blending,
379
+ depthTest: renderer.depthTest,
380
+ depthWrite: renderer.depthWrite,
258
381
  });
259
382
 
260
383
  const geometry = new THREE.BufferGeometry();
@@ -264,7 +387,8 @@ export const createParticleSystem = (
264
387
  shape,
265
388
  startSpeed,
266
389
  startPositions[i],
267
- velocities[i]
390
+ velocities[i],
391
+ velocityOverLifetime
268
392
  );
269
393
 
270
394
  geometry.setFromPoints(
@@ -283,7 +407,6 @@ export const createParticleSystem = (
283
407
  };
284
408
 
285
409
  createFloat32AttributesRequest("isActive", false);
286
- createFloat32AttributesRequest("creationTime", 0);
287
410
  createFloat32AttributesRequest("lifetime", 0);
288
411
  createFloat32AttributesRequest("startLifetime", () =>
289
412
  THREE.MathUtils.randFloat(startLifetime.min, startLifetime.max)
@@ -339,8 +462,10 @@ export const createParticleSystem = (
339
462
 
340
463
  const activateParticle = ({ particleIndex, activationTime }) => {
341
464
  geometry.attributes.isActive.array[particleIndex] = true;
342
- geometry.attributes.creationTime.array[particleIndex] =
343
- activationTime - float32Helper;
465
+ generalData.creationTimes[particleIndex] = activationTime;
466
+
467
+ if (generalData.noise.offsets)
468
+ generalData.noise.offsets[particleIndex] = Math.random() * 100;
344
469
 
345
470
  const colorRandomRatio = Math.random();
346
471
 
@@ -379,6 +504,13 @@ export const createParticleSystem = (
379
504
  THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
380
505
  );
381
506
 
507
+ if (normalizedConfig.rotationOverLifetime.isActive)
508
+ generalData.lifetimeValues.rotationOverLifetime[particleIndex] =
509
+ THREE.MathUtils.randFloat(
510
+ normalizedConfig.rotationOverLifetime.min,
511
+ normalizedConfig.rotationOverLifetime.max
512
+ );
513
+
382
514
  geometry.attributes.rotation.needsUpdate = true;
383
515
  geometry.attributes.colorB.needsUpdate = true;
384
516
 
@@ -386,7 +518,8 @@ export const createParticleSystem = (
386
518
  shape,
387
519
  startSpeed,
388
520
  startPositions[particleIndex],
389
- velocities[particleIndex]
521
+ velocities[particleIndex],
522
+ velocityOverLifetime
390
523
  );
391
524
  const positionIndex = Math.floor(particleIndex * 3);
392
525
  geometry.attributes.position.array[positionIndex] =
@@ -395,13 +528,16 @@ export const createParticleSystem = (
395
528
  startPositions[particleIndex].y;
396
529
  geometry.attributes.position.array[positionIndex + 2] =
397
530
  startPositions[particleIndex].z;
398
- particleSystem.geometry.attributes.position.needsUpdate = true;
399
531
 
400
532
  geometry.attributes.lifetime.array[particleIndex] = 0;
401
533
  geometry.attributes.lifetime.needsUpdate = true;
402
534
 
403
535
  applyModifiers({
536
+ delta: 0,
537
+ elapsed: 0,
538
+ noise: generalData.noise,
404
539
  startValues: generalData.startValues,
540
+ lifetimeValues: generalData.lifetimeValues,
405
541
  normalizedConfig,
406
542
  attributes: particleSystem.geometry.attributes,
407
543
  particleLifetimePercentage: 0,
@@ -489,12 +625,13 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
489
625
  particleSystem.material.uniforms.elapsed.value = elapsed;
490
626
 
491
627
  particleSystem.getWorldPosition(currentWorldPosition);
492
- if (lastWorldPosition.x !== -99999)
628
+ if (lastWorldPosition.x !== -99999) {
493
629
  worldPositionChange.set(
494
630
  currentWorldPosition.x - lastWorldPosition.x,
495
631
  currentWorldPosition.y - lastWorldPosition.y,
496
632
  currentWorldPosition.z - lastWorldPosition.z
497
633
  );
634
+ }
498
635
  generalData.distanceFromLastEmitByDistance += worldPositionChange.length();
499
636
  particleSystem.getWorldPosition(lastWorldPosition);
500
637
 
@@ -519,59 +656,65 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
519
656
  particleSystem.updateMatrixWorld();
520
657
  }
521
658
 
522
- particleSystem.geometry.attributes.creationTime.array.forEach(
523
- (entry, index) => {
524
- if (particleSystem.geometry.attributes.isActive.array[index]) {
525
- const particleLifetime = now - float32Helper - entry;
659
+ generalData.creationTimes.forEach((entry, index) => {
660
+ if (particleSystem.geometry.attributes.isActive.array[index]) {
661
+ const particleLifetime = now - entry;
662
+ if (
663
+ particleLifetime >
664
+ particleSystem.geometry.attributes.startLifetime.array[index]
665
+ )
666
+ deactivateParticle(index);
667
+ else {
668
+ const velocity = velocities[index];
669
+ velocity.x -= gravityVelocity.x;
670
+ velocity.y -= gravityVelocity.y;
671
+ velocity.z -= gravityVelocity.z;
672
+
526
673
  if (
527
- particleLifetime >
528
- particleSystem.geometry.attributes.startLifetime.array[index]
529
- )
530
- deactivateParticle(index);
531
- else {
532
- const velocity = velocities[index];
533
- velocity.x -= gravityVelocity.x;
534
- velocity.y -= gravityVelocity.y;
535
- velocity.z -= gravityVelocity.z;
536
-
537
- if (
538
- gravity !== 0 ||
539
- velocity.x !== 0 ||
540
- velocity.y !== 0 ||
541
- velocity.z !== 0
542
- ) {
543
- const positionIndex = index * 3;
544
- const positionArr =
545
- particleSystem.geometry.attributes.position.array;
546
- if (simulationSpace === SimulationSpace.WORLD) {
547
- positionArr[positionIndex] -= worldPositionChange.x;
548
- positionArr[positionIndex + 1] -= worldPositionChange.y;
549
- positionArr[positionIndex + 2] -= worldPositionChange.z;
550
- }
551
- positionArr[positionIndex] += velocity.x * delta;
552
- positionArr[positionIndex + 1] += velocity.y * delta;
553
- positionArr[positionIndex + 2] += velocity.z * delta;
554
- particleSystem.geometry.attributes.position.needsUpdate = true;
674
+ gravity !== 0 ||
675
+ velocity.x !== 0 ||
676
+ velocity.y !== 0 ||
677
+ velocity.z !== 0 ||
678
+ worldPositionChange.x !== 0 ||
679
+ worldPositionChange.y !== 0 ||
680
+ worldPositionChange.z !== 0
681
+ ) {
682
+ const positionIndex = index * 3;
683
+ const positionArr =
684
+ particleSystem.geometry.attributes.position.array;
685
+
686
+ if (simulationSpace === SimulationSpace.WORLD) {
687
+ positionArr[positionIndex] -= worldPositionChange.x;
688
+ positionArr[positionIndex + 1] -= worldPositionChange.y;
689
+ positionArr[positionIndex + 2] -= worldPositionChange.z;
555
690
  }
556
-
557
- particleSystem.geometry.attributes.lifetime.array[index] =
558
- particleLifetime;
559
- particleSystem.geometry.attributes.lifetime.needsUpdate = true;
560
-
561
- const particleLifetimePercentage =
562
- particleLifetime /
563
- particleSystem.geometry.attributes.startLifetime.array[index];
564
- applyModifiers({
565
- startValues: generalData.startValues,
566
- normalizedConfig,
567
- attributes: particleSystem.geometry.attributes,
568
- particleLifetimePercentage,
569
- particleIndex: index,
570
- });
691
+ positionArr[positionIndex] += velocity.x * delta;
692
+ positionArr[positionIndex + 1] += velocity.y * delta;
693
+ positionArr[positionIndex + 2] += velocity.z * delta;
694
+ particleSystem.geometry.attributes.position.needsUpdate = true;
571
695
  }
696
+
697
+ particleSystem.geometry.attributes.lifetime.array[index] =
698
+ particleLifetime;
699
+ particleSystem.geometry.attributes.lifetime.needsUpdate = true;
700
+
701
+ const particleLifetimePercentage =
702
+ particleLifetime /
703
+ particleSystem.geometry.attributes.startLifetime.array[index];
704
+ applyModifiers({
705
+ delta,
706
+ elapsed,
707
+ noise: generalData.noise,
708
+ startValues: generalData.startValues,
709
+ lifetimeValues: generalData.lifetimeValues,
710
+ normalizedConfig,
711
+ attributes: particleSystem.geometry.attributes,
712
+ particleLifetimePercentage,
713
+ particleIndex: index,
714
+ });
572
715
  }
573
716
  }
574
- );
717
+ });
575
718
 
576
719
  if (looping || lifetime < duration * 1000) {
577
720
  const emissionDelta = now - lastEmissionTime;