@newkrok/three-particles 0.0.1 → 0.2.2

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,8 +1,21 @@
1
1
  # THREE Particles
2
- Particle sytem for ThreeJS
2
+
3
+ Particle system for ThreeJS
3
4
 
4
5
  # THREE Particles Editor
6
+
5
7
  You can create your own particle effects with it's editor https://github.com/NewKrok/three-particles-editor
6
8
 
7
9
  # Live demo
10
+
8
11
  https://newkrok.com/three-particles-editor/index.html
12
+
13
+ # Install
14
+
15
+ npm package https://www.npmjs.com/package/@newkrok/three-particles
16
+
17
+ Install with npm
18
+ `npm i @newkrok/three-particles`
19
+
20
+ Add as a package.json dependency
21
+ `"dependencies": { ... "@newkrok/three-particles": "0.2.1" ... }, `
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@newkrok/three-particles",
3
- "version": "0.0.1",
3
+ "version": "0.2.2",
4
4
  "description": "Particle system for ThreeJS",
5
5
  "main": "src/js/three-particles.js",
6
6
  "bin": {
@@ -22,6 +22,9 @@
22
22
  "url": "https://github.com/NewKrok/three-particles/issues"
23
23
  },
24
24
  "homepage": "https://github.com/NewKrok/three-particles#readme",
25
+ "dependencies": {
26
+ "three": "^0.135.0"
27
+ },
25
28
  "scripts": {
26
29
  "test": "echo \"Error: no test specified\" && exit 1"
27
30
  }
