@newkrok/three-particles 0.6.1 → 0.7.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
@@ -6,6 +6,11 @@ Particle system for ThreeJS
6
6
 
7
7
  You can create your own particle effects with it's editor https://github.com/NewKrok/three-particles-editor
8
8
 
9
+ # Video
10
+
11
+ - Projectiles: https://youtu.be/Q352JuxON04
12
+ - First preview: https://youtu.be/dtN_bndvoGU
13
+
9
14
  # Live demo
10
15
 
11
16
  https://newkrok.com/three-particles-editor/index.html
@@ -18,4 +23,4 @@ Install with npm
18
23
  `npm i @newkrok/three-particles`
19
24
 
20
25
  Add as a package.json dependency
21
- `"dependencies": { ... "@newkrok/three-particles": "0.6.1" ... }, `
26
+ `"dependencies": { ... "@newkrok/three-particles": "0.7.0" ... }, `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newkrok/three-particles",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "Particle system for ThreeJS",
5
5
  "main": "src/js/three-particles.js",
6
6
  "bin": {
@@ -28,7 +28,8 @@
28
28
  "dependencies": {
29
29
  "easing-functions": "1.0.1",
30
30
  "three": "0.137.0",
31
- "three-noise": "^1.1.1"
31
+ "three-noise": "^1.1.1",
32
+ "@newkrok/three-utils": "0.1.0"
32
33
  },
33
34
  "scripts": {
34
35
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -4,6 +4,9 @@ const ParticleSystemFragmentShader = `
4
4
  uniform float fps;
5
5
  uniform bool useFPSForFrameIndex;
6
6
  uniform vec2 tiles;
7
+ uniform bool discardBackgroundColor;
8
+ uniform vec3 backgroundColor;
9
+ uniform float backgroundColorTolerance;
7
10
 
8
11
  varying vec4 vColor;
9
12
  varying float vLifetime;
@@ -56,6 +59,8 @@ const ParticleSystemFragmentShader = `
56
59
  vec4 rotatedTexture = texture2D(map, uvPoint);
57
60
 
58
61
  gl_FragColor = gl_FragColor * rotatedTexture;
62
+
63
+ if (discardBackgroundColor && abs(length(rotatedTexture.rgb - backgroundColor.rgb)) < backgroundColorTolerance ) discard;
59
64
  }
60
65
  `;
61
66
 
@@ -2,37 +2,9 @@ import * as THREE from "three";
2
2
 
3
3
  import { EmitFrom } from "../three-particles.js";
4
4
 
5
- export const patchObject = (
6
- objectA,
7
- objectB,
8
- config = { skippedProperties: [], applyToFirstObject: false }
9
- ) => {
10
- const result = {};
11
- Object.keys(objectA).forEach((key) => {
12
- if (!config.skippedProperties || !config.skippedProperties.includes(key)) {
13
- if (
14
- typeof objectA[key] === "object" &&
15
- objectA[key] &&
16
- objectB[key] &&
17
- !Array.isArray(objectA[key])
18
- ) {
19
- result[key] = patchObject(objectA[key], objectB[key], config);
20
- } else {
21
- result[key] =
22
- objectB[key] === 0
23
- ? 0
24
- : objectB[key] === false
25
- ? false
26
- : objectB[key] || objectA[key];
27
- if (config.applyToFirstObject) objectA[key] = result[key];
28
- }
29
- }
30
- });
31
- return result;
32
- };
33
-
34
5
  export const calculateRandomPositionAndVelocityOnSphere = (
35
6
  position,
7
+ quaternion,
36
8
  velocity,
37
9
  startSpeed,
38
10
  { radius, radiusThickness, arc }
@@ -59,6 +31,8 @@ export const calculateRandomPositionAndVelocityOnSphere = (
59
31
  radius * normalizedThickness * zDirection +
60
32
  radius * radiusThickness * randomizedDistanceRatio * zDirection;
61
33
 
34
+ position.applyQuaternion(quaternion);
35
+
62
36
  const randomizedSpeed = THREE.MathUtils.randFloat(
63
37
  startSpeed.min,
64
38
  startSpeed.max
@@ -73,6 +47,7 @@ export const calculateRandomPositionAndVelocityOnSphere = (
73
47
 
74
48
  export const calculateRandomPositionAndVelocityOnCone = (
75
49
  position,
50
+ quaternion,
76
51
  velocity,
77
52
  startSpeed,
78
53
  { radius, radiusThickness, arc, angle = 90 }
@@ -92,6 +67,8 @@ export const calculateRandomPositionAndVelocityOnCone = (
92
67
  radius * radiusThickness * randomizedDistanceRatio * yDirection;
93
68
  position.z = 0;
94
69
 
70
+ position.applyQuaternion(quaternion);
71
+
95
72
  const positionLength = position.length();
96
73
  const normalizedAngle = Math.abs(
97
74
  (positionLength / radius) * THREE.Math.degToRad(angle)
@@ -118,6 +95,7 @@ export const calculateRandomPositionAndVelocityOnCone = (
118
95
 
119
96
  export const calculateRandomPositionAndVelocityOnBox = (
120
97
  position,
98
+ quaternion,
121
99
  velocity,
122
100
  startSpeed,
123
101
  { scale, emitFrom }
@@ -157,6 +135,8 @@ export const calculateRandomPositionAndVelocityOnBox = (
157
135
  break;
158
136
  }
159
137
 
138
+ position.applyQuaternion(quaternion);
139
+
160
140
  const randomizedSpeed = THREE.MathUtils.randFloat(
161
141
  startSpeed.min,
162
142
  startSpeed.max
@@ -166,6 +146,7 @@ export const calculateRandomPositionAndVelocityOnBox = (
166
146
 
167
147
  export const calculateRandomPositionAndVelocityOnCircle = (
168
148
  position,
149
+ quaternion,
169
150
  velocity,
170
151
  startSpeed,
171
152
  { radius, radiusThickness, arc }
@@ -185,6 +166,8 @@ export const calculateRandomPositionAndVelocityOnCircle = (
185
166
  radius * radiusThickness * randomizedDistanceRatio * yDirection;
186
167
  position.z = 0;
187
168
 
169
+ position.applyQuaternion(quaternion);
170
+
188
171
  const randomizedSpeed = THREE.MathUtils.randFloat(
189
172
  startSpeed.min,
190
173
  startSpeed.max
@@ -201,6 +184,7 @@ export const calculateRandomPositionAndVelocityOnCircle = (
201
184
 
202
185
  export const calculateRandomPositionAndVelocityOnRectangle = (
203
186
  position,
187
+ quaternion,
204
188
  velocity,
205
189
  startSpeed,
206
190
  { rotation, scale }
@@ -213,6 +197,8 @@ export const calculateRandomPositionAndVelocityOnRectangle = (
213
197
  position.y = yOffset * Math.cos(rotationX);
214
198
  position.z = xOffset * Math.sin(rotationY) - yOffset * Math.sin(rotationX);
215
199
 
200
+ position.applyQuaternion(quaternion);
201
+
216
202
  const randomizedSpeed = THREE.MathUtils.randFloat(
217
203
  startSpeed.min,
218
204
  startSpeed.max
@@ -6,15 +6,16 @@ import {
6
6
  calculateRandomPositionAndVelocityOnCone,
7
7
  calculateRandomPositionAndVelocityOnRectangle,
8
8
  calculateRandomPositionAndVelocityOnSphere,
9
- patchObject,
10
9
  } from "./three-particles/three-particles-utils.js";
11
10
 
12
11
  import { CurveFunction } from "./three-particles/three-particles-curves.js";
13
12
  import { FBM } from "three-noise/build/three-noise.module.js";
13
+ import { Gyroscope } from "three/examples/jsm/misc/Gyroscope";
14
14
  import ParticleSystemFragmentShader from "./three-particles/shaders/particle-system-fragment-shader.glsl.js";
15
15
  import ParticleSystemVertexShader from "./three-particles/shaders/particle-system-vertex-shader.glsl.js";
16
16
  import { applyModifiers } from "./three-particles/three-particles-modifiers.js";
17
17
  import { createBezierCurveFunction } from "./three-particles/three-particles-bezier";
18
+ import { patchObject } from "@newkrok/three-utils/src/js/newkrok/three-utils/object-utils.js";
18
19
 
19
20
  let createdParticleSystems = [];
20
21
 
@@ -108,6 +109,9 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
108
109
  map: null,
109
110
  renderer: {
110
111
  blending: THREE.NormalBlending,
112
+ discardBackgroundColor: false,
113
+ backgroundColorTolerance: 1.0,
114
+ backgroundColor: { r: 1.0, g: 1.0, b: 1.0 },
111
115
  transparent: true,
112
116
  depthTest: true,
113
117
  depthWrite: false,
@@ -192,6 +196,7 @@ const calculatePositionAndVelocity = (
192
196
  { shape, sphere, cone, circle, rectangle, box },
193
197
  startSpeed,
194
198
  position,
199
+ quaternion,
195
200
  velocity,
196
201
  velocityOverLifetime
197
202
  ) => {
@@ -199,6 +204,7 @@ const calculatePositionAndVelocity = (
199
204
  case Shape.SPHERE:
200
205
  calculateRandomPositionAndVelocityOnSphere(
201
206
  position,
207
+ quaternion,
202
208
  velocity,
203
209
  startSpeed,
204
210
  sphere
@@ -208,6 +214,7 @@ const calculatePositionAndVelocity = (
208
214
  case Shape.CONE:
209
215
  calculateRandomPositionAndVelocityOnCone(
210
216
  position,
217
+ quaternion,
211
218
  velocity,
212
219
  startSpeed,
213
220
  cone
@@ -217,6 +224,7 @@ const calculatePositionAndVelocity = (
217
224
  case Shape.CIRCLE:
218
225
  calculateRandomPositionAndVelocityOnCircle(
219
226
  position,
227
+ quaternion,
220
228
  velocity,
221
229
  startSpeed,
222
230
  circle
@@ -226,6 +234,7 @@ const calculatePositionAndVelocity = (
226
234
  case Shape.RECTANGLE:
227
235
  calculateRandomPositionAndVelocityOnRectangle(
228
236
  position,
237
+ quaternion,
229
238
  velocity,
230
239
  startSpeed,
231
240
  rectangle
@@ -235,6 +244,7 @@ const calculatePositionAndVelocity = (
235
244
  case Shape.BOX:
236
245
  calculateRandomPositionAndVelocityOnBox(
237
246
  position,
247
+ quaternion,
238
248
  velocity,
239
249
  startSpeed,
240
250
  box
@@ -268,6 +278,7 @@ export const createParticleSystem = (
268
278
  currentWorldPosition: new THREE.Vector3(-99999),
269
279
  worldPositionChange: new THREE.Vector3(),
270
280
  worldQuaternion: new THREE.Quaternion(),
281
+ wrapperQuaternion: new THREE.Quaternion(),
271
282
  lastWorldQuaternion: new THREE.Quaternion(-99999),
272
283
  worldEuler: new THREE.Euler(),
273
284
  gravityVelocity: new THREE.Vector3(0, 0, 0),
@@ -391,6 +402,15 @@ export const createParticleSystem = (
391
402
  useFPSForFrameIndex: {
392
403
  value: textureSheetAnimation.timeMode === TimeMode.FPS,
393
404
  },
405
+ backgroundColor: {
406
+ value: renderer.backgroundColor,
407
+ },
408
+ discardBackgroundColor: {
409
+ value: renderer.discardBackgroundColor,
410
+ },
411
+ backgroundColorTolerance: {
412
+ value: renderer.backgroundColorTolerance,
413
+ },
394
414
  },
395
415
  vertexShader: ParticleSystemVertexShader,
396
416
  fragmentShader: ParticleSystemFragmentShader,
@@ -407,6 +427,7 @@ export const createParticleSystem = (
407
427
  shape,
408
428
  startSpeed,
409
429
  startPositions[i],
430
+ generalData.wrapperQuaternion,
410
431
  velocities[i],
411
432
  velocityOverLifetime
412
433
  );
@@ -480,7 +501,7 @@ export const createParticleSystem = (
480
501
  geometry.attributes.colorA.needsUpdate = true;
481
502
  };
482
503
 
483
- const activateParticle = ({ particleIndex, activationTime }) => {
504
+ const activateParticle = ({ particleIndex, activationTime, position }) => {
484
505
  geometry.attributes.isActive.array[particleIndex] = true;
485
506
  generalData.creationTimes[particleIndex] = activationTime;
486
507
 
@@ -538,16 +559,17 @@ export const createParticleSystem = (
538
559
  shape,
539
560
  startSpeed,
540
561
  startPositions[particleIndex],
562
+ generalData.wrapperQuaternion,
541
563
  velocities[particleIndex],
542
564
  velocityOverLifetime
543
565
  );
544
566
  const positionIndex = Math.floor(particleIndex * 3);
545
567
  geometry.attributes.position.array[positionIndex] =
546
- startPositions[particleIndex].x;
568
+ (position ? position.x : 0) + startPositions[particleIndex].x;
547
569
  geometry.attributes.position.array[positionIndex + 1] =
548
- startPositions[particleIndex].y;
570
+ (position ? position.y : 0) + startPositions[particleIndex].y;
549
571
  geometry.attributes.position.array[positionIndex + 2] =
550
- startPositions[particleIndex].z;
572
+ (position ? position.z : 0) + startPositions[particleIndex].z;
551
573
  geometry.attributes.position.needsUpdate = true;
552
574
 
553
575
  geometry.attributes.lifetime.array[particleIndex] = 0;
@@ -567,7 +589,7 @@ export const createParticleSystem = (
567
589
  });
568
590
  };
569
591
 
570
- const particleSystem = new THREE.Points(geometry, material);
592
+ let particleSystem = new THREE.Points(geometry, material);
571
593
  particleSystem.sortParticles = true;
572
594
 
573
595
  particleSystem.position.copy(transform.position);
@@ -579,8 +601,15 @@ export const createParticleSystem = (
579
601
  const calculatedCreationTime =
580
602
  now + THREE.MathUtils.randFloat(startDelay.min, startDelay.max) * 1000;
581
603
 
604
+ let wrapper;
605
+ if (normalizedConfig.simulationSpace === SimulationSpace.WORLD) {
606
+ wrapper = new Gyroscope();
607
+ wrapper.add(particleSystem);
608
+ }
609
+
582
610
  createdParticleSystems.push({
583
611
  particleSystem,
612
+ wrapper,
584
613
  generalData,
585
614
  onUpdate,
586
615
  onComplete,
@@ -597,18 +626,26 @@ export const createParticleSystem = (
597
626
  deactivateParticle,
598
627
  activateParticle,
599
628
  });
600
- return particleSystem;
629
+
630
+ return wrapper || particleSystem;
601
631
  };
602
632
 
603
633
  export const destroyParticleSystem = (particleSystem) => {
604
634
  createdParticleSystems = createdParticleSystems.filter(
605
- ({ particleSystem: savedParticleSystem }) =>
606
- savedParticleSystem !== particleSystem
607
- );
635
+ ({ particleSystem: savedParticleSystem, wrapper }) => {
636
+ if (
637
+ savedParticleSystem !== particleSystem &&
638
+ wrapper !== particleSystem
639
+ ) {
640
+ return true;
641
+ }
608
642
 
609
- particleSystem.geometry.dispose();
610
- particleSystem.material.dispose();
611
- particleSystem.parent.remove(particleSystem);
643
+ savedParticleSystem.geometry.dispose();
644
+ savedParticleSystem.material.dispose();
645
+ savedParticleSystem.parent.remove(savedParticleSystem);
646
+ return false;
647
+ }
648
+ );
612
649
  };
613
650
 
614
651
  export const updateParticleSystems = ({ now, delta, elapsed }) => {
@@ -618,6 +655,7 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
618
655
  generalData,
619
656
  onComplete,
620
657
  particleSystem,
658
+ wrapper,
621
659
  creationTime,
622
660
  lastEmissionTime,
623
661
  duration,
@@ -642,6 +680,10 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
642
680
  gravityVelocity,
643
681
  } = generalData;
644
682
 
683
+ if (wrapper) generalData.wrapperQuaternion.copy(wrapper.parent.quaternion);
684
+
685
+ const lastWorldPositionSnapshot = { ...lastWorldPosition };
686
+
645
687
  const lifetime = now - creationTime;
646
688
  particleSystem.material.uniforms.elapsed.value = elapsed;
647
689
 
@@ -652,7 +694,7 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
652
694
  currentWorldPosition.y - lastWorldPosition.y,
653
695
  currentWorldPosition.z - lastWorldPosition.z
654
696
  );
655
- worldPositionChange.applyQuaternion(worldQuaternion.invert())
697
+ worldPositionChange.applyQuaternion(worldQuaternion.invert());
656
698
  }
657
699
  generalData.distanceFromLastEmitByDistance += worldPositionChange.length();
658
700
  particleSystem.getWorldPosition(lastWorldPosition);
@@ -669,7 +711,7 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
669
711
  lastWorldPosition.x,
670
712
  lastWorldPosition.y + gravity,
671
713
  lastWorldPosition.z
672
- );
714
+ );
673
715
  particleSystem.worldToLocal(gravityVelocity);
674
716
  }
675
717
 
@@ -746,12 +788,28 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
746
788
  (1 / emission.rateOverDistance)
747
789
  )
748
790
  : 0;
791
+ const distanceStep =
792
+ neededParticlesByDistance > 0
793
+ ? {
794
+ x:
795
+ (currentWorldPosition.x - lastWorldPositionSnapshot.x) /
796
+ neededParticlesByDistance,
797
+ y:
798
+ (currentWorldPosition.y - lastWorldPositionSnapshot.y) /
799
+ neededParticlesByDistance,
800
+ z:
801
+ (currentWorldPosition.z - lastWorldPositionSnapshot.z) /
802
+ neededParticlesByDistance,
803
+ }
804
+ : null;
749
805
  const neededParticles = neededParticlesByTime + neededParticlesByDistance;
750
806
 
751
- if (emission.rateOverDistance > 0 && neededParticlesByDistance >= 1)
807
+ if (emission.rateOverDistance > 0 && neededParticlesByDistance >= 1) {
752
808
  generalData.distanceFromLastEmitByDistance = 0;
809
+ }
753
810
 
754
811
  if (neededParticles > 0) {
812
+ let generatedParticlesByDistanceNeeds = 0;
755
813
  for (let i = 0; i < neededParticles; i++) {
756
814
  let particleIndex = -1;
757
815
  particleSystem.geometry.attributes.isActive.array.find(
@@ -770,7 +828,19 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
770
828
  particleIndex <
771
829
  particleSystem.geometry.attributes.isActive.array.length
772
830
  ) {
773
- activateParticle({ particleIndex, activationTime: now });
831
+ let position;
832
+ if (generatedParticlesByDistanceNeeds < neededParticlesByDistance) {
833
+ position =
834
+ generatedParticlesByDistanceNeeds < neededParticlesByDistance
835
+ ? {
836
+ x: distanceStep.x * generatedParticlesByDistanceNeeds,
837
+ y: distanceStep.y * generatedParticlesByDistanceNeeds,
838
+ z: distanceStep.z * generatedParticlesByDistanceNeeds,
839
+ }
840
+ : null;
841
+ generatedParticlesByDistanceNeeds++;
842
+ }
843
+ activateParticle({ particleIndex, activationTime: now, position });
774
844
  props.lastEmissionTime = now;
775
845
  }
776
846
  }