@newkrok/three-particles 0.2.2 → 0.4.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/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.1" ... }, `
21
+ `"dependencies": { ... "@newkrok/three-particles": "0.4.0" ... }, `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newkrok/three-particles",
3
- "version": "0.2.2",
3
+ "version": "0.4.1",
4
4
  "description": "Particle system for ThreeJS",
5
5
  "main": "src/js/three-particles.js",
6
6
  "bin": {
@@ -23,7 +23,9 @@
23
23
  },
24
24
  "homepage": "https://github.com/NewKrok/three-particles#readme",
25
25
  "dependencies": {
26
- "three": "^0.135.0"
26
+ "easing-functions": "1.0.1",
27
+ "three": "0.136.0",
28
+ "three-noise": "^1.1.1"
27
29
  },
28
30
  "scripts": {
29
31
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -1,5 +1,5 @@
1
1
  const ParticleSystemVertexShader = `
2
- attribute float startSize;
2
+ attribute float size;
3
3
  attribute float colorR;
4
4
  attribute float colorG;
5
5
  attribute float colorB;
@@ -25,7 +25,7 @@ const ParticleSystemVertexShader = `
25
25
  vStartFrame = startFrame;
26
26
 
27
27
  vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
28
- gl_PointSize = startSize * (100.0 / length(mvPosition.xyz));
28
+ gl_PointSize = size * (100.0 / length(mvPosition.xyz));
29
29
  gl_Position = projectionMatrix * mvPosition;
30
30
  }