@@ -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
- 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
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;
@@ -0,0 +1,161 @@
1
+ import * as THREE from "three/build/three.module.js";
2
+
3
+ export const deepMerge = (
4
+ objectA,
5
+ objectB,
6
+ config = { skippedProperties: [], applyToFirstObject: false }
7
+ ) => {
8
+ const result = {};
9
+ Object.keys(objectA).forEach((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
+ }
18
+ });
19
+ return result;
20
+ };
21
+
22
+ export const calculateRandomPositionAndVelocityOnSphere = (
23
+ position,
24
+ velocity,
25
+ startSpeed,
26
+ { radius, radiusThickness, arc }
27
+ ) => {
28
+ const u = Math.random() * (arc / 360);
29
+ const v = Math.random();
30
+ const randomizedDistanceRatio = Math.random();
31
+ const theta = 2 * Math.PI * u;
32
+ const phi = Math.acos(2 * v - 1);
33
+ const sinPhi = Math.sin(phi);
34
+
35
+ const xDirection = sinPhi * Math.cos(theta);
36
+ const yDirection = sinPhi * Math.sin(theta);
37
+ const zDirection = Math.cos(phi);
38
+ const normalizedThickness = 1 - radiusThickness;
39
+
40
+ position.x =
41
+ radius * normalizedThickness * xDirection +
42
+ radius * radiusThickness * randomizedDistanceRatio * xDirection;
43
+ position.y =
44
+ radius * normalizedThickness * yDirection +
45
+ radius * radiusThickness * randomizedDistanceRatio * yDirection;
46
+ position.z =
47
+ radius * normalizedThickness * zDirection +
48
+ radius * radiusThickness * randomizedDistanceRatio * zDirection;
49
+
50
+ const randomizedSpeed = THREE.MathUtils.randFloat(
51
+ startSpeed.min,
52
+ startSpeed.max
53
+ );
54
+ const speedMultiplierByPosition = 1 / position.length();
55
+ velocity.set(
56
+ position.x * speedMultiplierByPosition * randomizedSpeed,
57
+ position.y * speedMultiplierByPosition * randomizedSpeed,
58
+ position.z * speedMultiplierByPosition * randomizedSpeed
59
+ );
60
+ };
61
+
62
+ export const calculateRandomPositionAndVelocityOnCone = (
63
+ position,
64
+ velocity,
65
+ startSpeed,
66
+ { radius, radiusThickness, arc, angle = 90 }
67
+ ) => {
68
+ const theta = 2 * Math.PI * Math.random() * (arc / 360);
69
+ const randomizedDistanceRatio = Math.random();
70
+
71
+ const xDirection = Math.cos(theta);
72
+ const yDirection = Math.sin(theta);
73
+ const normalizedThickness = 1 - radiusThickness;
74
+
75
+ position.x =
76
+ radius * normalizedThickness * xDirection +
77
+ radius * radiusThickness * randomizedDistanceRatio * xDirection;
78
+ position.y =
79
+ radius * normalizedThickness * yDirection +
80
+ radius * radiusThickness * randomizedDistanceRatio * yDirection;
81
+ position.z = 0;
82
+
83
+ const positionLength = position.length();
84
+ const normalizedAngle = Math.abs(
85
+ (positionLength / radius) * THREE.Math.degToRad(angle)
86
+ );
87
+ const sinNormalizedAngle = Math.sin(normalizedAngle);
88
+
89
+ const randomizedSpeed = THREE.MathUtils.randFloat(
90
+ startSpeed.min,
91
+ startSpeed.max
92
+ );
93
+ const speedMultiplierByPosition = 1 / positionLength;
94
+ velocity.set(
95
+ position.x *
96
+ sinNormalizedAngle *
97
+ speedMultiplierByPosition *
98
+ randomizedSpeed,
99
+ position.y *
100
+ sinNormalizedAngle *
101
+ speedMultiplierByPosition *
102
+ randomizedSpeed,
103
+ -Math.cos(normalizedAngle) * randomizedSpeed
104
+ );
105
+ };
106
+
107
+ export const calculateRandomPositionAndVelocityOnCircle = (
108
+ position,
109
+ velocity,
110
+ startSpeed,
111
+ { radius, radiusThickness, arc }
112
+ ) => {
113
+ const theta = 2 * Math.PI * Math.random() * (arc / 360);
114
+ const randomizedDistanceRatio = Math.random();
115
+
116
+ const xDirection = Math.cos(theta);
117
+ const yDirection = Math.sin(theta);
118
+ const normalizedThickness = 1 - radiusThickness;
119
+
120
+ position.x =
121
+ radius * normalizedThickness * xDirection +
122
+ radius * radiusThickness * randomizedDistanceRatio * xDirection;
123
+ position.y =
124
+ radius * normalizedThickness * yDirection +
125
+ radius * radiusThickness * randomizedDistanceRatio * yDirection;
126
+ position.z = 0;
127
+
128
+ const randomizedSpeed = THREE.MathUtils.randFloat(
129
+ startSpeed.min,
130
+ startSpeed.max
131
+ );
132
+
133
+ const positionLength = position.length();
134
+ const speedMultiplierByPosition = 1 / positionLength;
135
+ velocity.set(
136
+ position.x * speedMultiplierByPosition * randomizedSpeed,
137
+ position.y * speedMultiplierByPosition * randomizedSpeed,
138
+ 0
139
+ );
140
+ };
141
+
142
+ export const calculateRandomPositionAndVelocityOnRectangle = (
143
+ position,
144
+ velocity,
145
+ startSpeed,
146
+ { rotation, scale }
147
+ ) => {
148
+ const xOffset = Math.random() * scale.x - scale.x / 2;
149
+ const yOffset = Math.random() * scale.y - scale.y / 2;
150
+ const rotationX = THREE.Math.degToRad(rotation.x);
151
+ const rotationY = THREE.Math.degToRad(rotation.y);
152
+ position.x = xOffset * Math.cos(rotationY);
153
+ position.y = yOffset * Math.cos(rotationX);
154
+ position.z = xOffset * Math.sin(rotationY) - yOffset * Math.sin(rotationX);
155
+
156
+ const randomizedSpeed = THREE.MathUtils.randFloat(
157
+ startSpeed.min,
158
+ startSpeed.max
159
+ );
160
+ velocity.set(0, 0, -randomizedSpeed);
161
+ };
@@ -1,5 +1,13 @@
1
1
  import * as THREE from "three/build/three.module.js";
