@newkrok/three-particles 0.2.0 → 0.3.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
@@ -1,6 +1,6 @@
1
1
  # THREE Particles
2
2
 
3
- Particle sytem for ThreeJS
3
+ Particle system for ThreeJS
4
4
 
5
5
  # THREE Particles Editor
6
6
 
@@ -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.0" ... }, `
21
+ `"dependencies": { ... "@newkrok/three-particles": "0.2.2" ... }, `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newkrok/three-particles",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Particle system for ThreeJS",
5
5
  "main": "src/js/three-particles.js",
6
6
  "bin": {
@@ -23,7 +23,8 @@
23
23
  },
24
24
  "homepage": "https://github.com/NewKrok/three-particles#readme",
25
25
  "dependencies": {
26
- "three": "^0.135.0"
26
+ "three": "0.135.0",
27
+ "easing-functions": "1.0.1"
27
28
  },
28
29
  "scripts": {
29
30
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -2,20 +2,30 @@ const ParticleSystemFragmentShader = `
2
2
  uniform sampler2D map;
3
3
  uniform float elapsed;
4
4
  uniform float fps;
5
+ uniform bool useFPSForFrameIndex;
5
6
  uniform vec2 tiles;
6
7
 
7
8
  varying vec4 vColor;
8
- varying float vLifeTime;
9
+ varying float vLifetime;
10
+ varying float vStartLifetime;
9
11
  varying float vRotation;
12
+ varying float vStartFrame;
10
13
 
11
14
  void main()
12
15
  {
13
16
  gl_FragColor = vColor;
14
17
  float mid = 0.5;
15
18
 
16
- float frameIndex = max(min(vLifeTime / 1000.0 * fps, tiles.x * tiles.y - 1.0), 0.0);
19
+ float frameIndex = round(vStartFrame) + (
20
+ useFPSForFrameIndex == true
21
+ ? fps == 0.0
22
+ ? 0.0
23
+ : max((vLifetime / 1000.0) * fps, 0.0)
24
+ : max(min(floor(min(vLifetime / vStartLifetime, 1.0) * (tiles.x * tiles.y)), tiles.x * tiles.y - 1.0), 0.0)
25
+ );
26
+
17
27
  float spriteXIndex = floor(mod(frameIndex, tiles.x));
18
- float spriteYIndex = floor(mod(frameIndex / tiles.y, tiles.y));
28
+ float spriteYIndex = floor(mod(frameIndex / tiles.x, tiles.y));
19
29
 
20
30
  vec2 frameUV = vec2(
21
31
  gl_PointCoord.x / tiles.x + spriteXIndex / tiles.x,
@@ -1,27 +1,33 @@
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;
6
6
  attribute float colorA;
7
- attribute float lifeTime;
7
+ attribute float lifetime;
8
+ attribute float startLifetime;
8
9
  attribute float rotation;
10
+ attribute float startFrame;
9
11
 
10
12
  varying mat4 vPosition;
11
13
  varying vec4 vColor;
12
- varying float vLifeTime;
14
+ varying float vLifetime;
15
+ varying float vStartLifetime;
13
16
  varying float vRotation;
17
+ varying float vStartFrame;
14
18
 
15
19
  void main()
16
20
  {
17
21
  vColor = vec4(colorR, colorG, colorB, colorA);
18
- vLifeTime = lifeTime;
22
+ vLifetime = lifetime;
23
+ vStartLifetime = startLifetime;
19
24
  vRotation = rotation;
25
+ vStartFrame = startFrame;
20
26
 
21
27
  vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
22
- gl_PointSize = startSize * (100.0 / length(mvPosition.xyz));
28
+ gl_PointSize = size * (100.0 / length(mvPosition.xyz));
23
29
  gl_Position = projectionMatrix * mvPosition;
24
30
  }
25
- `
31
+ `;
26
32
 
27
- export default ParticleSystemVertexShader
33
+ export default ParticleSystemVertexShader;
@@ -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,44 @@
1
+ import { getCurveFunction } from "./three-particles-curves.js";
2
+
3
+ const modifiers = [
4
+ // {key:"colorOverLifetime", attributeKeys:["colorR", "colorG", "colorB"]},
5
+ {
6
+ key: "opacityOverLifetime",
7
+ attributeKeys: ["colorA"],
8
+ startValueKeys: ["startOpacity"],
9
+ },
10
+ {
11
+ key: "sizeOverLifetime",
12
+ attributeKeys: ["size"],
13
+ startValueKeys: ["startSize"],
14
+ },
15
+ ];
16
+
17
+ export const applyModifiers = ({
18
+ startValues,
19
+ normalizedConfig,
20
+ attributes,
21
+ particleLifetimePercentage,
22
+ particleIndex,
23
+ forceUpdate = false,
24
+ }) => {
25
+ modifiers.forEach(({ key, attributeKeys, startValueKeys }) => {
26
+ const modifier = normalizedConfig[key];
27
+ if (modifier.isActive) {
28
+ const multiplier = getCurveFunction(modifier.curveFunction)(
29
+ particleLifetimePercentage
30
+ );
31
+ attributeKeys.forEach((attributeKey, index) => {
32
+ attributes[attributeKey].array[particleIndex] =
33
+ startValues[startValueKeys[index]][particleIndex] * multiplier;
34
+ attributes[attributeKey].needsUpdate = true;
35
+ });
36
+ } else if (forceUpdate) {
37
+ attributeKeys.forEach((attributeKey, index) => {
38
+ attributes[attributeKey].array[particleIndex] =
39
+ startValues[startValueKeys[index]][particleIndex];
40
+ attributes[attributeKey].needsUpdate = true;
41
+ });
42
+ }
43
+ });
44
+ };
@@ -1,14 +1,20 @@
1
1
  import * as THREE from "three/build/three.module.js";
2
2
 
3
- export const deepMerge = (objectA, objectB) => {
3
+ export const patchObject = (
4
+ objectA,
5
+ objectB,
6
+ config = { skippedProperties: [], applyToFirstObject: false }
7
+ ) => {
4
8
  const result = {};
5
9
  Object.keys(objectA).forEach((key) => {
6
- result[key] =
7
- typeof objectA[key] === "object" && objectA[key] && objectB[key]
8
- ? deepMerge(objectA[key], objectB[key])
9
- : objectB[key] === 0
10
- ? 0
11
- : objectB[key] || objectA[key];
10
+ if (!config.skippedProperties || !config.skippedProperties.includes(key)) {
11
+ if (typeof objectA[key] === "object" && objectA[key] && objectB[key]) {
12
+ result[key] = patchObject(objectA[key], objectB[key], config);
13
+ } else {
14
+ result[key] = objectB[key] === 0 ? 0 : objectB[key] || objectA[key];
15
+ if (config.applyToFirstObject) objectA[key] = result[key];
16
+ }
17
+ }
12
18
  });
13
19
  return result;
14
20
  };
@@ -94,7 +100,7 @@ export const calculateRandomPositionAndVelocityOnCone = (
94
100
  sinNormalizedAngle *
95
101
  speedMultiplierByPosition *
96
102
  randomizedSpeed,
97
- -Math.cos(normalizedAngle) * randomizedSpeed
103
+ Math.cos(normalizedAngle) * randomizedSpeed
98
104
  );
99
105
  };
100
106
 
@@ -151,5 +157,5 @@ export const calculateRandomPositionAndVelocityOnRectangle = (
151
157
  startSpeed.min,
152
158
  startSpeed.max
153
159
  );
154
- velocity.set(0, 0, -randomizedSpeed);
160
+ velocity.set(0, 0, randomizedSpeed);
155
161
  };
@@ -5,11 +5,13 @@ 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";
11
+ import { CurveFunction } from "./three-particles/three-particles-curves.js";
12
+ import ParticleSystemFragmentShader from "./three-particles/shaders/particle-system-fragment-shader.glsl.js";
13
+ import ParticleSystemVertexShader from "./three-particles/shaders/particle-system-vertex-shader.glsl.js";
14
+ import { applyModifiers } from "./three-particles/three-particles-modifiers.js";
13
15
 
14
16
  // Float32Array is not enough accurate when we are storing timestamp in it so we just remove unnecessary time
15
17
  const float32Helper = 1638200000000;
@@ -29,14 +31,24 @@ export const Shape = {
29
31
  RECTANGLE: "RECTANGLE",
30
32
  };
31
33
 
34
+ export const TimeMode = {
35
+ LIFETIME: "LIFETIME",
36
+ FPS: "FPS",
37
+ };
38
+
32
39
  export const getDefaultParticleSystemConfig = () =>
33
40
  JSON.parse(JSON.stringify(DEFAULT_PARTICLE_SYSTEM_CONFIG));
34
41
 
35
42
  const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
43
+ transform: {
44
+ position: { x: 0, y: 0, z: 0 },
45
+ rotation: { x: 0, y: 0, z: 0 },
46
+ scale: { x: 1, y: 1, z: 1 },
47
+ },
36
48
  duration: 5.0,
37
49
  looping: true,
38
50
  startDelay: { min: 0.0, max: 0.0 },
39
- startLifeTime: { min: 2.0, max: 2.0 },
51
+ startLifetime: { min: 2.0, max: 2.0 },
40
52
  startSpeed: { min: 1.0, max: 1.0 },
41
53
  startSize: { min: 1.0, max: 1.0 },
42
54
  startRotation: { min: 0.0, max: 0.0 },
@@ -45,7 +57,7 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
45
57
  max: { r: 1.0, g: 1.0, b: 1.0 },
46
58
  },
47
59
  startOpacity: { min: 1.0, max: 1.0 },
48
- gravity: 0,
60
+ gravity: 0.0,
49
61
  simulationSpace: SimulationSpace.LOCAL,
50
62
  maxParticles: 100.0,
51
63
  emission: {
@@ -76,7 +88,24 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
76
88
  },
77
89
  },
78
90
  map: null,
79
- textureSheetAnimation: { tiles: new THREE.Vector2(1.0, 1.0), fps: 30.0 },
91
+ sizeOverLifetime: {
92
+ isActive: false,
93
+ curveFunction: CurveFunction.LINEAR,
94
+ },
95
+ /* colorOverLifetime: {
96
+ isActive: false,
97
+ curveFunction: CurveFunction.LINEAR,
98
+ }, */
99
+ opacityOverLifetime: {
100
+ isActive: false,
101
+ curveFunction: CurveFunction.LINEAR,
102
+ },
103
+ textureSheetAnimation: {
104
+ tiles: new THREE.Vector2(1.0, 1.0),
105
+ timeMode: TimeMode.LIFETIME,
106
+ fps: 10.0,
107
+ startFrame: { min: 0.0, max: 0.0 },
108
+ },
80
109
  };
81
110
 
82
111
  const createFloat32Attributes = ({
@@ -148,15 +177,25 @@ export const createParticleSystem = (
148
177
  config = DEFAULT_PARTICLE_SYSTEM_CONFIG
149
178
  ) => {
150
179
  const now = Date.now();
151
- const lastWorldPosition = new THREE.Vector3(-99999, -99999, -99999);
152
- const worldPositionChange = new THREE.Vector3();
153
- const generalData = { distanceFromLastEmitByDistance: 0 };
180
+ const generalData = {
181
+ distanceFromLastEmitByDistance: 0,
182
+ lastWorldPosition: new THREE.Vector3(-99999),
183
+ currentWorldPosition: new THREE.Vector3(-99999),
184
+ worldPositionChange: new THREE.Vector3(),
185
+ worldQuaternion: new THREE.Quaternion(),
186
+ lastWorldQuaternion: new THREE.Quaternion(-99999),
187
+ worldEuler: new THREE.Euler(),
188
+ gravityVelocity: new THREE.Vector3(0, 0, 0),
189
+ startValues: {},
190
+ };
154
191
 
192
+ const normalizedConfig = patchObject(DEFAULT_PARTICLE_SYSTEM_CONFIG, config);
155
193
  const {
194
+ transform,
156
195
  duration,
157
196
  looping,
158
197
  startDelay,
159
- startLifeTime,
198
+ startLifetime,
160
199
  startSpeed,
161
200
  startSize,
162
201
  startRotation,
@@ -171,7 +210,7 @@ export const createParticleSystem = (
171
210
  onUpdate,
172
211
  onComplete,
173
212
  textureSheetAnimation,
174
- } = deepMerge(DEFAULT_PARTICLE_SYSTEM_CONFIG, config);
213
+ } = normalizedConfig;
175
214
 
176
215
  const startPositions = Array.from(
177
216
  { length: maxParticles },
@@ -182,13 +221,15 @@ export const createParticleSystem = (
182
221
  () => new THREE.Vector3()
183
222
  );
184
223
 
185
- const uniforms = Object.keys(textureSheetAnimation).reduce(
186
- (prev, key) => ({
187
- ...prev,
188
- [key]: { value: textureSheetAnimation[key] },
189
- }),
190
- {}
191
- );
224
+ const startValueKeys = ["startSize", "startOpacity"];
225
+ startValueKeys.forEach((key) => {
226
+ generalData.startValues[key] = Array.from({ length: maxParticles }, () =>
227
+ THREE.MathUtils.randFloat(
228
+ normalizedConfig[key].min,
229
+ normalizedConfig[key].max
230
+ )
231
+ );
232
+ });
192
233
 
193
234
  const material = new THREE.ShaderMaterial({
194
235
  uniforms: {
@@ -198,7 +239,15 @@ export const createParticleSystem = (
198
239
  map: {
199
240
  value: map,
200
241
  },
201
- ...uniforms,
242
+ tiles: {
243
+ value: textureSheetAnimation.tiles,
244
+ },
245
+ fps: {
246
+ value: textureSheetAnimation.fps,
247
+ },
248
+ useFPSForFrameIndex: {
249
+ value: textureSheetAnimation.timeMode === TimeMode.FPS,
250
+ },
202
251
  },
203
252
  vertexShader: ParticleSystemVertexShader,
204
253
  fragmentShader: ParticleSystemFragmentShader,
@@ -235,29 +284,29 @@ export const createParticleSystem = (
235
284
 
236
285
  createFloat32AttributesRequest("isActive", false);
237
286
  createFloat32AttributesRequest("creationTime", 0);
238
- createFloat32AttributesRequest("lifeTime", 0);
239
- createFloat32AttributesRequest("startLifeTime", () =>
240
- THREE.MathUtils.randFloat(startLifeTime.min, startLifeTime.max)
287
+ createFloat32AttributesRequest("lifetime", 0);
288
+ createFloat32AttributesRequest("startLifetime", () =>
289
+ THREE.MathUtils.randFloat(startLifetime.min, startLifetime.max)
290
+ );
291
+ createFloat32AttributesRequest("startFrame", () =>
292
+ THREE.MathUtils.randInt(
293
+ textureSheetAnimation.startFrame.min,
294
+ textureSheetAnimation.startFrame.max
295
+ )
241
296
  );
242
297
 
243
298
  createFloat32AttributesRequest("opacity", 0);
244
299
 
245
- createFloat32Attributes({
246
- geometry,
247
- propertyName: "rotation",
248
- maxParticles,
249
- factory: () =>
250
- THREE.Math.degToRad(
251
- THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
252
- ),
253
- });
300
+ createFloat32AttributesRequest("rotation", () =>
301
+ THREE.Math.degToRad(
302
+ THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
303
+ )
304
+ );
254
305
 
255
- createFloat32Attributes({
256
- geometry,
257
- propertyName: "startSize",
258
- maxParticles,
259
- factory: () => THREE.MathUtils.randFloat(startSize.min, startSize.max),
260
- });
306
+ createFloat32AttributesRequest(
307
+ "size",
308
+ (_, index) => generalData.startValues.startSize[index]
309
+ );
261
310
 
262
311
  createFloat32AttributesRequest("rotation", 0);
263
312
 
@@ -284,8 +333,6 @@ export const createParticleSystem = (
284
333
 
285
334
  const deactivateParticle = (particleIndex) => {
286
335
  geometry.attributes.isActive.array[particleIndex] = false;
287
- geometry.attributes.lifeTime.array[particleIndex] = 0;
288
- geometry.attributes.lifeTime.needsUpdate = true;
289
336
  geometry.attributes.colorA.array[particleIndex] = 0;
290
337
  geometry.attributes.colorA.needsUpdate = true;
291
338
  };
@@ -312,24 +359,28 @@ export const createParticleSystem = (
312
359
  colorRandomRatio * (startColor.max.b - startColor.min.b);
313
360
  geometry.attributes.colorB.needsUpdate = true;
314
361
 
315
- geometry.attributes.colorA.array[particleIndex] = THREE.MathUtils.randFloat(
316
- startOpacity.min,
317
- startOpacity.max
318
- );
319
- geometry.attributes.colorA.needsUpdate = true;
362
+ geometry.attributes.startFrame.array[particleIndex] =
363
+ THREE.MathUtils.randInt(
364
+ textureSheetAnimation.startFrame.min,
365
+ textureSheetAnimation.startFrame.max
366
+ );
367
+ geometry.attributes.startFrame.needsUpdate = true;
320
368
 
321
- geometry.attributes.startLifeTime.array[particleIndex] =
322
- THREE.MathUtils.randFloat(startLifeTime.min, startLifeTime.max) * 1000;
323
- geometry.attributes.startLifeTime.needsUpdate = true;
369
+ geometry.attributes.startLifetime.array[particleIndex] =
370
+ THREE.MathUtils.randFloat(startLifetime.min, startLifetime.max) * 1000;
371
+ geometry.attributes.startLifetime.needsUpdate = true;
324
372
 
325
- geometry.attributes.startSize.array[particleIndex] =
373
+ generalData.startValues.startSize[particleIndex] =
326
374
  THREE.MathUtils.randFloat(startSize.min, startSize.max);
327
- geometry.attributes.startSize.needsUpdate = true;
375
+ generalData.startValues.startOpacity[particleIndex] =
376
+ THREE.MathUtils.randFloat(startOpacity.min, startOpacity.max);
328
377
 
329
378
  geometry.attributes.rotation.array[particleIndex] = THREE.Math.degToRad(
330
379
  THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
331
380
  );
381
+
332
382
  geometry.attributes.rotation.needsUpdate = true;
383
+ geometry.attributes.colorB.needsUpdate = true;
333
384
 
334
385
  calculatePositionAndVelocity(
335
386
  shape,
@@ -346,21 +397,34 @@ export const createParticleSystem = (
346
397
  startPositions[particleIndex].z;
347
398
  particleSystem.geometry.attributes.position.needsUpdate = true;
348
399
 
349
- geometry.attributes.lifeTime.array[particleIndex] = 0;
350
- geometry.attributes.lifeTime.needsUpdate = true;
400
+ geometry.attributes.lifetime.array[particleIndex] = 0;
401
+ geometry.attributes.lifetime.needsUpdate = true;
402
+
403
+ applyModifiers({
404
+ startValues: generalData.startValues,
405
+ normalizedConfig,
406
+ attributes: particleSystem.geometry.attributes,
407
+ particleLifetimePercentage: 0,
408
+ particleIndex,
409
+ forceUpdate: true,
410
+ });
351
411
  };
352
412
 
353
413
  const particleSystem = new THREE.Points(geometry, material);
354
414
  particleSystem.sortParticles = true;
355
415
 
416
+ particleSystem.position.copy(transform.position);
417
+ particleSystem.rotation.x = THREE.Math.degToRad(transform.rotation.x);
418
+ particleSystem.rotation.y = THREE.Math.degToRad(transform.rotation.y);
419
+ particleSystem.rotation.z = THREE.Math.degToRad(transform.rotation.z);
420
+ particleSystem.scale.copy(transform.scale);
421
+
356
422
  const calculatedCreationTime =
357
423
  now + THREE.MathUtils.randFloat(startDelay.min, startDelay.max) * 1000;
358
424
 
359
425
  createdParticleSystems.push({
360
426
  particleSystem,
361
427
  generalData,
362
- lastWorldPosition,
363
- worldPositionChange,
364
428
  onUpdate,
365
429
  onComplete,
366
430
  creationTime: calculatedCreationTime,
@@ -370,6 +434,7 @@ export const createParticleSystem = (
370
434
  simulationSpace,
371
435
  gravity,
372
436
  emission,
437
+ normalizedConfig,
373
438
  iterationCount: 0,
374
439
  velocities,
375
440
  deactivateParticle,
@@ -389,14 +454,11 @@ export const destroyParticleSystem = (particleSystem) => {
389
454
  particleSystem.parent.remove(particleSystem);
390
455
  };
391
456
 
392
- export const updateParticleSystems = ({ delta, elapsed }) => {
393
- const now = Date.now();
457
+ export const updateParticleSystems = ({ now, delta, elapsed }) => {
394
458
  createdParticleSystems.forEach((props) => {
395
459
  const {
396
460
  onUpdate,
397
461
  generalData,
398
- lastWorldPosition,
399
- worldPositionChange,
400
462
  onComplete,
401
463
  particleSystem,
402
464
  creationTime,
@@ -404,6 +466,7 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
404
466
  duration,
405
467
  looping,
406
468
  emission,
469
+ normalizedConfig,
407
470
  iterationCount,
408
471
  velocities,
409
472
  deactivateParticle,
@@ -411,34 +474,65 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
411
474
  simulationSpace,
412
475
  gravity,
413
476
  } = props;
414
- const lifeTime = now - creationTime;
477
+
478
+ const {
479
+ lastWorldPosition,
480
+ currentWorldPosition,
481
+ worldPositionChange,
482
+ lastWorldQuaternion,
483
+ worldQuaternion,
484
+ worldEuler,
485
+ gravityVelocity,
486
+ } = generalData;
487
+
488
+ const lifetime = now - creationTime;
415
489
  particleSystem.material.uniforms.elapsed.value = elapsed;
416
490
 
417
- if (
418
- lastWorldPosition.x !== -99999 &&
419
- lastWorldPosition.y !== -99999 &&
420
- lastWorldPosition.z !== -99999
421
- )
491
+ particleSystem.getWorldPosition(currentWorldPosition);
492
+ if (lastWorldPosition.x !== -99999)
422
493
  worldPositionChange.set(
423
- particleSystem.position.x - lastWorldPosition.x,
424
- particleSystem.position.y - lastWorldPosition.y,
425
- particleSystem.position.z - lastWorldPosition.z
494
+ currentWorldPosition.x - lastWorldPosition.x,
495
+ currentWorldPosition.y - lastWorldPosition.y,
496
+ currentWorldPosition.z - lastWorldPosition.z
426
497
  );
427
498
  generalData.distanceFromLastEmitByDistance += worldPositionChange.length();
428
- lastWorldPosition.copy(particleSystem.position);
499
+ particleSystem.getWorldPosition(lastWorldPosition);
500
+
501
+ particleSystem.getWorldQuaternion(worldQuaternion);
502
+ if (
503
+ lastWorldQuaternion.x === -99999 ||
504
+ lastWorldQuaternion.x != worldQuaternion.x ||
505
+ lastWorldQuaternion.y != worldQuaternion.y ||
506
+ lastWorldQuaternion.z != worldQuaternion.z
507
+ ) {
508
+ worldEuler.setFromQuaternion(worldQuaternion);
509
+ lastWorldQuaternion.copy(worldQuaternion);
510
+
511
+ const tempPosX = particleSystem.position.x;
512
+ const tempPosY = particleSystem.position.y;
513
+ const tempPosZ = particleSystem.position.z;
514
+ gravityVelocity.set(0, gravity, 0);
515
+ particleSystem.position.set(0, 0, 0);
516
+ particleSystem.updateMatrixWorld();
517
+ particleSystem.worldToLocal(gravityVelocity);
518
+ particleSystem.position.set(tempPosX, tempPosY, tempPosZ);
519
+ particleSystem.updateMatrixWorld();
520
+ }
429
521
 
430
522
  particleSystem.geometry.attributes.creationTime.array.forEach(
431
523
  (entry, index) => {
432
524
  if (particleSystem.geometry.attributes.isActive.array[index]) {
433
- const particleLifeTime = now - float32Helper - entry;
525
+ const particleLifetime = now - float32Helper - entry;
434
526
  if (
435
- particleLifeTime >
436
- particleSystem.geometry.attributes.startLifeTime.array[index]
527
+ particleLifetime >
528
+ particleSystem.geometry.attributes.startLifetime.array[index]
437
529
  )
438
530
  deactivateParticle(index);
439
531
  else {
440
532
  const velocity = velocities[index];
441
- velocity.y -= gravity;
533
+ velocity.x -= gravityVelocity.x;
534
+ velocity.y -= gravityVelocity.y;
535
+ velocity.z -= gravityVelocity.z;
442
536
 
443
537
  if (
444
538
  gravity !== 0 ||
@@ -460,22 +554,26 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
460
554
  particleSystem.geometry.attributes.position.needsUpdate = true;
461
555
  }
462
556
 
463
- particleSystem.geometry.attributes.lifeTime.array[index] =
464
- particleLifeTime;
465
- particleSystem.geometry.attributes.lifeTime.needsUpdate = true;
466
-
467
- // TEMP
468
- particleSystem.geometry.attributes.colorA.array[index] =
469
- 1 -
470
- particleLifeTime /
471
- particleSystem.geometry.attributes.startLifeTime.array[index];
472
- particleSystem.geometry.attributes.colorA.needsUpdate = true;
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
+ });
473
571
  }
474
572
  }
475
573
  }
476
574
  );
477
575
 
478
- if (looping || lifeTime < duration * 1000) {
576
+ if (looping || lifetime < duration * 1000) {
479
577
  const emissionDelta = now - lastEmissionTime;
480
578
  const neededParticlesByTime = Math.floor(
481
579
  emission.rateOverTime * (emissionDelta / 1000)
@@ -523,7 +621,7 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
523
621
  particleSystem,
524
622
  delta,
525
623
  elapsed,
526
- lifeTime,
624
+ lifetime,
527
625
  iterationCount: iterationCount + 1,
528
626
  });
529
627
  } else if (onComplete)