31
31
  `;
@@ -0,0 +1,74 @@
1
+ import Easing from "easing-functions";
2
+
3
+ export const CurveFunction = {
4
+ LINEAR: "LINEAR",
5
+ QUADRATIC_IN: "QUADRATIC_IN",
6
+ QUADRATIC_OUT: "QUADRATIC_OUT",
7
+ QUADRATIC_IN_OUT: "QUADRATIC_IN_OUT",
8
+ CUBIC_IN: "CUBIC_IN",
9
+ CUBIC_OUT: "CUBIC_OUT",
10
+ CUBIC_IN_OUT: "CUBIC_IN_OUT",
11
+ QUARTIC_IN: "QUARTIC_IN",
12
+ QUARTIC_OUT: "QUARTIC_OUT",
13
+ QUARTIC_IN_OUT: "QUARTIC_IN_OUT",
14
+ QUINTIC_IN: "QUINTIC_IN",
15
+ QUINTIC_OUT: "QUINTIC_OUT",
16
+ QUINTIC_IN_OUT: "QUINTIC_IN_OUT",
17
+ SINUSOIDAL_IN: "SINUSOIDAL_IN",
18
+ SINUSOIDAL_OUT: "SINUSOIDAL_OUT",
19
+ SINUSOIDAL_IN_OUT: "SINUSOIDAL_IN_OUT",
20
+ EXPONENTIAL_IN: "EXPONENTIAL_IN",
21
+ EXPONENTIAL_OUT: "EXPONENTIAL_OUT",
22
+ EXPONENTIAL_IN_OUT: "EXPONENTIAL_IN_OUT",
23
+ CIRCULAR_IN: "CIRCULAR_IN",
24
+ CIRCULAR_OUT: "CIRCULAR_OUT",
25
+ CIRCULAR_IN_OUT: "CIRCULAR_IN_OUT",
26
+ ELASTIC_IN: "ELASTIC_IN",
27
+ ELASTIC_OUT: "ELASTIC_OUT",
28
+ ELASTIC_IN_OUT: "ELASTIC_IN_OUT",
29
+ BACK_IN: "BACK_IN",
30
+ BACK_OUT: "BACK_OUT",
31
+ BACK_IN_OUT: "BACK_IN_OUT",
32
+ BOUNCE_IN: "BOUNCE_IN",
33
+ BOUNCE_OUT: "BOUNCE_OUT",
34
+ BOUNCE_IN_OUT: "BOUNCE_IN_OUT",
35
+ };
36
+
37
+ const CurveFunctionMap = {
38
+ [CurveFunction.LINEAR]: Easing.Linear.None,
39
+ [CurveFunction.QUADRATIC_IN]: Easing.Quadratic.In,
40
+ [CurveFunction.QUADRATIC_OUT]: Easing.Quadratic.Out,
41
+ [CurveFunction.QUADRATIC_IN_OUT]: Easing.Quadratic.InOut,
42
+ [CurveFunction.CUBIC_IN]: Easing.Cubic.In,
43
+ [CurveFunction.CUBIC_OUT]: Easing.Cubic.Out,
44
+ [CurveFunction.CUBIC_IN_OUT]: Easing.Cubic.InOut,
45
+ [CurveFunction.QUARTIC_IN]: Easing.Quartic.In,
46
+ [CurveFunction.QUARTIC_OUT]: Easing.Quartic.Out,
47
+ [CurveFunction.QUARTIC_IN_OUT]: Easing.Quartic.InOut,
48
+ [CurveFunction.QUINTIC_IN]: Easing.Quintic.In,
49
+ [CurveFunction.QUINTIC_OUT]: Easing.Quintic.Out,
50
+ [CurveFunction.QUINTIC_IN_OUT]: Easing.Quintic.InOut,
51
+ [CurveFunction.SINUSOIDAL_IN]: Easing.Sinusoidal.In,
52
+ [CurveFunction.SINUSOIDAL_OUT]: Easing.Sinusoidal.Out,
53
+ [CurveFunction.SINUSOIDAL_IN_OUT]: Easing.Sinusoidal.InOut,
54
+ [CurveFunction.EXPONENTIAL_IN]: Easing.Exponential.In,
55
+ [CurveFunction.EXPONENTIAL_OUT]: Easing.Exponential.Out,
56
+ [CurveFunction.EXPONENTIAL_IN_OUT]: Easing.Exponential.InOut,
57
+ [CurveFunction.CIRCULAR_IN]: Easing.Circular.In,
58
+ [CurveFunction.CIRCULAR_OUT]: Easing.Circular.Out,
59
+ [CurveFunction.CIRCULAR_IN_OUT]: Easing.Circular.InOut,
60
+ [CurveFunction.ELASTIC_IN]: Easing.Elastic.In,
61
+ [CurveFunction.ELASTIC_OUT]: Easing.Elastic.Out,
62
+ [CurveFunction.ELASTIC_IN_OUT]: Easing.Elastic.InOut,
63
+ [CurveFunction.BACK_IN]: Easing.Back.In,
64
+ [CurveFunction.BACK_OUT]: Easing.Back.Out,
65
+ [CurveFunction.BACK_IN_OUT]: Easing.Back.InOut,
66
+ [CurveFunction.BOUNCE_IN]: Easing.Bounce.In,
67
+ [CurveFunction.BOUNCE_OUT]: Easing.Bounce.Out,
68
+ [CurveFunction.BOUNCE_IN_OUT]: Easing.Bounce.InOut,
69
+ };
70
+
71
+ export const getCurveFunction = (curveFunction) =>
72
+ typeof curveFunction === "function"
73
+ ? curveFunction
74
+ : CurveFunctionMap[curveFunction];
@@ -0,0 +1,106 @@
1
+ import * as THREE from "three/build/three.module.js";
2
+
3
+ import { getCurveFunction } from "./three-particles-curves.js";
4
+ import { size } from "lodash";
5
+
6
+ const noiseInput = new THREE.Vector3(0, 0, 0);
7
+
8
+ const curveModifiers = [
9
+ // {key:"colorOverLifetime", attributeKeys:["colorR", "colorG", "colorB"]},
10
+ {
11
+ key: "opacityOverLifetime",
12
+ attributeKeys: ["colorA"],
13
+ startValueKeys: ["startOpacity"],
14
+ },
15
+ {
16
+ key: "sizeOverLifetime",
17
+ attributeKeys: ["size"],
18
+ startValueKeys: ["startSize"],
19
+ },
20
+ ];
21
+
22
+ export const applyModifiers = ({
23
+ delta,
24
+ noise,
25
+ startValues,
26
+ lifetimeValues,
27
+ normalizedConfig,
28
+ attributes,
29
+ particleLifetimePercentage,
30
+ particleIndex,
31
+ forceUpdate = false,
32
+ }) => {
33
+ curveModifiers.forEach(({ key, attributeKeys, startValueKeys }) => {
34
+ const curveModifier = normalizedConfig[key];
35
+ if (curveModifier.isActive) {
36
+ const multiplier = getCurveFunction(curveModifier.curveFunction)(
37
+ particleLifetimePercentage
38
+ );
39
+ attributeKeys.forEach((attributeKey, index) => {
40
+ attributes[attributeKey].array[particleIndex] =
41
+ startValues[startValueKeys[index]][particleIndex] * multiplier;
42
+ attributes[attributeKey].needsUpdate = true;
43
+ });
44
+ } else if (forceUpdate) {
45
+ attributeKeys.forEach((attributeKey, index) => {
46
+ attributes[attributeKey].array[particleIndex] =
47
+ startValues[startValueKeys[index]][particleIndex];
48
+ attributes[attributeKey].needsUpdate = true;
49
+ });
50
+ }
51
+ });
52
+
53
+ if (lifetimeValues.rotationOverLifetime) {
54
+ attributes.rotation.array[particleIndex] +=
55
+ lifetimeValues.rotationOverLifetime[particleIndex] * delta * 0.02;
56
+ attributes.rotation.needsUpdate = true;
57
+ }
58
+
59
+ if (noise.isActive) {
60
+ const {
61
+ sampler,
62
+ strength,
63
+ offsets,
64
+ positionAmount,
65
+ rotationAmount,
66
+ sizeAmount,
67
+ } = noise;
68
+ const positionIndex = particleIndex * 3;
69
+ const positionArr = attributes.position.array;
70
+ let noiseOnPosition;
71
+
72
+ const noisePosition =
73
+ (particleLifetimePercentage + (offsets ? offsets[particleIndex] : 0)) *
74
+ 10 *
75
+ strength;
76
+ const noisePower = 0.15 * strength;
77
+
78
+ noiseInput.set(noisePosition, 0, 0);
79
+ noiseOnPosition = sampler.get3(noiseInput);
80
+ positionArr[positionIndex] += noiseOnPosition * noisePower * positionAmount;
81
+
82
+ if (rotationAmount !== 0) {
83
+ attributes.rotation.array[particleIndex] +=
84
+ noiseOnPosition * noisePower * rotationAmount;
85
+ attributes.rotation.needsUpdate = true;
86
+ }
87
+
88
+ if (sizeAmount !== 0) {
89
+ attributes.size.array[particleIndex] +=
90
+ noiseOnPosition * noisePower * sizeAmount;
91
+ attributes.size.needsUpdate = true;
92
+ }
93
+
94
+ noiseInput.set(noisePosition, noisePosition, 0);
95
+ noiseOnPosition = sampler.get3(noiseInput);
96
+ positionArr[positionIndex + 1] +=
97
+ noiseOnPosition * noisePower * positionAmount;
98
+
99
+ noiseInput.set(noisePosition, noisePosition, noisePosition);
100
+ noiseOnPosition = sampler.get3(noiseInput);
101
+ positionArr[positionIndex + 2] +=
102
+ noiseOnPosition * noisePower * positionAmount;
103
+
104
+ attributes.position.needsUpdate = true;
105
+ }
106
+ };
@@ -1,6 +1,6 @@
1
1
  import * as THREE from "three/build/three.module.js";
2
2
 
3
- export const deepMerge = (
3
+ export const patchObject = (
4
4
  objectA,
5
5
  objectB,
6
6
  config = { skippedProperties: [], applyToFirstObject: false }
@@ -9,9 +9,14 @@ export const deepMerge = (
9
9
  Object.keys(objectA).forEach((key) => {
10
10
  if (!config.skippedProperties || !config.skippedProperties.includes(key)) {
11
11
  if (typeof objectA[key] === "object" && objectA[key] && objectB[key]) {
12
- result[key] = deepMerge(objectA[key], objectB[key], config);
12
+ result[key] = patchObject(objectA[key], objectB[key], config);
13
13
  } else {
14
- result[key] = objectB[key] === 0 ? 0 : objectB[key] || objectA[key];
14
+ result[key] =
15
+ objectB[key] === 0
16
+ ? 0
17
+ : objectB[key] === false
18
+ ? false
19
+ : objectB[key] || objectA[key];
15
20
  if (config.applyToFirstObject) objectA[key] = result[key];
16
21
  }
17
22
  }
@@ -100,7 +105,7 @@ export const calculateRandomPositionAndVelocityOnCone = (
100
105
  sinNormalizedAngle *
101
106
  speedMultiplierByPosition *
102
107
  randomizedSpeed,
103
- -Math.cos(normalizedAngle) * randomizedSpeed
108
+ Math.cos(normalizedAngle) * randomizedSpeed
104
109
  );
105
110
  };
106
111
 
@@ -157,5 +162,5 @@ export const calculateRandomPositionAndVelocityOnRectangle = (
157
162
  startSpeed.min,
158
163
  startSpeed.max
159
164
  );
160
- velocity.set(0, 0, -randomizedSpeed);
165
+ velocity.set(0, 0, randomizedSpeed);
161
166
  };
@@ -5,14 +5,14 @@ import {
5
5
  calculateRandomPositionAndVelocityOnCone,
6
6
  calculateRandomPositionAndVelocityOnRectangle,
7
7
  calculateRandomPositionAndVelocityOnSphere,
8
- deepMerge,
9
- } from "./three-particles-utils.js";
8
+ patchObject,
9
+ } from "./three-particles/three-particles-utils.js";
10
10
 
11
- import ParticleSystemFragmentShader from "./shaders/particle-system-fragment-shader.glsl.js";
12
- import ParticleSystemVertexShader from "./shaders/particle-system-vertex-shader.glsl.js";
13
-
14
- // Float32Array is not enough accurate when we are storing timestamp in it so we just remove unnecessary time
15
- const float32Helper = 1638200000000;
11
+ import { CurveFunction } from "./three-particles/three-particles-curves.js";
12
+ import { FBM } from "three-noise/build/three-noise.module.js";
13
+ import ParticleSystemFragmentShader from "./three-particles/shaders/particle-system-fragment-shader.glsl.js";
14
+ import ParticleSystemVertexShader from "./three-particles/shaders/particle-system-vertex-shader.glsl.js";
15
+ import { applyModifiers } from "./three-particles/three-particles-modifiers.js";
16
16
 
17
17
  let createdParticleSystems = [];
18
18
 
@@ -34,10 +34,23 @@ export const TimeMode = {
34
34
  FPS: "FPS",
35
35
  };
36
36
 
37
+ export const blendingMap = {
38
+ "THREE.NoBlending": THREE.NoBlending,
39
+ "THREE.NormalBlending": THREE.NormalBlending,
40
+ "THREE.AdditiveBlending": THREE.AdditiveBlending,
41
+ "THREE.SubtractiveBlending": THREE.SubtractiveBlending,
42
+ "THREE.MultiplyBlending": THREE.MultiplyBlending,
43
+ };
44
+
37
45
  export const getDefaultParticleSystemConfig = () =>
38
46
  JSON.parse(JSON.stringify(DEFAULT_PARTICLE_SYSTEM_CONFIG));
39
47
 
40
48
  const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
49
+ transform: {
50
+ position: { x: 0, y: 0, z: 0 },
51
+ rotation: { x: 0, y: 0, z: 0 },
52
+ scale: { x: 1, y: 1, z: 1 },
53
+ },
41
54
  duration: 5.0,
42
55
  looping: true,
43
56
  startDelay: { min: 0.0, max: 0.0 },
@@ -81,6 +94,52 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
81
94
  },
82
95
  },
83
96
  map: null,
97
+ renderer: {
98
+ blending: THREE.NormalBlending,
99
+ transparent: true,
100
+ depthTest: true,
101
+ depthWrite: false,
102
+ },
103
+ velocityOverLifetime: {
104
+ isActive: false,
105
+ linear: {
106
+ x: { min: 0, max: 0 },
107
+ y: { min: 0, max: 0 },
108
+ z: { min: 0, max: 0 },
109
+ },
110
+ orbital: {
111
+ x: { min: 0, max: 0 },
112
+ y: { min: 0, max: 0 },
113
+ z: { min: 0, max: 0 },
114
+ },
115
+ },
116
+ sizeOverLifetime: {
117
+ isActive: false,
118
+ curveFunction: CurveFunction.LINEAR,
119
+ },
120
+ /* colorOverLifetime: {
121
+ isActive: false,
122
+ curveFunction: CurveFunction.LINEAR,
123
+ }, */
124
+ opacityOverLifetime: {
125
+ isActive: false,
126
+ curveFunction: CurveFunction.LINEAR,
127
+ },
128
+ rotationOverLifetime: {
129
+ isActive: false,
130
+ min: 0.0,
131
+ max: 0.0,
132
+ },
133
+ noise: {
134
+ isActive: false,
135
+ useRandomOffset: false,
136
+ strength: 1.0,
137
+ frequency: 0.5,
138
+ octaves: 1,
139
+ positionAmount: 1.0,
140
+ rotationAmount: 0.0,
141
+ sizeAmount: 0.0,
142
+ },
84
143
  textureSheetAnimation: {
85
144
  tiles: new THREE.Vector2(1.0, 1.0),
86
145
  timeMode: TimeMode.LIFETIME,
@@ -113,7 +172,8 @@ const calculatePositionAndVelocity = (
113
172
  { shape, sphere, cone, circle, rectangle },
114
173
  startSpeed,
115
174
  position,
116
- velocity
175
+ velocity,
176
+ velocityOverLifetime
117
177
  ) => {
118
178
  switch (shape) {
119
179
  case Shape.SPHERE:
@@ -152,17 +212,45 @@ const calculatePositionAndVelocity = (
152
212
  );
153
213
  break;
154
214
  }
215
+
216
+ if (velocityOverLifetime.isActive) {
217
+ velocity.x += THREE.MathUtils.randFloat(
218
+ velocityOverLifetime.linear.x.min,
219
+ velocityOverLifetime.linear.x.max
220
+ );
221
+ velocity.y += THREE.MathUtils.randFloat(
222
+ velocityOverLifetime.linear.y.min,
223
+ velocityOverLifetime.linear.y.max
224
+ );
225
+ velocity.z += THREE.MathUtils.randFloat(
226
+ velocityOverLifetime.linear.z.min,
227
+ velocityOverLifetime.linear.z.max
228
+ );
229
+ }
155
230
  };
156
231
 
157
232
  export const createParticleSystem = (
158
233
  config = DEFAULT_PARTICLE_SYSTEM_CONFIG
159
234
  ) => {
160
235
  const now = Date.now();
161
- const lastWorldPosition = new THREE.Vector3(-99999, -99999, -99999);
162
- const worldPositionChange = new THREE.Vector3();
163
- const generalData = { distanceFromLastEmitByDistance: 0 };
236
+ const generalData = {
237
+ distanceFromLastEmitByDistance: 0,
238
+ lastWorldPosition: new THREE.Vector3(-99999),
239
+ currentWorldPosition: new THREE.Vector3(-99999),
240
+ worldPositionChange: new THREE.Vector3(),
241
+ worldQuaternion: new THREE.Quaternion(),
242
+ lastWorldQuaternion: new THREE.Quaternion(-99999),
243
+ worldEuler: new THREE.Euler(),
244
+ gravityVelocity: new THREE.Vector3(0, 0, 0),
245
+ startValues: {},
246
+ lifetimeValues: {},
247
+ creationTimes: [],
248
+ noise: null,
249
+ };
164
250
 
251
+ const normalizedConfig = patchObject(DEFAULT_PARTICLE_SYSTEM_CONFIG, config);
165
252
  const {
253
+ transform,
166
254
  duration,
167
255
  looping,
168
256
  startDelay,
@@ -178,10 +266,16 @@ export const createParticleSystem = (
178
266
  emission,
179
267
  shape,
180
268
  map,
269
+ renderer,
270
+ noise,
271
+ velocityOverLifetime,
181
272
  onUpdate,
182
273
  onComplete,
183
274
  textureSheetAnimation,
184
- } = deepMerge(DEFAULT_PARTICLE_SYSTEM_CONFIG, config);
275
+ } = normalizedConfig;
276
+
277
+ if (typeof renderer.blending === "string")
278
+ renderer.blending = blendingMap[renderer.blending];
185
279
 
186
280
  const startPositions = Array.from(
187
281
  { length: maxParticles },
@@ -192,6 +286,49 @@ export const createParticleSystem = (
192
286
  () => new THREE.Vector3()
193
287
  );
194
288
 
289
+ generalData.creationTimes = Array.from({ length: maxParticles }, () => 0);
290
+
291
+ const startValueKeys = ["startSize", "startOpacity"];
292
+ startValueKeys.forEach((key) => {
293
+ generalData.startValues[key] = Array.from({ length: maxParticles }, () =>
294
+ THREE.MathUtils.randFloat(
295
+ normalizedConfig[key].min,
296
+ normalizedConfig[key].max
297
+ )
298
+ );
299
+ });
300
+
301
+ const lifetimeValueKeys = ["rotationOverLifetime"];
302
+ lifetimeValueKeys.forEach((key) => {
303
+ if (normalizedConfig[key].isActive)
304
+ generalData.lifetimeValues[key] = Array.from(
305
+ { length: maxParticles },
306
+ () =>
307
+ THREE.MathUtils.randFloat(
308
+ normalizedConfig[key].min,
309
+ normalizedConfig[key].max
310
+ )
311
+ );
312
+ });
313
+
314
+ generalData.noise = {
315
+ isActive: noise.isActive,
316
+ strength: noise.strength,
317
+ positionAmount: noise.positionAmount,
318
+ rotationAmount: noise.rotationAmount,
319
+ sizeAmount: noise.sizeAmount,
320
+ sampler: noise.isActive
321
+ ? new FBM({
322
+ seed: Math.random(),
323
+ scale: noise.frequency,
324
+ octaves: noise.octaves,
325
+ })
326
+ : null,
327
+ offsets: noise.useRandomOffset
328
+ ? Array.from({ length: maxParticles }, () => Math.random() * 100)
329
+ : null,
330
+ };
331
+
195
332
  const material = new THREE.ShaderMaterial({
196
333
  uniforms: {
197
334
  elapsed: {
@@ -212,10 +349,10 @@ export const createParticleSystem = (
212
349
  },
213
350
  vertexShader: ParticleSystemVertexShader,
214
351
  fragmentShader: ParticleSystemFragmentShader,
215
- transparent: true,
216
- blending: THREE.AdditiveBlending,
217
- depthTest: true,
218
- depthWrite: false,
352
+ transparent: renderer.transparent,
353
+ blending: renderer.blending,
354
+ depthTest: renderer.depthTest,
355
+ depthWrite: renderer.depthWrite,
219
356
  });
220
357
 
221
358
  const geometry = new THREE.BufferGeometry();
@@ -225,7 +362,8 @@ export const createParticleSystem = (
225
362
  shape,
226
363
  startSpeed,
227
364
  startPositions[i],
228
- velocities[i]
365
+ velocities[i],
366
+ velocityOverLifetime
229
367
  );
230
368
 
231
369
  geometry.setFromPoints(
@@ -244,7 +382,6 @@ export const createParticleSystem = (
244
382
  };
245
383
 
246
384
  createFloat32AttributesRequest("isActive", false);
247
- createFloat32AttributesRequest("creationTime", 0);
248
385
  createFloat32AttributesRequest("lifetime", 0);
249
386
  createFloat32AttributesRequest("startLifetime", () =>
250
387
  THREE.MathUtils.randFloat(startLifetime.min, startLifetime.max)
@@ -258,22 +395,16 @@ export const createParticleSystem = (
258
395
 
259
396
  createFloat32AttributesRequest("opacity", 0);
260
397
 
261
- createFloat32Attributes({
262
- geometry,
263
- propertyName: "rotation",
264
- maxParticles,
265
- factory: () =>
266
- THREE.Math.degToRad(
267
- THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
268
- ),
269
- });
398
+ createFloat32AttributesRequest("rotation", () =>
399
+ THREE.Math.degToRad(
400
+ THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
401
+ )
402
+ );
270
403
 
271
- createFloat32Attributes({
272
- geometry,
273
- propertyName: "startSize",
274
- maxParticles,
275
- factory: () => THREE.MathUtils.randFloat(startSize.min, startSize.max),
276
- });
404
+ createFloat32AttributesRequest(
405
+ "size",
406
+ (_, index) => generalData.startValues.startSize[index]
407
+ );
277
408
 
278
409
  createFloat32AttributesRequest("rotation", 0);
279
410
 
@@ -300,16 +431,16 @@ export const createParticleSystem = (
300
431
 
301
432
  const deactivateParticle = (particleIndex) => {
302
433
  geometry.attributes.isActive.array[particleIndex] = false;
303
- geometry.attributes.lifetime.array[particleIndex] = 0;
304
- geometry.attributes.lifetime.needsUpdate = true;
305
434
  geometry.attributes.colorA.array[particleIndex] = 0;
306
435
  geometry.attributes.colorA.needsUpdate = true;
307
436
  };
308
437
 
309
438
  const activateParticle = ({ particleIndex, activationTime }) => {
310
439
  geometry.attributes.isActive.array[particleIndex] = true;
311
- geometry.attributes.creationTime.array[particleIndex] =
312
- activationTime - float32Helper;
440
+ generalData.creationTimes[particleIndex] = activationTime;
441
+
442
+ if (generalData.noise.offsets)
443
+ generalData.noise.offsets[particleIndex] = Math.random() * 100;
313
444
 
314
445
  const colorRandomRatio = Math.random();
315
446
 
@@ -328,12 +459,6 @@ export const createParticleSystem = (
328
459
  colorRandomRatio * (startColor.max.b - startColor.min.b);
329
460
  geometry.attributes.colorB.needsUpdate = true;
330
461
 
331
- geometry.attributes.colorA.array[particleIndex] = THREE.MathUtils.randFloat(
332
- startOpacity.min,
333
- startOpacity.max
334
- );
335
- geometry.attributes.colorA.needsUpdate = true;
336
-
337
462
  geometry.attributes.startFrame.array[particleIndex] =
338
463
  THREE.MathUtils.randInt(
339
464
  textureSheetAnimation.startFrame.min,
@@ -345,20 +470,31 @@ export const createParticleSystem = (
345
470
  THREE.MathUtils.randFloat(startLifetime.min, startLifetime.max) * 1000;
346
471
  geometry.attributes.startLifetime.needsUpdate = true;
347
472
 
348
- geometry.attributes.startSize.array[particleIndex] =
473
+ generalData.startValues.startSize[particleIndex] =
349
474
  THREE.MathUtils.randFloat(startSize.min, startSize.max);
350
- geometry.attributes.startSize.needsUpdate = true;
475
+ generalData.startValues.startOpacity[particleIndex] =
476
+ THREE.MathUtils.randFloat(startOpacity.min, startOpacity.max);
351
477
 
352
478
  geometry.attributes.rotation.array[particleIndex] = THREE.Math.degToRad(
353
479
  THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
354
480
  );
481
+
482
+ if (normalizedConfig.rotationOverLifetime.isActive)
483
+ generalData.lifetimeValues.rotationOverLifetime[particleIndex] =
484
+ THREE.MathUtils.randFloat(
485
+ normalizedConfig.rotationOverLifetime.min,
486
+ normalizedConfig.rotationOverLifetime.max
487
+ );
488
+
355
489
  geometry.attributes.rotation.needsUpdate = true;
490
+ geometry.attributes.colorB.needsUpdate = true;
356
491
 
357
492
  calculatePositionAndVelocity(
358
493
  shape,
359
494
  startSpeed,
360
495
  startPositions[particleIndex],
361
- velocities[particleIndex]
496
+ velocities[particleIndex],
497
+ velocityOverLifetime
362
498
  );
363
499
  const positionIndex = Math.floor(particleIndex * 3);
364
500
  geometry.attributes.position.array[positionIndex] =
@@ -367,23 +503,39 @@ export const createParticleSystem = (
367
503
  startPositions[particleIndex].y;
368
504
  geometry.attributes.position.array[positionIndex + 2] =
369
505
  startPositions[particleIndex].z;
370
- particleSystem.geometry.attributes.position.needsUpdate = true;
371
506
 
372
507
  geometry.attributes.lifetime.array[particleIndex] = 0;
373
508
  geometry.attributes.lifetime.needsUpdate = true;
509
+
510
+ applyModifiers({
511
+ delta: 0,
512
+ elapsed: 0,
513
+ noise: generalData.noise,
514
+ startValues: generalData.startValues,
515
+ lifetimeValues: generalData.lifetimeValues,
516
+ normalizedConfig,
517
+ attributes: particleSystem.geometry.attributes,
518
+ particleLifetimePercentage: 0,
519
+ particleIndex,
520
+ forceUpdate: true,
521
+ });
374
522
  };
375
523
 
376
524
  const particleSystem = new THREE.Points(geometry, material);
377
525
  particleSystem.sortParticles = true;
378
526
 
527
+ particleSystem.position.copy(transform.position);
528
+ particleSystem.rotation.x = THREE.Math.degToRad(transform.rotation.x);
529
+ particleSystem.rotation.y = THREE.Math.degToRad(transform.rotation.y);
530
+ particleSystem.rotation.z = THREE.Math.degToRad(transform.rotation.z);
531
+ particleSystem.scale.copy(transform.scale);
532
+
379
533
  const calculatedCreationTime =
380
534
  now + THREE.MathUtils.randFloat(startDelay.min, startDelay.max) * 1000;
381
535
 
382
536
  createdParticleSystems.push({
383
537
  particleSystem,
384
538
  generalData,
385
- lastWorldPosition,
386
- worldPositionChange,
387
539
  onUpdate,
388
540
  onComplete,
389
541
  creationTime: calculatedCreationTime,
@@ -393,6 +545,7 @@ export const createParticleSystem = (
393
545
  simulationSpace,
394
546
  gravity,
395
547
  emission,
548
+ normalizedConfig,
396
549
  iterationCount: 0,
397
550
  velocities,
398
551
  deactivateParticle,
@@ -417,8 +570,6 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
417
570
  const {
418
571
  onUpdate,
419
572
  generalData,
420
- lastWorldPosition,
421
- worldPositionChange,
422
573
  onComplete,
423
574
  particleSystem,
424
575
  creationTime,
@@ -426,6 +577,7 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
426
577
  duration,
427
578
  looping,
428
579
  emission,
580
+ normalizedConfig,
429
581
  iterationCount,
430
582
  velocities,
431
583
  deactivateParticle,
@@ -433,69 +585,106 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
433
585
  simulationSpace,
434
586
  gravity,
435
587
  } = props;
588
+
589
+ const {
590
+ lastWorldPosition,
591
+ currentWorldPosition,
592
+ worldPositionChange,
593
+ lastWorldQuaternion,
594
+ worldQuaternion,
595
+ worldEuler,
596
+ gravityVelocity,
597
+ } = generalData;
598
+
436
599
  const lifetime = now - creationTime;
437
600
  particleSystem.material.uniforms.elapsed.value = elapsed;
438
601
 
439
- if (
440
- lastWorldPosition.x !== -99999 &&
441
- lastWorldPosition.y !== -99999 &&
442
- lastWorldPosition.z !== -99999
443
- )
602
+ particleSystem.getWorldPosition(currentWorldPosition);
603
+ if (lastWorldPosition.x !== -99999)
444
604
  worldPositionChange.set(
445
- particleSystem.position.x - lastWorldPosition.x,
446
- particleSystem.position.y - lastWorldPosition.y,
447
- particleSystem.position.z - lastWorldPosition.z
605
+ currentWorldPosition.x - lastWorldPosition.x,
606
+ currentWorldPosition.y - lastWorldPosition.y,
607
+ currentWorldPosition.z - lastWorldPosition.z
448
608
  );
449
609
  generalData.distanceFromLastEmitByDistance += worldPositionChange.length();
450
- lastWorldPosition.copy(particleSystem.position);
610
+ particleSystem.getWorldPosition(lastWorldPosition);
611
+
612
+ particleSystem.getWorldQuaternion(worldQuaternion);
613
+ if (
614
+ lastWorldQuaternion.x === -99999 ||
615
+ lastWorldQuaternion.x != worldQuaternion.x ||
616
+ lastWorldQuaternion.y != worldQuaternion.y ||
617
+ lastWorldQuaternion.z != worldQuaternion.z
618
+ ) {
619
+ worldEuler.setFromQuaternion(worldQuaternion);
620
+ lastWorldQuaternion.copy(worldQuaternion);
621
+
622
+ const tempPosX = particleSystem.position.x;
623
+ const tempPosY = particleSystem.position.y;
624
+ const tempPosZ = particleSystem.position.z;
625
+ gravityVelocity.set(0, gravity, 0);
626
+ particleSystem.position.set(0, 0, 0);
627
+ particleSystem.updateMatrixWorld();
628
+ particleSystem.worldToLocal(gravityVelocity);
629
+ particleSystem.position.set(tempPosX, tempPosY, tempPosZ);
630
+ particleSystem.updateMatrixWorld();
631
+ }
632
+
633
+ generalData.creationTimes.forEach((entry, index) => {
634
+ if (particleSystem.geometry.attributes.isActive.array[index]) {
635
+ const particleLifetime = now - entry;
636
+ if (
637
+ particleLifetime >
638
+ particleSystem.geometry.attributes.startLifetime.array[index]
639
+ )
640
+ deactivateParticle(index);
641
+ else {
642
+ const velocity = velocities[index];
643
+ velocity.x -= gravityVelocity.x;
644
+ velocity.y -= gravityVelocity.y;
645
+ velocity.z -= gravityVelocity.z;
451
646
 
452
- particleSystem.geometry.attributes.creationTime.array.forEach(
453
- (entry, index) => {
454
- if (particleSystem.geometry.attributes.isActive.array[index]) {
455
- const particleLifetime = now - float32Helper - entry;
456
647
  if (
457
- particleLifetime >
458
- particleSystem.geometry.attributes.startLifetime.array[index]
459
- )
460
- deactivateParticle(index);
461
- else {
462
- const velocity = velocities[index];
463
- velocity.y -= gravity;
464
-
465
- if (
466
- gravity !== 0 ||
467
- velocity.x !== 0 ||
468
- velocity.y !== 0 ||
469
- velocity.z !== 0
470
- ) {
471
- const positionIndex = index * 3;
472
- const positionArr =
473
- particleSystem.geometry.attributes.position.array;
474
- if (simulationSpace === SimulationSpace.WORLD) {
475
- positionArr[positionIndex] -= worldPositionChange.x;
476
- positionArr[positionIndex + 1] -= worldPositionChange.y;
477
- positionArr[positionIndex + 2] -= worldPositionChange.z;
478
- }
479
- positionArr[positionIndex] += velocity.x * delta;
480
- positionArr[positionIndex + 1] += velocity.y * delta;
481
- positionArr[positionIndex + 2] += velocity.z * delta;
482
- particleSystem.geometry.attributes.position.needsUpdate = true;
648
+ gravity !== 0 ||
649
+ velocity.x !== 0 ||
650
+ velocity.y !== 0 ||
651
+ velocity.z !== 0
652
+ ) {
653
+ const positionIndex = index * 3;
654
+ const positionArr =
655
+ particleSystem.geometry.attributes.position.array;
656
+ if (simulationSpace === SimulationSpace.WORLD) {
657
+ positionArr[positionIndex] -= worldPositionChange.x;
658
+ positionArr[positionIndex + 1] -= worldPositionChange.y;
659
+ positionArr[positionIndex + 2] -= worldPositionChange.z;
483
660
  }
484
-
485
- particleSystem.geometry.attributes.lifetime.array[index] =
486
- particleLifetime;
487
- particleSystem.geometry.attributes.lifetime.needsUpdate = true;
488
-
489
- // TEMP
490
- particleSystem.geometry.attributes.colorA.array[index] =
491
- 1 -
492
- particleLifetime /
493
- particleSystem.geometry.attributes.startLifetime.array[index];
494
- particleSystem.geometry.attributes.colorA.needsUpdate = true;
661
+ positionArr[positionIndex] += velocity.x * delta;
662
+ positionArr[positionIndex + 1] += velocity.y * delta;
663
+ positionArr[positionIndex + 2] += velocity.z * delta;
664
+ particleSystem.geometry.attributes.position.needsUpdate = true;
495
665
  }
666
+
667
+ particleSystem.geometry.attributes.lifetime.array[index] =
668
+ particleLifetime;
669
+ particleSystem.geometry.attributes.lifetime.needsUpdate = true;
670
+
671
+ const particleLifetimePercentage =
672
+ particleLifetime /
673
+ particleSystem.geometry.attributes.startLifetime.array[index];
674
+ applyModifiers({
675
+ delta,
676
+ elapsed,
677
+ noise: generalData.noise,
678
+ startValues: generalData.startValues,
679
+ lifetimeValues: generalData.lifetimeValues,
680
+ normalizedConfig,
681
+ attributes: particleSystem.geometry.attributes,
682
+ particleLifetimePercentage,
683
+ particleIndex: index,
684
+ });
496
685
  }
497
686
  }
498
- );
687
+ });
499
688
 
500
689
  if (looping || lifetime < duration * 1000) {
501
690
  const emissionDelta = now - lastEmissionTime;