2
2
 
3
+ import {
4
+ calculateRandomPositionAndVelocityOnCircle,
5
+ calculateRandomPositionAndVelocityOnCone,
6
+ calculateRandomPositionAndVelocityOnRectangle,
7
+ calculateRandomPositionAndVelocityOnSphere,
8
+ deepMerge,
9
+ } from "./three-particles-utils.js";
10
+
3
11
  import ParticleSystemFragmentShader from "./shaders/particle-system-fragment-shader.glsl.js";
4
12
  import ParticleSystemVertexShader from "./shaders/particle-system-vertex-shader.glsl.js";
5
13
 
@@ -21,9 +29,64 @@ export const Shape = {
21
29
  RECTANGLE: "RECTANGLE",
22
30
  };
23
31
 
24
- const defaultTextureSheetAnimation = {
25
- tiles: new THREE.Vector2(1.0, 1.0),
26
- fps: 30.0,
32
+ export const TimeMode = {
33
+ LIFETIME: "LIFETIME",
34
+ FPS: "FPS",
35
+ };
36
+
37
+ export const getDefaultParticleSystemConfig = () =>
38
+ JSON.parse(JSON.stringify(DEFAULT_PARTICLE_SYSTEM_CONFIG));
39
+
40
+ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
41
+ duration: 5.0,
42
+ looping: true,
43
+ startDelay: { min: 0.0, max: 0.0 },
44
+ startLifetime: { min: 2.0, max: 2.0 },
45
+ startSpeed: { min: 1.0, max: 1.0 },
46
+ startSize: { min: 1.0, max: 1.0 },
47
+ startRotation: { min: 0.0, max: 0.0 },
48
+ startColor: {
49
+ min: { r: 1.0, g: 1.0, b: 1.0 },
50
+ max: { r: 1.0, g: 1.0, b: 1.0 },
51
+ },
52
+ startOpacity: { min: 1.0, max: 1.0 },
53
+ gravity: 0.0,
54
+ simulationSpace: SimulationSpace.LOCAL,
55
+ maxParticles: 100.0,
56
+ emission: {
57
+ rateOverTime: 10.0,
58
+ rateOverDistance: 0.0,
59
+ },
60
+ shape: {
61
+ shape: Shape.SPHERE,
62
+ sphere: {
63
+ radius: 1.0,
64
+ radiusThickness: 1.0,
65
+ arc: 360.0,
66
+ },
67
+ cone: {
68
+ angle: 25.0,
69
+ radius: 1.0,
70
+ radiusThickness: 1.0,
71
+ arc: 360.0,
72
+ },
73
+ circle: {
74
+ radius: 1.0,
75
+ radiusThickness: 1.0,
76
+ arc: 360.0,
77
+ },
78
+ rectangle: {
79
+ rotation: { x: 0.0, y: 0.0 }, // TODO: add z rotation
80
+ scale: { x: 1.0, y: 1.0 },
81
+ },
82
+ },
83
+ map: null,
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
+ },
27
90
  };
28
91
 
