@newkrok/three-particles 0.0.1 → 0.1.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
@@ -6,3 +6,18 @@ You can create your own particle effects with it's editor https://github.com/New
6
6
 
7
7
  # Live demo
8
8
  https://newkrok.com/three-particles-editor/index.html
9
+
10
+ # Install
11
+ npm package https://www.npmjs.com/package/@newkrok/three-particles
12
+
13
+ Install with npm
14
+ `npm i @newkrok/three-particles`
15
+
16
+ Add as a package.json dependency
17
+ `
18
+ "dependencies": {
19
+ ...
20
+ "@newkrok/three-particles": "0.0.1"
21
+ ...
22
+ },
23
+ `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newkrok/three-particles",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
4
  "description": "Particle system for ThreeJS",
5
5
  "main": "src/js/three-particles.js",
6
6
  "bin": {
@@ -0,0 +1,121 @@
1
+ import * as THREE from "three/build/three.module.js";
2
+
3
+ export const calculateRandomPositionAndVelocityOnSphere = (
4
+ position,
5
+ velocity,
6
+ startSpeed,
7
+ { radius, radiusThickness, arc }
8
+ ) => {
9
+ const u = Math.random() * (arc / 360);
10
+ const v = Math.random();
11
+ const randomizedDistanceRatio = Math.random();
12
+ const theta = 2 * Math.PI * u;
13
+ const phi = Math.acos(2 * v - 1);
14
+ const sinPhi = Math.sin(phi);
15
+
16
+ const xDirection = sinPhi * Math.cos(theta);
17
+ const yDirection = sinPhi * Math.sin(theta);
18
+ const zDirection = Math.cos(phi);
19
+ const normalizedThickness = 1 - radiusThickness;
20
+
21
+ position.x =
22
+ radius * normalizedThickness * xDirection +
23
+ radius * radiusThickness * randomizedDistanceRatio * xDirection;
24
+ position.y =
25
+ radius * normalizedThickness * yDirection +
26
+ radius * radiusThickness * randomizedDistanceRatio * yDirection;
27
+ position.z =
28
+ radius * normalizedThickness * zDirection +
29
+ radius * radiusThickness * randomizedDistanceRatio * zDirection;
30
+
31
+ const randomizedSpeed = THREE.MathUtils.randFloat(
32
+ startSpeed.min,
33
+ startSpeed.max
34
+ );
35
+ const speedMultiplierByPosition = 1 / position.length();
36
+ velocity.set(
37
+ position.x * speedMultiplierByPosition * randomizedSpeed,
38
+ position.y * speedMultiplierByPosition * randomizedSpeed,
39
+ position.z * speedMultiplierByPosition * randomizedSpeed
40
+ );
41
+ };
42
+
43
+ export const calculateRandomPositionAndVelocityOnCone = (
44
+ position,
45
+ velocity,
46
+ startSpeed,
47
+ { radius, radiusThickness, arc, angle = 90 }
48
+ ) => {
49
+ const theta = 2 * Math.PI * Math.random() * (arc / 360);
50
+ const randomizedDistanceRatio = Math.random();
51
+
52
+ const xDirection = Math.cos(theta);
53
+ const yDirection = Math.sin(theta);
54
+ const normalizedThickness = 1 - radiusThickness;
55
+
56
+ position.x =
57
+ radius * normalizedThickness * xDirection +
58
+ radius * radiusThickness * randomizedDistanceRatio * xDirection;
59
+ position.y =
60
+ radius * normalizedThickness * yDirection +
61
+ radius * radiusThickness * randomizedDistanceRatio * yDirection;
62
+ position.z = 0;
63
+
64
+ const positionLength = position.length();
65
+ const normalizedAngle = Math.abs(
66
+ (positionLength / radius) * THREE.Math.degToRad(angle)
67
+ );
68
+ const sinNormalizedAngle = Math.sin(normalizedAngle);
69
+
70
+ const randomizedSpeed = THREE.MathUtils.randFloat(
71
+ startSpeed.min,
72
+ startSpeed.max
73
+ );
74
+ const speedMultiplierByPosition = 1 / positionLength;
75
+ velocity.set(
76
+ position.x *
77
+ sinNormalizedAngle *
78
+ speedMultiplierByPosition *
79
+ randomizedSpeed,
80
+ position.y *
81
+ sinNormalizedAngle *
82
+ speedMultiplierByPosition *
83
+ randomizedSpeed,
84
+ -Math.cos(normalizedAngle) * randomizedSpeed
85
+ );
86
+ };
87
+
88
+ export const calculateRandomPositionAndVelocityOnCircle = (
89
+ position,
90
+ velocity,
91
+ startSpeed,
92
+ { radius, radiusThickness, arc }
93
+ ) => {
94
+ const theta = 2 * Math.PI * Math.random() * (arc / 360);
95
+ const randomizedDistanceRatio = Math.random();
96
+
97
+ const xDirection = Math.cos(theta);
98
+ const yDirection = Math.sin(theta);
99
+ const normalizedThickness = 1 - radiusThickness;
100
+
101
+ position.x =
102
+ radius * normalizedThickness * xDirection +
103
+ radius * radiusThickness * randomizedDistanceRatio * xDirection;
104
+ position.y =
105
+ radius * normalizedThickness * yDirection +
106
+ radius * radiusThickness * randomizedDistanceRatio * yDirection;
107
+ position.z = 0;
108
+
109
+ const positionLength = position.length();
110
+
111
+ const randomizedSpeed = THREE.MathUtils.randFloat(
112
+ startSpeed.min,
113
+ startSpeed.max
114
+ );
115
+ const speedMultiplierByPosition = 1 / positionLength;
116
+ velocity.set(
117
+ position.x * speedMultiplierByPosition * randomizedSpeed,
118
+ position.y * speedMultiplierByPosition * randomizedSpeed,
119
+ 0
120
+ );
121
+ };
@@ -1,5 +1,11 @@
1
1
  import * as THREE from "three/build/three.module.js";
2
2
 
3
+ import {
4
+ calculateRandomPositionAndVelocityOnCircle,
5
+ calculateRandomPositionAndVelocityOnCone,
6
+ calculateRandomPositionAndVelocityOnSphere,
7
+ } from "./three-particles-utils.js";
8
+
3
9
  import ParticleSystemFragmentShader from "./shaders/particle-system-fragment-shader.glsl.js";
4
10
  import ParticleSystemVertexShader from "./shaders/particle-system-vertex-shader.glsl.js";
5
11
 
@@ -46,34 +52,40 @@ const createFloat32Attributes = ({
46
52
  );
47
53
  };
48
54
 
49
- const getRandomStartPositionByShape = ({ shape, radius, radiusThickness }) => {
50
- const position = new THREE.Vector3();
55
+ const calculatePositionAndVelocity = (
56
+ { shape, sphere, cone, circle },
57
+ startSpeed,
58
+ position,
59
+ velocity
60
+ ) => {
51
61
  switch (shape) {
52
- case Shape.SPHERE: {
53
- const u = Math.random();
54
- const v = Math.random();
55
- const randomizedDistanceRatio = Math.random();
56
- const theta = 2 * Math.PI * u;
57
- const phi = Math.acos(2 * v - 1);
58
- const xDirection = Math.sin(phi) * Math.cos(theta);
59
- const yDirection = Math.sin(phi) * Math.sin(theta);
60
- const zDirection = Math.cos(phi);
61
- const normalizedThickness = 1 - radiusThickness;
62
-
63
- position.x =
64
- radius * normalizedThickness * xDirection +
65
- radius * radiusThickness * randomizedDistanceRatio * xDirection;
66
- position.y =
67
- radius * normalizedThickness * yDirection +
68
- radius * radiusThickness * randomizedDistanceRatio * yDirection;
69
- position.z =
70
- radius * normalizedThickness * zDirection +
71
- radius * radiusThickness * randomizedDistanceRatio * zDirection;
62
+ case Shape.SPHERE:
63
+ calculateRandomPositionAndVelocityOnSphere(
64
+ position,
65
+ velocity,
66
+ startSpeed,
67
+ sphere
68
+ );
69
+ break;
70
+
71
+ case Shape.CONE:
72
+ calculateRandomPositionAndVelocityOnCone(
73
+ position,
74
+ velocity,
75
+ startSpeed,
76
+ cone
77
+ );
72
78
  break;
73
- }
74
- }
75
79
 
76
- return position;
80
+ case Shape.CIRCLE:
81
+ calculateRandomPositionAndVelocityOnCircle(
82
+ position,
83
+ velocity,
84
+ startSpeed,
85
+ circle
86
+ );
87
+ break;
88
+ }
77
89
  };
78
90
 
79
91
  export const createParticleSystem = ({
@@ -96,12 +108,7 @@ export const createParticleSystem = ({
96
108
  rateOverTime: 10.0,
97
109
  rateOverDistance: 0.0,
98
110
  },
99
- shape = {
100
- shape: Shape.SPHERE,
101
- radius: 1.0,
102
- radiusThickness: 1.0,
103
- arc: 360.0,
104
- },
111
+ shape,
105
112
  map,
106
113
  onUpdate = null,
107
114
  onComplete = null,
@@ -129,12 +136,37 @@ export const createParticleSystem = ({
129
136
  };
130
137
  const normalizedShape = {
131
138
  shape: Shape.SPHERE,
132
- radius: 1.0,
133
- radiusThickness: 1.0,
134
- arc: 360.0,
139
+ sphere: {
140
+ radius: 1.0,
141
+ radiusThickness: 1.0,
142
+ arc: 360.0,
143
+ ...shape.sphere,
144
+ },
145
+ cone: {
146
+ angle: 25,
147
+ radius: 1.0,
148
+ radiusThickness: 1.0,
149
+ arc: 360.0,
150
+ ...shape.cone,
151
+ },
152
+ circle: {
153
+ radius: 1.0,
154
+ radiusThickness: 1.0,
155
+ arc: 360.0,
156
+ ...shape.circle,
157
+ },
135
158
  ...shape,
136
159
  };
137
160
 
161
+ const startPositions = Array.from(
162
+ { length: maxParticles },
163
+ () => new THREE.Vector3()
164
+ );
165
+ const velocities = Array.from(
166
+ { length: maxParticles },
167
+ () => new THREE.Vector3()
168
+ );
169
+
138
170
  const rawUniforms = {
139
171
  ...defaultTextureSheetAnimation,
140
172
  ...textureSheetAnimation,
@@ -171,9 +203,18 @@ export const createParticleSystem = ({
171
203
 
172
204
  const geometry = new THREE.BufferGeometry();
173
205
 
174
- const startPosition = getRandomStartPositionByShape(normalizedShape);
206
+ for (let i = 0; i < maxParticles; i++)
207
+ calculatePositionAndVelocity(
208
+ normalizedShape,
209
+ normalizedStartSpeed,
210
+ startPositions[i],
211
+ velocities[i]
212
+ );
213
+
175
214
  geometry.setFromPoints(
176
- Array.from({ length: maxParticles }, () => ({ ...startPosition }))
215
+ Array.from({ length: maxParticles }, (_, index) => ({
216
+ ...startPositions[index],
217
+ }))
177
218
  );
178
219
 
179
220
  const createFloat32AttributesRequest = (propertyName, factory) => {
@@ -194,23 +235,6 @@ export const createParticleSystem = ({
194
235
  normalizedStartLifeTime.max
195
236
  )
196
237
  );
197
- const randomizedSpeed = THREE.MathUtils.randFloat(
198
- normalizedStartSpeed.min,
199
- normalizedStartSpeed.max
200
- );
201
- const speedMultiplierByPosition = startPosition.length() / 1;
202
- createFloat32AttributesRequest(
203
- "velocityX",
204
- () => startPosition.x * speedMultiplierByPosition * randomizedSpeed
205
- );
206
- createFloat32AttributesRequest(
207
- "velocityY",
208
- () => startPosition.y * speedMultiplierByPosition * randomizedSpeed
209
- );
210
- createFloat32AttributesRequest(
211
- "velocityZ",
212
- () => startPosition.z * speedMultiplierByPosition * randomizedSpeed
213
- );
214
238
 
215
239
  createFloat32AttributesRequest("opacity", 0);
216
240
 
@@ -325,26 +349,20 @@ export const createParticleSystem = ({
325
349
  );
326
350
  geometry.attributes.rotation.needsUpdate = true;
327
351
 
328
- const startPosition = getRandomStartPositionByShape(normalizedShape);
329
- geometry.attributes.position.array[Math.floor(particleIndex * 3)] =
330
- startPosition.x;
331
- geometry.attributes.position.array[Math.floor(particleIndex * 3) + 1] =
332
- startPosition.y;
333
- geometry.attributes.position.array[Math.floor(particleIndex * 3) + 2] =
334
- startPosition.z;
335
- particleSystem.geometry.attributes.position.needsUpdate = true;
336
-
337
- const randomizedSpeed = THREE.MathUtils.randFloat(
338
- normalizedStartSpeed.min,
339
- normalizedStartSpeed.max
352
+ calculatePositionAndVelocity(
353
+ normalizedShape,
354
+ normalizedStartSpeed,
355
+ startPositions[particleIndex],
356
+ velocities[particleIndex]
340
357
  );
341
- const speedMultiplierByPosition = 1 / startPosition.length();
342
- geometry.attributes.velocityX.array[particleIndex] =
343
- startPosition.x * speedMultiplierByPosition * randomizedSpeed;
344
- geometry.attributes.velocityY.array[particleIndex] =
345
- startPosition.y * speedMultiplierByPosition * randomizedSpeed;
346
- geometry.attributes.velocityZ.array[particleIndex] =
347
- startPosition.z * speedMultiplierByPosition * randomizedSpeed;
358
+ const positionIndex = Math.floor(particleIndex * 3);
359
+ geometry.attributes.position.array[positionIndex] =
360
+ startPositions[particleIndex].x;
361
+ geometry.attributes.position.array[positionIndex + 1] =
362
+ startPositions[particleIndex].y;
363
+ geometry.attributes.position.array[positionIndex + 2] =
364
+ startPositions[particleIndex].z;
365
+ particleSystem.geometry.attributes.position.needsUpdate = true;
348
366
 
349
367
  geometry.attributes.lifeTime.array[particleIndex] = 0;
350
368
  geometry.attributes.lifeTime.needsUpdate = true;
@@ -376,6 +394,7 @@ export const createParticleSystem = ({
376
394
  gravity,
377
395
  emission: normalizedEmission,
378
396
  iterationCount: 0,
397
+ velocities,
379
398
  deactivateParticle,
380
399
  activateParticle,
381
400
  });
@@ -409,6 +428,7 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
409
428
  looping,
410
429
  emission,
411
430
  iterationCount,
431
+ velocities,
412
432
  deactivateParticle,
413
433
  activateParticle,
414
434
  simulationSpace,
@@ -440,29 +460,26 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
440
460
  )
441
461
  deactivateParticle(index);
442
462
  else {
443
- const accelerationX =
444
- particleSystem.geometry.attributes.velocityX.array[index];
445
-
446
- const accelerationY =
447
- particleSystem.geometry.attributes.velocityY.array[index] -
448
- gravity;
449
- particleSystem.geometry.attributes.velocityY.array[index] =
450
- accelerationY;
451
-
452
- const accelerationZ =
453
- particleSystem.geometry.attributes.velocityZ.array[index];
454
-
455
- if (gravity !== 0 || accelerationX !== 0 || accelerationY !== 0) {
463
+ const velocity = velocities[index];
464
+ velocity.y -= gravity;
465
+
466
+ if (
467
+ gravity !== 0 ||
468
+ velocity.x !== 0 ||
469
+ velocity.y !== 0 ||
470
+ velocity.z !== 0
471
+ ) {
472
+ const positionIndex = index * 3;
456
473
  const positionArr =
457
474
  particleSystem.geometry.attributes.position.array;
458
475
  if (simulationSpace === SimulationSpace.WORLD) {
459
- positionArr[index * 3] -= worldPositionChange.x;
460
- positionArr[index * 3 + 1] -= worldPositionChange.y;
461
- positionArr[index * 3 + 2] -= worldPositionChange.z;
476
+ positionArr[positionIndex] -= worldPositionChange.x;
477
+ positionArr[positionIndex + 1] -= worldPositionChange.y;
478
+ positionArr[positionIndex + 2] -= worldPositionChange.z;
462
479
  }
463
- positionArr[index * 3] += accelerationX * delta;
464
- positionArr[index * 3 + 1] += accelerationY * delta;
465
- positionArr[index * 3 + 2] += accelerationZ * delta;
480
+ positionArr[positionIndex] += velocity.x * delta;
481
+ positionArr[positionIndex + 1] += velocity.y * delta;
482
+ positionArr[positionIndex + 2] += velocity.z * delta;
466
483
  particleSystem.geometry.attributes.position.needsUpdate = true;
467
484
  }
468
485