@newkrok/three-particles 0.4.0 → 0.6.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.4.0" ... }, `
21
+ `"dependencies": { ... "@newkrok/three-particles": "0.6.1" ... }, `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newkrok/three-particles",
3
- "version": "0.4.0",
3
+ "version": "0.6.1",
4
4
  "description": "Particle system for ThreeJS",
5
5
  "main": "src/js/three-particles.js",
6
6
  "bin": {
@@ -12,7 +12,10 @@
12
12
  },
13
13
  "keywords": [
14
14
  "threejs",
15
+ "particle",
15
16
  "particles",
17
+ "particle engine",
18
+ "effects",
16
19
  "3d",
17
20
  "lib"
18
21
  ],
@@ -24,7 +27,7 @@
24
27
  "homepage": "https://github.com/NewKrok/three-particles#readme",
25
28
  "dependencies": {
26
29
  "easing-functions": "1.0.1",
27
- "three": "0.136.0",
30
+ "three": "0.137.0",
28
31
  "three-noise": "^1.1.1"
29
32
  },
30
33
  "scripts": {
@@ -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,7 +1,6 @@
1
- import * as THREE from "three/build/three.module.js";
1
+ import * as THREE from "three";
2
2
 
3
3
  import { getCurveFunction } from "./three-particles-curves.js";
4
- import { size } from "lodash";
5
4
 
6
5
  const noiseInput = new THREE.Vector3(0, 0, 0);
7
6
 
@@ -1,4 +1,6 @@
1
- import * as THREE from "three/build/three.module.js";
1
+ import * as THREE from "three";
2
+
3
+ import { EmitFrom } from "../three-particles.js";
2
4
 
3
5
  export const patchObject = (
4
6
  objectA,
@@ -8,7 +10,12 @@ export const patchObject = (
8
10
  const result = {};
9
11
  Object.keys(objectA).forEach((key) => {
10
12
  if (!config.skippedProperties || !config.skippedProperties.includes(key)) {
11
- if (typeof objectA[key] === "object" && objectA[key] && objectB[key]) {
13
+ if (
14
+ typeof objectA[key] === "object" &&
15
+ objectA[key] &&
16
+ objectB[key] &&
17
+ !Array.isArray(objectA[key])
18
+ ) {
12
19
  result[key] = patchObject(objectA[key], objectB[key], config);
13
20
  } else {
14
21
  result[key] =
@@ -109,6 +116,54 @@ export const calculateRandomPositionAndVelocityOnCone = (
109
116
  );
110
117
  };
111
118
 
119
+ export const calculateRandomPositionAndVelocityOnBox = (
120
+ position,
121
+ velocity,
122
+ startSpeed,
123
+ { scale, emitFrom }
124
+ ) => {
125
+ switch (emitFrom) {
126
+ case EmitFrom.VOLUME:
127
+ position.x = Math.random() * scale.x - scale.x / 2;
128
+ position.y = Math.random() * scale.y - scale.y / 2;
129
+ position.z = Math.random() * scale.z - scale.z / 2;
130
+ break;
131
+
132
+ case EmitFrom.SHELL:
133
+ const side = Math.floor(Math.random() * 6);
134
+ const perpendicularAxis = side % 3;
135
+ const shellResult = [];
136
+ shellResult[perpendicularAxis] = side > 2 ? 1 : 0;
137
+ shellResult[(perpendicularAxis + 1) % 3] = Math.random();
138
+ shellResult[(perpendicularAxis + 2) % 3] = Math.random();
139
+ position.x = shellResult[0] * scale.x - scale.x / 2;
140
+ position.y = shellResult[1] * scale.y - scale.y / 2;
141
+ position.z = shellResult[2] * scale.z - scale.z / 2;
142
+ break;
143
+
144
+ case EmitFrom.EDGE:
145
+ const side2 = Math.floor(Math.random() * 6);
146
+ const perpendicularAxis2 = side2 % 3;
147
+ const edge = Math.floor(Math.random() * 4);
148
+ const edgeResult = [];
149
+ edgeResult[perpendicularAxis2] = side2 > 2 ? 1 : 0;
150
+ edgeResult[(perpendicularAxis2 + 1) % 3] =
151
+ edge < 2 ? Math.random() : edge - 2;
152
+ edgeResult[(perpendicularAxis2 + 2) % 3] =
153
+ edge < 2 ? edge : Math.random();
154
+ position.x = edgeResult[0] * scale.x - scale.x / 2;
155
+ position.y = edgeResult[1] * scale.y - scale.y / 2;
156
+ position.z = edgeResult[2] * scale.z - scale.z / 2;
157
+ break;
158
+ }
159
+
160
+ const randomizedSpeed = THREE.MathUtils.randFloat(
161
+ startSpeed.min,
162
+ startSpeed.max
163
+ );
164
+ velocity.set(0, 0, randomizedSpeed);
165
+ };
166
+
112
167
  export const calculateRandomPositionAndVelocityOnCircle = (
113
168
  position,
114
169
  velocity,
@@ -1,6 +1,7 @@
1
- import * as THREE from "three/build/three.module.js";
1
+ import * as THREE from "three";
2
2
 
3
3
  import {
4
+ calculateRandomPositionAndVelocityOnBox,
4
5
  calculateRandomPositionAndVelocityOnCircle,
5
6
  calculateRandomPositionAndVelocityOnCone,
6
7
  calculateRandomPositionAndVelocityOnRectangle,
@@ -13,6 +14,7 @@ import { FBM } from "three-noise/build/three-noise.module.js";
13
14
  import ParticleSystemFragmentShader from "./three-particles/shaders/particle-system-fragment-shader.glsl.js";
14
15
  import ParticleSystemVertexShader from "./three-particles/shaders/particle-system-vertex-shader.glsl.js";
15
16
  import { applyModifiers } from "./three-particles/three-particles-modifiers.js";
17
+ import { createBezierCurveFunction } from "./three-particles/three-particles-bezier";
16
18
 
17
19
  let createdParticleSystems = [];
18
20
 
@@ -29,6 +31,12 @@ export const Shape = {
29
31
  RECTANGLE: "RECTANGLE",
30
32
  };
31
33
 
34
+ export const EmitFrom = {
35
+ VOLUME: "VOLUME",
36
+ SHELL: "SHELL",
37
+ EDGE: "EDGE",
38
+ };
39
+
32
40
  export const TimeMode = {
33
41
  LIFETIME: "LIFETIME",
34
42
  FPS: "FPS",
@@ -92,10 +100,14 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
92
100
  rotation: { x: 0.0, y: 0.0 }, // TODO: add z rotation
93
101
  scale: { x: 1.0, y: 1.0 },
94
102
  },
103
+ box: {
104
+ scale: { x: 1.0, y: 1.0, z: 1.0 },
105
+ emitFrom: EmitFrom.VOLUME,
106
+ },
95
107
  },
96
108
  map: null,
97
109
  renderer: {
98
- blending: THREE.THREE.NormalBlending,
110
+ blending: THREE.NormalBlending,
99
111
  transparent: true,
100
112
  depthTest: true,
101
113
  depthWrite: false,
@@ -115,7 +127,11 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
115
127
  },
116
128
  sizeOverLifetime: {
117
129
  isActive: false,
118
- curveFunction: CurveFunction.LINEAR,
130
+ curveFunction: CurveFunction.BEZIER,
131
+ bezierPoints: [
132
+ { x: 0, y: 0, percentage: 0 },
133
+ { x: 1, y: 1, percentage: 1 },
134
+ ],
119
135
  },
120
136
  /* colorOverLifetime: {
121
137
  isActive: false,
@@ -123,7 +139,11 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
123
139
  }, */
124
140
  opacityOverLifetime: {
125
141
  isActive: false,
126
- curveFunction: CurveFunction.LINEAR,
142
+ curveFunction: CurveFunction.BEZIER,
143
+ bezierPoints: [
144
+ { x: 0, y: 0, percentage: 0 },
145
+ { x: 1, y: 1, percentage: 1 },
146
+ ],
127
147
  },
128
148
  rotationOverLifetime: {
129
149
  isActive: false,
@@ -169,7 +189,7 @@ const createFloat32Attributes = ({
169
189
  };
170
190
 
171
191
  const calculatePositionAndVelocity = (
172
- { shape, sphere, cone, circle, rectangle },
192
+ { shape, sphere, cone, circle, rectangle, box },
173
193
  startSpeed,
174
194
  position,
175
195
  velocity,
@@ -211,6 +231,15 @@ const calculatePositionAndVelocity = (
211
231
  rectangle
212
232
  );
213
233
  break;
234
+
235
+ case Shape.BOX:
236
+ calculateRandomPositionAndVelocityOnBox(
237
+ position,
238
+ velocity,
239
+ startSpeed,
240
+ box
241
+ );
242
+ break;
214
243
  }
215
244
 
216
245
  if (velocityOverLifetime.isActive) {
@@ -249,6 +278,22 @@ export const createParticleSystem = (
249
278
  };
250
279
 
251
280
  const normalizedConfig = patchObject(DEFAULT_PARTICLE_SYSTEM_CONFIG, config);
281
+
282
+ const bezierCompatibleProperties = [
283
+ "sizeOverLifetime",
284
+ "opacityOverLifetime",
285
+ ];
286
+ bezierCompatibleProperties.forEach((key) => {
287
+ if (
288
+ normalizedConfig[key].isActive &&
289
+ normalizedConfig[key].curveFunction === CurveFunction.BEZIER &&
290
+ normalizedConfig[key].bezierPoints
291
+ )
292
+ normalizedConfig[key].curveFunction = createBezierCurveFunction(
293
+ normalizedConfig[key].bezierPoints
294
+ );
295
+ });
296
+
252
297
  const {
253
298
  transform,
254
299
  duration,
@@ -503,6 +548,7 @@ export const createParticleSystem = (
503
548
  startPositions[particleIndex].y;
504
549
  geometry.attributes.position.array[positionIndex + 2] =
505
550
  startPositions[particleIndex].z;
551
+ geometry.attributes.position.needsUpdate = true;
506
552
 
507
553
  geometry.attributes.lifetime.array[particleIndex] = 0;
508
554
  geometry.attributes.lifetime.needsUpdate = true;
@@ -600,15 +646,16 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
600
646
  particleSystem.material.uniforms.elapsed.value = elapsed;
601
647
 
602
648
  particleSystem.getWorldPosition(currentWorldPosition);
603
- if (lastWorldPosition.x !== -99999)
649
+ if (lastWorldPosition.x !== -99999) {
604
650
  worldPositionChange.set(
605
651
  currentWorldPosition.x - lastWorldPosition.x,
606
652
  currentWorldPosition.y - lastWorldPosition.y,
607
653
  currentWorldPosition.z - lastWorldPosition.z
608
654
  );
655
+ worldPositionChange.applyQuaternion(worldQuaternion.invert())
656
+ }
609
657
  generalData.distanceFromLastEmitByDistance += worldPositionChange.length();
610
658
  particleSystem.getWorldPosition(lastWorldPosition);
611
-
612
659
  particleSystem.getWorldQuaternion(worldQuaternion);
613
660
  if (
614
661
  lastWorldQuaternion.x === -99999 ||
@@ -618,16 +665,12 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
618
665
  ) {
619
666
  worldEuler.setFromQuaternion(worldQuaternion);
620
667
  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();
668
+ gravityVelocity.set(
669
+ lastWorldPosition.x,
670
+ lastWorldPosition.y + gravity,
671
+ lastWorldPosition.z
672
+ );
628
673
  particleSystem.worldToLocal(gravityVelocity);
629
- particleSystem.position.set(tempPosX, tempPosY, tempPosZ);
630
- particleSystem.updateMatrixWorld();
631
674
  }
632
675
 
633
676
  generalData.creationTimes.forEach((entry, index) => {
@@ -648,11 +691,15 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
648
691
  gravity !== 0 ||
649
692
  velocity.x !== 0 ||
650
693
  velocity.y !== 0 ||
651
- velocity.z !== 0
694
+ velocity.z !== 0 ||
695
+ worldPositionChange.x !== 0 ||
696
+ worldPositionChange.y !== 0 ||
697
+ worldPositionChange.z !== 0
652
698
  ) {
653
699
  const positionIndex = index * 3;
654
700
  const positionArr =
655
701
  particleSystem.geometry.attributes.position.array;
702
+
656
703
  if (simulationSpace === SimulationSpace.WORLD) {
657
704
  positionArr[positionIndex] -= worldPositionChange.x;
658
705
  positionArr[positionIndex + 1] -= worldPositionChange.y;