29
92
  const createFloat32Attributes = ({
@@ -46,109 +109,87 @@ const createFloat32Attributes = ({
46
109
  );
47
110
  };
48
111
 
49
- const getRandomStartPositionByShape = ({ shape, radius, radiusThickness }) => {
50
- const position = new THREE.Vector3();
112
+ const calculatePositionAndVelocity = (
113
+ { shape, sphere, cone, circle, rectangle },
114
+ startSpeed,
115
+ position,
116
+ velocity
117
+ ) => {
51
118
  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;
119
+ case Shape.SPHERE:
120
+ calculateRandomPositionAndVelocityOnSphere(
121
+ position,
122
+ velocity,
123
+ startSpeed,
124
+ sphere
125
+ );
126
+ break;
127
+
128
+ case Shape.CONE:
129
+ calculateRandomPositionAndVelocityOnCone(
130
+ position,
131
+ velocity,
132
+ startSpeed,
133
+ cone
134
+ );
135
+ break;
136
+
137
+ case Shape.CIRCLE:
138
+ calculateRandomPositionAndVelocityOnCircle(
139
+ position,
140
+ velocity,
141
+ startSpeed,
142
+ circle
143
+ );
72
144
  break;
73
- }
74
- }
75
145
 
76
- return position;
146
+ case Shape.RECTANGLE:
147
+ calculateRandomPositionAndVelocityOnRectangle(
148
+ position,
149
+ velocity,
150
+ startSpeed,
151
+ rectangle
152
+ );
153
+ break;
154
+ }
77
155
  };
78
156
 
