@newkrok/three-particles 0.2.0 → 0.2.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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newkrok/three-particles",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Particle system for ThreeJS",
5
5
  "main": "src/js/three-particles.js",
6
6
  "bin": {
@@ -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,
@@ -4,24 +4,30 @@ const ParticleSystemVertexShader = `
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
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
28
  gl_PointSize = startSize * (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;
@@ -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 deepMerge = (
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] = deepMerge(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
  };
@@ -29,6 +29,11 @@ export const Shape = {
29
29
  RECTANGLE: "RECTANGLE",
30
30
  };
31
31
 
32
+ export const TimeMode = {
33
+ LIFETIME: "LIFETIME",
34
+ FPS: "FPS",
35
+ };
36
+
32
37
  export const getDefaultParticleSystemConfig = () =>
33
38
  JSON.parse(JSON.stringify(DEFAULT_PARTICLE_SYSTEM_CONFIG));
34
39
 
@@ -36,7 +41,7 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
36
41
  duration: 5.0,
37
42
  looping: true,
38
43
  startDelay: { min: 0.0, max: 0.0 },
39
- startLifeTime: { min: 2.0, max: 2.0 },
44
+ startLifetime: { min: 2.0, max: 2.0 },
40
45
  startSpeed: { min: 1.0, max: 1.0 },
41
46
  startSize: { min: 1.0, max: 1.0 },
42
47
  startRotation: { min: 0.0, max: 0.0 },
@@ -45,7 +50,7 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
45
50
  max: { r: 1.0, g: 1.0, b: 1.0 },
46
51
  },
47
52
  startOpacity: { min: 1.0, max: 1.0 },
48
- gravity: 0,
53
+ gravity: 0.0,
49
54
  simulationSpace: SimulationSpace.LOCAL,
50
55
  maxParticles: 100.0,
51
56
  emission: {
@@ -76,7 +81,12 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
76
81
  },
77
82
  },
78
83
  map: null,
79
- textureSheetAnimation: { tiles: new THREE.Vector2(1.0, 1.0), fps: 30.0 },
84
+ textureSheetAnimation: {
85
+ tiles: new THREE.Vector2(1.0, 1.0),
86
+ timeMode: TimeMode.LIFETIME,
87
+ fps: 10.0,
88
+ startFrame: { min: 0.0, max: 0.0 },
89
+ },
80
90
  };
81
91
 
82
92
  const createFloat32Attributes = ({
@@ -156,7 +166,7 @@ export const createParticleSystem = (
156
166
  duration,
157
167
  looping,
158
168
  startDelay,
159
- startLifeTime,
169
+ startLifetime,
160
170
  startSpeed,
161
171
  startSize,
162
172
  startRotation,
@@ -182,14 +192,6 @@ export const createParticleSystem = (
182
192
  () => new THREE.Vector3()
183
193
  );
184
194
 
185
- const uniforms = Object.keys(textureSheetAnimation).reduce(
186
- (prev, key) => ({
187
- ...prev,
188
- [key]: { value: textureSheetAnimation[key] },
189
- }),
190
- {}
191
- );
192
-
193
195
  const material = new THREE.ShaderMaterial({
194
196
  uniforms: {
195
197
  elapsed: {
@@ -198,7 +200,15 @@ export const createParticleSystem = (
198
200
  map: {
199
201
  value: map,
200
202
  },
201
- ...uniforms,
203
+ tiles: {
204
+ value: textureSheetAnimation.tiles,
205
+ },
206
+ fps: {
207
+ value: textureSheetAnimation.fps,
208
+ },
209
+ useFPSForFrameIndex: {
210
+ value: textureSheetAnimation.timeMode === TimeMode.FPS,
211
+ },
202
212
  },
203
213
  vertexShader: ParticleSystemVertexShader,
204
214
  fragmentShader: ParticleSystemFragmentShader,
@@ -235,9 +245,15 @@ export const createParticleSystem = (
235
245
 
236
246
  createFloat32AttributesRequest("isActive", false);
237
247
  createFloat32AttributesRequest("creationTime", 0);
238
- createFloat32AttributesRequest("lifeTime", 0);
239
- createFloat32AttributesRequest("startLifeTime", () =>
240
- THREE.MathUtils.randFloat(startLifeTime.min, startLifeTime.max)
248
+ createFloat32AttributesRequest("lifetime", 0);
249
+ createFloat32AttributesRequest("startLifetime", () =>
250
+ THREE.MathUtils.randFloat(startLifetime.min, startLifetime.max)
251
+ );
252
+ createFloat32AttributesRequest("startFrame", () =>
253
+ THREE.MathUtils.randInt(
254
+ textureSheetAnimation.startFrame.min,
255
+ textureSheetAnimation.startFrame.max
256
+ )
241
257
  );
242
258
 
243
259
  createFloat32AttributesRequest("opacity", 0);
@@ -284,8 +300,8 @@ export const createParticleSystem = (
284
300
 
285
301
  const deactivateParticle = (particleIndex) => {
286
302
  geometry.attributes.isActive.array[particleIndex] = false;
287
- geometry.attributes.lifeTime.array[particleIndex] = 0;
288
- geometry.attributes.lifeTime.needsUpdate = true;
303
+ geometry.attributes.lifetime.array[particleIndex] = 0;
304
+ geometry.attributes.lifetime.needsUpdate = true;
289
305
  geometry.attributes.colorA.array[particleIndex] = 0;
290
306
  geometry.attributes.colorA.needsUpdate = true;
291
307
  };
@@ -318,9 +334,16 @@ export const createParticleSystem = (
318
334
  );
319
335
  geometry.attributes.colorA.needsUpdate = true;
320
336
 
321
- geometry.attributes.startLifeTime.array[particleIndex] =
322
- THREE.MathUtils.randFloat(startLifeTime.min, startLifeTime.max) * 1000;
323
- geometry.attributes.startLifeTime.needsUpdate = true;
337
+ geometry.attributes.startFrame.array[particleIndex] =
338
+ THREE.MathUtils.randInt(
339
+ textureSheetAnimation.startFrame.min,
340
+ textureSheetAnimation.startFrame.max
341
+ );
342
+ geometry.attributes.startFrame.needsUpdate = true;
343
+
344
+ geometry.attributes.startLifetime.array[particleIndex] =
345
+ THREE.MathUtils.randFloat(startLifetime.min, startLifetime.max) * 1000;
346
+ geometry.attributes.startLifetime.needsUpdate = true;
324
347
 
325
348
  geometry.attributes.startSize.array[particleIndex] =
326
349
  THREE.MathUtils.randFloat(startSize.min, startSize.max);
@@ -346,8 +369,8 @@ export const createParticleSystem = (
346
369
  startPositions[particleIndex].z;
347
370
  particleSystem.geometry.attributes.position.needsUpdate = true;
348
371
 
349
- geometry.attributes.lifeTime.array[particleIndex] = 0;
350
- geometry.attributes.lifeTime.needsUpdate = true;
372
+ geometry.attributes.lifetime.array[particleIndex] = 0;
373
+ geometry.attributes.lifetime.needsUpdate = true;
351
374
  };
352
375
 
353
376
  const particleSystem = new THREE.Points(geometry, material);
@@ -389,8 +412,7 @@ export const destroyParticleSystem = (particleSystem) => {
389
412
  particleSystem.parent.remove(particleSystem);
390
413
  };
391
414
 
392
- export const updateParticleSystems = ({ delta, elapsed }) => {
393
- const now = Date.now();
415
+ export const updateParticleSystems = ({ now, delta, elapsed }) => {
394
416
  createdParticleSystems.forEach((props) => {
395
417
  const {
396
418
  onUpdate,
@@ -411,7 +433,7 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
411
433
  simulationSpace,
412
434
  gravity,
413
435
  } = props;
414
- const lifeTime = now - creationTime;
436
+ const lifetime = now - creationTime;
415
437
  particleSystem.material.uniforms.elapsed.value = elapsed;
416
438
 
417
439
  if (
@@ -430,10 +452,10 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
430
452
  particleSystem.geometry.attributes.creationTime.array.forEach(
431
453
  (entry, index) => {
432
454
  if (particleSystem.geometry.attributes.isActive.array[index]) {
433
- const particleLifeTime = now - float32Helper - entry;
455
+ const particleLifetime = now - float32Helper - entry;
434
456
  if (
435
- particleLifeTime >
436
- particleSystem.geometry.attributes.startLifeTime.array[index]
457
+ particleLifetime >
458
+ particleSystem.geometry.attributes.startLifetime.array[index]
437
459
  )
438
460
  deactivateParticle(index);
439
461
  else {
@@ -460,22 +482,22 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
460
482
  particleSystem.geometry.attributes.position.needsUpdate = true;
461
483
  }
462
484
 
463
- particleSystem.geometry.attributes.lifeTime.array[index] =
464
- particleLifeTime;
465
- particleSystem.geometry.attributes.lifeTime.needsUpdate = true;
485
+ particleSystem.geometry.attributes.lifetime.array[index] =
486
+ particleLifetime;
487
+ particleSystem.geometry.attributes.lifetime.needsUpdate = true;
466
488
 
467
489
  // TEMP
468
490
  particleSystem.geometry.attributes.colorA.array[index] =
469
491
  1 -
470
- particleLifeTime /
471
- particleSystem.geometry.attributes.startLifeTime.array[index];
492
+ particleLifetime /
493
+ particleSystem.geometry.attributes.startLifetime.array[index];
472
494
  particleSystem.geometry.attributes.colorA.needsUpdate = true;
473
495
  }
474
496
  }
475
497
  }
476
498
  );
477
499
 
478
- if (looping || lifeTime < duration * 1000) {
500
+ if (looping || lifetime < duration * 1000) {
479
501
  const emissionDelta = now - lastEmissionTime;
480
502
  const neededParticlesByTime = Math.floor(
481
503
  emission.rateOverTime * (emissionDelta / 1000)
@@ -523,7 +545,7 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
523
545
  particleSystem,
524
546
  delta,
525
547
  elapsed,
526
- lifeTime,
548
+ lifetime,
527
549
  iterationCount: iterationCount + 1,
528
550
  });
529
551
  } else if (onComplete)