79
- export const createParticleSystem = ({
80
- duration = 5.0,
81
- looping = true,
82
- startDelay = { min: 0.0, max: 0.0 },
83
- startLifeTime = { min: 5.0, max: 5.0 },
84
- startSpeed = { min: 5.0, max: 5.0 },
85
- startSize = { min: 1.0, max: 1.0 },
86
- startRotation = { min: 0.0, max: 0.0 },
87
- startColor = {
88
- min: { r: 1.0, g: 1.0, b: 1.0 },
89
- max: { r: 1.0, g: 1.0, b: 1.0 },
90
- },
91
- startOpacity = { min: 1.0, max: 1.0 },
92
- gravity = 0.0,
93
- simulationSpace = SimulationSpace.LOCAL,
94
- maxParticles = 100,
95
- emission = {
96
- rateOverTime: 10.0,
97
- rateOverDistance: 0.0,
98
- },
99
- shape = {
100
- shape: Shape.SPHERE,
101
- radius: 1.0,
102
- radiusThickness: 1.0,
103
- arc: 360.0,
104
- },
105
- map,
106
- onUpdate = null,
107
- onComplete = null,
108
- textureSheetAnimation = defaultTextureSheetAnimation,
109
- }) => {
157
+ export const createParticleSystem = (
158
+ config = DEFAULT_PARTICLE_SYSTEM_CONFIG
159
+ ) => {
110
160
  const now = Date.now();
111
161
  const lastWorldPosition = new THREE.Vector3(-99999, -99999, -99999);
112
162
  const worldPositionChange = new THREE.Vector3();
113
163
  const generalData = { distanceFromLastEmitByDistance: 0 };
114
164
 
115
- const normalizedStartDelay = { min: 0.0, max: 0.0, ...startDelay };
116
- const normalizedStartLifeTime = { min: 5.0, max: 5.0, ...startLifeTime };
117
- const normalizedStartSpeed = { min: 5.0, max: 5.0, ...startSpeed };
118
- const normalizedStartSize = { min: 1.0, max: 1.0, ...startSize };
119
- const normalizedStartRotation = { min: 0.0, max: 0.0, ...startRotation };
120
- const normalizedStartColor = {
121
- min: { r: 1.0, g: 1.0, b: 1.0, ...startColor.min },
122
- max: { r: 1.0, g: 1.0, b: 1.0, ...startColor.max },
123
- };
124
- const normalizedStartOpacity = { min: 0.0, max: 0.0, ...startOpacity };
125
- const normalizedEmission = {
126
- rateOverTime: 10.0,
127
- rateOverDistance: 0.0,
128
- ...emission,
129
- };
130
- const normalizedShape = {
131
- shape: Shape.SPHERE,
132
- radius: 1.0,
133
- radiusThickness: 1.0,
134
- arc: 360.0,
135
- ...shape,
136
- };
165
+ const {
166
+ duration,
167
+ looping,
168
+ startDelay,
169
+ startLifetime,
170
+ startSpeed,
171
+ startSize,
172
+ startRotation,
173
+ startColor,
174
+ startOpacity,
175
+ gravity,
176
+ simulationSpace,
177
+ maxParticles,
178
+ emission,
179
+ shape,
180
+ map,
181
+ onUpdate,
182
+ onComplete,
183
+ textureSheetAnimation,
184
+ } = deepMerge(DEFAULT_PARTICLE_SYSTEM_CONFIG, config);
137
185
 
138
- const rawUniforms = {
139
- ...defaultTextureSheetAnimation,
140
- ...textureSheetAnimation,
141
- };
142
- rawUniforms.tiles = rawUniforms.tiles
143
- ? rawUniforms.tiles
144
- : defaultTextureSheetAnimation.tiles;
145
-
146
- const uniforms = Object.keys(rawUniforms).reduce(
147
- (prev, key) => ({
148
- ...prev,
149
- [key]: { value: rawUniforms[key] },
150
- }),
151
- {}
186
+ const startPositions = Array.from(
187
+ { length: maxParticles },
188
+ () => new THREE.Vector3()
189
+ );
190
+ const velocities = Array.from(
191
+ { length: maxParticles },
192
+ () => new THREE.Vector3()
152
193
  );
153
194
 
154
195
  const material = new THREE.ShaderMaterial({
@@ -159,7 +200,15 @@ export const createParticleSystem = ({
159
200
  map: {
160
201
  value: map,
161
202
  },
162
- ...uniforms,
203
+ tiles: {
204
+ value: textureSheetAnimation.tiles,
205
+ },
206
+ fps: {
207
+ value: textureSheetAnimation.fps,
208
+ },
209
+ useFPSForFrameIndex: {
210
+ value: textureSheetAnimation.timeMode === TimeMode.FPS,
211
+ },
163
212
  },
164
213
  vertexShader: ParticleSystemVertexShader,
165
214
  fragmentShader: ParticleSystemFragmentShader,
@@ -171,9 +220,18 @@ export const createParticleSystem = ({
171
220
 
172
221
  const geometry = new THREE.BufferGeometry();
173
222
 
174
- const startPosition = getRandomStartPositionByShape(normalizedShape);
223
+ for (let i = 0; i < maxParticles; i++)
224
+ calculatePositionAndVelocity(
225
+ shape,
226
+ startSpeed,
227
+ startPositions[i],
228
+ velocities[i]
229
+ );
230
+
175
231
  geometry.setFromPoints(
176
- Array.from({ length: maxParticles }, () => ({ ...startPosition }))
232
+ Array.from({ length: maxParticles }, (_, index) => ({
233
+ ...startPositions[index],
234
+ }))
177
235
  );
178
236
 
179
237
  const createFloat32AttributesRequest = (propertyName, factory) => {
@@ -187,29 +245,15 @@ export const createParticleSystem = ({
187
245
 
188
246
  createFloat32AttributesRequest("isActive", false);
189
247
  createFloat32AttributesRequest("creationTime", 0);
190
- createFloat32AttributesRequest("lifeTime", 0);
191
- createFloat32AttributesRequest("startLifeTime", () =>
192
- THREE.MathUtils.randFloat(
193
- normalizedStartLifeTime.min,
194
- normalizedStartLifeTime.max
195
- )
248
+ createFloat32AttributesRequest("lifetime", 0);
249
+ createFloat32AttributesRequest("startLifetime", () =>
250
+ THREE.MathUtils.randFloat(startLifetime.min, startLifetime.max)
196
251
  );
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
252
+ createFloat32AttributesRequest("startFrame", () =>
253
+ THREE.MathUtils.randInt(
254
+ textureSheetAnimation.startFrame.min,
255
+ textureSheetAnimation.startFrame.max
256
+ )
213
257
  );
214
258
 
215
259
  createFloat32AttributesRequest("opacity", 0);
@@ -220,10 +264,7 @@ export const createParticleSystem = ({
220
264
  maxParticles,
221
265
  factory: () =>
222
266
  THREE.Math.degToRad(
223
- THREE.MathUtils.randFloat(
224
- normalizedStartRotation.min,
225
- normalizedStartRotation.max
226
- )
267
+ THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
227
268
  ),
228
269
  });
229
270
 
@@ -231,11 +272,7 @@ export const createParticleSystem = ({
231
272
  geometry,
232
273
  propertyName: "startSize",
233
274
  maxParticles,
234
- factory: () =>
235
- THREE.MathUtils.randFloat(
236
- normalizedStartSize.min,
237
- normalizedStartSize.max
238
- ),
275
+ factory: () => THREE.MathUtils.randFloat(startSize.min, startSize.max),
239
276
  });
240
277
 
241
278
  createFloat32AttributesRequest("rotation", 0);
@@ -244,30 +281,27 @@ export const createParticleSystem = ({
244
281
  createFloat32AttributesRequest(
245
282
  "colorR",
246
283
  () =>
247
- normalizedStartColor.min.r +
248
- colorRandomRatio *
249
- (normalizedStartColor.max.r - normalizedStartColor.min.r)
284
+ startColor.min.r +
285
+ colorRandomRatio * (startColor.max.r - startColor.min.r)
250
286
  );
251
287
  createFloat32AttributesRequest(
252
288
  "colorG",
253
289
  () =>
254
- normalizedStartColor.min.g +
255
- colorRandomRatio *
256
- (normalizedStartColor.max.g - normalizedStartColor.min.g)
290
+ startColor.min.g +
291
+ colorRandomRatio * (startColor.max.g - startColor.min.g)
257
292
  );
258
293
  createFloat32AttributesRequest(
259
294
  "colorB",
260
295
  () =>
261
- normalizedStartColor.min.b +
262
- colorRandomRatio *
263
- (normalizedStartColor.max.b - normalizedStartColor.min.b)
296
+ startColor.min.b +
297
+ colorRandomRatio * (startColor.max.b - startColor.min.b)
264
298
  );
265
299
  createFloat32AttributesRequest("colorA", 0);
266
300
 
267
301
  const deactivateParticle = (particleIndex) => {
268
302
  geometry.attributes.isActive.array[particleIndex] = false;
269
- geometry.attributes.lifeTime.array[particleIndex] = 0;
270
- geometry.attributes.lifeTime.needsUpdate = true;
303
+ geometry.attributes.lifetime.array[particleIndex] = 0;
304
+ geometry.attributes.lifetime.needsUpdate = true;
271
305
  geometry.attributes.colorA.array[particleIndex] = 0;
272
306
  geometry.attributes.colorA.needsUpdate = true;
273
307
  };
@@ -280,86 +314,70 @@ export const createParticleSystem = ({
280
314
  const colorRandomRatio = Math.random();
281
315
 
282
316
  geometry.attributes.colorR.array[particleIndex] =
283
- normalizedStartColor.min.r +
284
- colorRandomRatio *
285
- (normalizedStartColor.max.r - normalizedStartColor.min.r);
317
+ startColor.min.r +
318
+ colorRandomRatio * (startColor.max.r - startColor.min.r);
286
319
  geometry.attributes.colorR.needsUpdate = true;
287
320
 
288
321
  geometry.attributes.colorG.array[particleIndex] =
289
- normalizedStartColor.min.g +
290
- colorRandomRatio *
291
- (normalizedStartColor.max.g - normalizedStartColor.min.g);
322
+ startColor.min.g +
323
+ colorRandomRatio * (startColor.max.g - startColor.min.g);
292
324
  geometry.attributes.colorG.needsUpdate = true;
293
325
 
294
326
  geometry.attributes.colorB.array[particleIndex] =
295
- normalizedStartColor.min.b +
296
- colorRandomRatio *
297
- (normalizedStartColor.max.b - normalizedStartColor.min.b);
327
+ startColor.min.b +
328
+ colorRandomRatio * (startColor.max.b - startColor.min.b);
298
329
  geometry.attributes.colorB.needsUpdate = true;
299
330
 
300
331
  geometry.attributes.colorA.array[particleIndex] = THREE.MathUtils.randFloat(
301
- normalizedStartOpacity.min,
302
- normalizedStartOpacity.max
332
+ startOpacity.min,
333
+ startOpacity.max
303
334
  );
304
335
  geometry.attributes.colorA.needsUpdate = true;
305
336
 
306
- geometry.attributes.startLifeTime.array[particleIndex] =
307
- THREE.MathUtils.randFloat(
308
- normalizedStartLifeTime.min,
309
- normalizedStartLifeTime.max
310
- ) * 1000;
311
- 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;
312
347
 
313
348
  geometry.attributes.startSize.array[particleIndex] =
314
- THREE.MathUtils.randFloat(
315
- normalizedStartSize.min,
316
- normalizedStartSize.max
317
- );
349
+ THREE.MathUtils.randFloat(startSize.min, startSize.max);
318
350
  geometry.attributes.startSize.needsUpdate = true;
319
351
 
320
352
  geometry.attributes.rotation.array[particleIndex] = THREE.Math.degToRad(
321
- THREE.MathUtils.randFloat(
322
- normalizedStartRotation.min,
323
- normalizedStartRotation.max
324
- )
353
+ THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
325
354
  );
326
355
  geometry.attributes.rotation.needsUpdate = true;
327
356
 
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;
357
+ calculatePositionAndVelocity(
358
+ shape,
359
+ startSpeed,
360
+ startPositions[particleIndex],
361
+ velocities[particleIndex]
362
+ );
363
+ const positionIndex = Math.floor(particleIndex * 3);
364
+ geometry.attributes.position.array[positionIndex] =
365
+ startPositions[particleIndex].x;
366
+ geometry.attributes.position.array[positionIndex + 1] =
367
+ startPositions[particleIndex].y;
368
+ geometry.attributes.position.array[positionIndex + 2] =
369
+ startPositions[particleIndex].z;
335
370
  particleSystem.geometry.attributes.position.needsUpdate = true;
336
371
 
337
- const randomizedSpeed = THREE.MathUtils.randFloat(
338
- normalizedStartSpeed.min,
339
- normalizedStartSpeed.max
340
- );
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;
348
-
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);
354
377
  particleSystem.sortParticles = true;
355
378
 
356
379
  const calculatedCreationTime =
357
- now +
358
- THREE.MathUtils.randFloat(
359
- normalizedStartDelay.min,
360
- normalizedStartDelay.max
361
- ) *
362
- 1000;
380
+ now + THREE.MathUtils.randFloat(startDelay.min, startDelay.max) * 1000;
363
381
 
364
382
  createdParticleSystems.push({
365
383
  particleSystem,
@@ -374,8 +392,9 @@ export const createParticleSystem = ({
374
392
  looping,
375
393
  simulationSpace,
376
394
  gravity,
377
- emission: normalizedEmission,
395
+ emission,
378
396
  iterationCount: 0,
397
+ velocities,
379
398
  deactivateParticle,
380
399
  activateParticle,
381
400
  });
@@ -393,8 +412,7 @@ export const destroyParticleSystem = (particleSystem) => {
393
412
  particleSystem.parent.remove(particleSystem);
394
413
  };
395
414
 
396
- export const updateParticleSystems = ({ delta, elapsed }) => {
397
- const now = Date.now();
415
+ export const updateParticleSystems = ({ now, delta, elapsed }) => {
398
416
  createdParticleSystems.forEach((props) => {
399
417
  const {
400
418
  onUpdate,
@@ -409,12 +427,13 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
409
427
  looping,
410
428
  emission,
411
429
  iterationCount,
430
+ velocities,
412
431
  deactivateParticle,
413
432
  activateParticle,
414
433
  simulationSpace,
415
434
  gravity,
416
435
  } = props;
417
- const lifeTime = now - creationTime;
436
+ const lifetime = now - creationTime;
418
437
  particleSystem.material.uniforms.elapsed.value = elapsed;
419
438
 
420
439
  if (
@@ -433,55 +452,52 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
433
452
  particleSystem.geometry.attributes.creationTime.array.forEach(
434
453
  (entry, index) => {
435
454
  if (particleSystem.geometry.attributes.isActive.array[index]) {
436
- const particleLifeTime = now - float32Helper - entry;
455
+ const particleLifetime = now - float32Helper - entry;
437
456
  if (
438
- particleLifeTime >
439
- particleSystem.geometry.attributes.startLifeTime.array[index]
457
+ particleLifetime >
458
+ particleSystem.geometry.attributes.startLifetime.array[index]
440
459
  )
441
460
  deactivateParticle(index);
442
461
  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) {
462
+ const velocity = velocities[index];
463
+ velocity.y -= gravity;
464
+
465
+ if (
466
+ gravity !== 0 ||
467
+ velocity.x !== 0 ||
468
+ velocity.y !== 0 ||
469
+ velocity.z !== 0
470
+ ) {
471
+ const positionIndex = index * 3;
456
472
  const positionArr =
457
473
  particleSystem.geometry.attributes.position.array;
458
474
  if (simulationSpace === SimulationSpace.WORLD) {
459
- positionArr[index * 3] -= worldPositionChange.x;
460
- positionArr[index * 3 + 1] -= worldPositionChange.y;
461
- positionArr[index * 3 + 2] -= worldPositionChange.z;
475
+ positionArr[positionIndex] -= worldPositionChange.x;
476
+ positionArr[positionIndex + 1] -= worldPositionChange.y;
477
+ positionArr[positionIndex + 2] -= worldPositionChange.z;
462
478
  }
463
- positionArr[index * 3] += accelerationX * delta;
464
- positionArr[index * 3 + 1] += accelerationY * delta;
465
- positionArr[index * 3 + 2] += accelerationZ * delta;
479
+ positionArr[positionIndex] += velocity.x * delta;
480
+ positionArr[positionIndex + 1] += velocity.y * delta;
481
+ positionArr[positionIndex + 2] += velocity.z * delta;
466
482
  particleSystem.geometry.attributes.position.needsUpdate = true;
467
483
  }
468
484
 
469
- particleSystem.geometry.attributes.lifeTime.array[index] =
470
- particleLifeTime;
471
- particleSystem.geometry.attributes.lifeTime.needsUpdate = true;
485
+ particleSystem.geometry.attributes.lifetime.array[index] =
486
+ particleLifetime;
487
+ particleSystem.geometry.attributes.lifetime.needsUpdate = true;
472
488
 
473
489
  // TEMP
474
490
  particleSystem.geometry.attributes.colorA.array[index] =
475
491
  1 -
476
- particleLifeTime /
477
- particleSystem.geometry.attributes.startLifeTime.array[index];
492
+ particleLifetime /
493
+ particleSystem.geometry.attributes.startLifetime.array[index];
478
494
  particleSystem.geometry.attributes.colorA.needsUpdate = true;
479
495
  }
480
496
  }
481
497
  }
482
498
  );
483
499
 
484
- if (looping || lifeTime < duration * 1000) {
500
+ if (looping || lifetime < duration * 1000) {
485
501
  const emissionDelta = now - lastEmissionTime;
486
502
  const neededParticlesByTime = Math.floor(
487
503
  emission.rateOverTime * (emissionDelta / 1000)
@@ -529,7 +545,7 @@ export const updateParticleSystems = ({ delta, elapsed }) => {
529
545
  particleSystem,
530
546
  delta,
531
547
  elapsed,
532
- lifeTime,
548
+ lifetime,
533
549
  iterationCount: iterationCount + 1,
534
550
  });
535
551
  } else if (onComplete)