@newkrok/three-particles 0.3.1 → 0.6.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 +1 -1
- package/package.json +7 -3
- package/src/js/effects/three-particles/three-particles-bezier.js +36 -0
- package/src/js/effects/three-particles/three-particles-curves.js +1 -0
- package/src/js/effects/three-particles/three-particles-modifiers.js +66 -5
- package/src/js/effects/three-particles/three-particles-utils.js +63 -3
- package/src/js/effects/three-particles.js +231 -67
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newkrok/three-particles",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
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
|
],
|
|
@@ -23,8 +26,9 @@
|
|
|
23
26
|
},
|
|
24
27
|
"homepage": "https://github.com/NewKrok/three-particles#readme",
|
|
25
28
|
"dependencies": {
|
|
26
|
-
"
|
|
27
|
-
"
|
|
29
|
+
"easing-functions": "1.0.1",
|
|
30
|
+
"three": "0.137.0",
|
|
31
|
+
"three-noise": "^1.1.1"
|
|
28
32
|
},
|
|
29
33
|
"scripts": {
|
|
30
34
|
"test": "echo \"Error: no test specified\" && exit 1"
|
|
@@ -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,10 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
|
|
1
3
|
import { getCurveFunction } from "./three-particles-curves.js";
|
|
2
4
|
|
|
3
|
-
const
|
|
5
|
+
const noiseInput = new THREE.Vector3(0, 0, 0);
|
|
6
|
+
|
|
7
|
+
const curveModifiers = [
|
|
4
8
|
// {key:"colorOverLifetime", attributeKeys:["colorR", "colorG", "colorB"]},
|
|
5
9
|
{
|
|
6
10
|
key: "opacityOverLifetime",
|
|
@@ -15,17 +19,20 @@ const modifiers = [
|
|
|
15
19
|
];
|
|
16
20
|
|
|
17
21
|
export const applyModifiers = ({
|
|
22
|
+
delta,
|
|
23
|
+
noise,
|
|
18
24
|
startValues,
|
|
25
|
+
lifetimeValues,
|
|
19
26
|
normalizedConfig,
|
|
20
27
|
attributes,
|
|
21
28
|
particleLifetimePercentage,
|
|
22
29
|
particleIndex,
|
|
23
30
|
forceUpdate = false,
|
|
24
31
|
}) => {
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
if (
|
|
28
|
-
const multiplier = getCurveFunction(
|
|
32
|
+
curveModifiers.forEach(({ key, attributeKeys, startValueKeys }) => {
|
|
33
|
+
const curveModifier = normalizedConfig[key];
|
|
34
|
+
if (curveModifier.isActive) {
|
|
35
|
+
const multiplier = getCurveFunction(curveModifier.curveFunction)(
|
|
29
36
|
particleLifetimePercentage
|
|
30
37
|
);
|
|
31
38
|
attributeKeys.forEach((attributeKey, index) => {
|
|
@@ -41,4 +48,58 @@ export const applyModifiers = ({
|
|
|
41
48
|
});
|
|
42
49
|
}
|
|
43
50
|
});
|
|
51
|
+
|
|
52
|
+
if (lifetimeValues.rotationOverLifetime) {
|
|
53
|
+
attributes.rotation.array[particleIndex] +=
|
|
54
|
+
lifetimeValues.rotationOverLifetime[particleIndex] * delta * 0.02;
|
|
55
|
+
attributes.rotation.needsUpdate = true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (noise.isActive) {
|
|
59
|
+
const {
|
|
60
|
+
sampler,
|
|
61
|
+
strength,
|
|
62
|
+
offsets,
|
|
63
|
+
positionAmount,
|
|
64
|
+
rotationAmount,
|
|
65
|
+
sizeAmount,
|
|
66
|
+
} = noise;
|
|
67
|
+
const positionIndex = particleIndex * 3;
|
|
68
|
+
const positionArr = attributes.position.array;
|
|
69
|
+
let noiseOnPosition;
|
|
70
|
+
|
|
71
|
+
const noisePosition =
|
|
72
|
+
(particleLifetimePercentage + (offsets ? offsets[particleIndex] : 0)) *
|
|
73
|
+
10 *
|
|
74
|
+
strength;
|
|
75
|
+
const noisePower = 0.15 * strength;
|
|
76
|
+
|
|
77
|
+
noiseInput.set(noisePosition, 0, 0);
|
|
78
|
+
noiseOnPosition = sampler.get3(noiseInput);
|
|
79
|
+
positionArr[positionIndex] += noiseOnPosition * noisePower * positionAmount;
|
|
80
|
+
|
|
81
|
+
if (rotationAmount !== 0) {
|
|
82
|
+
attributes.rotation.array[particleIndex] +=
|
|
83
|
+
noiseOnPosition * noisePower * rotationAmount;
|
|
84
|
+
attributes.rotation.needsUpdate = true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (sizeAmount !== 0) {
|
|
88
|
+
attributes.size.array[particleIndex] +=
|
|
89
|
+
noiseOnPosition * noisePower * sizeAmount;
|
|
90
|
+
attributes.size.needsUpdate = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
noiseInput.set(noisePosition, noisePosition, 0);
|
|
94
|
+
noiseOnPosition = sampler.get3(noiseInput);
|
|
95
|
+
positionArr[positionIndex + 1] +=
|
|
96
|
+
noiseOnPosition * noisePower * positionAmount;
|
|
97
|
+
|
|
98
|
+
noiseInput.set(noisePosition, noisePosition, noisePosition);
|
|
99
|
+
noiseOnPosition = sampler.get3(noiseInput);
|
|
100
|
+
positionArr[positionIndex + 2] +=
|
|
101
|
+
noiseOnPosition * noisePower * positionAmount;
|
|
102
|
+
|
|
103
|
+
attributes.position.needsUpdate = true;
|
|
104
|
+
}
|
|
44
105
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import * as THREE from "three
|
|
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,10 +10,20 @@ 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 (
|
|
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
|
-
result[key] =
|
|
21
|
+
result[key] =
|
|
22
|
+
objectB[key] === 0
|
|
23
|
+
? 0
|
|
24
|
+
: objectB[key] === false
|
|
25
|
+
? false
|
|
26
|
+
: objectB[key] || objectA[key];
|
|
15
27
|
if (config.applyToFirstObject) objectA[key] = result[key];
|
|
16
28
|
}
|
|
17
29
|
}
|
|
@@ -104,6 +116,54 @@ export const calculateRandomPositionAndVelocityOnCone = (
|
|
|
104
116
|
);
|
|
105
117
|
};
|
|
106
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
|
+
|
|
107
167
|
export const calculateRandomPositionAndVelocityOnCircle = (
|
|
108
168
|
position,
|
|
109
169
|
velocity,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import * as THREE from "three
|
|
1
|
+
import * as THREE from "three";
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
|
+
calculateRandomPositionAndVelocityOnBox,
|
|
4
5
|
calculateRandomPositionAndVelocityOnCircle,
|
|
5
6
|
calculateRandomPositionAndVelocityOnCone,
|
|
6
7
|
calculateRandomPositionAndVelocityOnRectangle,
|
|
@@ -9,12 +10,11 @@ import {
|
|
|
9
10
|
} from "./three-particles/three-particles-utils.js";
|
|
10
11
|
|
|
11
12
|
import { CurveFunction } from "./three-particles/three-particles-curves.js";
|
|
13
|
+
import { FBM } from "three-noise/build/three-noise.module.js";
|
|
12
14
|
import ParticleSystemFragmentShader from "./three-particles/shaders/particle-system-fragment-shader.glsl.js";
|
|
13
15
|
import ParticleSystemVertexShader from "./three-particles/shaders/particle-system-vertex-shader.glsl.js";
|
|
14
16
|
import { applyModifiers } from "./three-particles/three-particles-modifiers.js";
|
|
15
|
-
|
|
16
|
-
// Float32Array is not enough accurate when we are storing timestamp in it so we just remove unnecessary time
|
|
17
|
-
const float32Helper = 1638200000000;
|
|
17
|
+
import { createBezierCurveFunction } from "./three-particles/three-particles-bezier";
|
|
18
18
|
|
|
19
19
|
let createdParticleSystems = [];
|
|
20
20
|
|
|
@@ -31,11 +31,25 @@ export const Shape = {
|
|
|
31
31
|
RECTANGLE: "RECTANGLE",
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
export const EmitFrom = {
|
|
35
|
+
VOLUME: "VOLUME",
|
|
36
|
+
SHELL: "SHELL",
|
|
37
|
+
EDGE: "EDGE",
|
|
38
|
+
};
|
|
39
|
+
|
|
34
40
|
export const TimeMode = {
|
|
35
41
|
LIFETIME: "LIFETIME",
|
|
36
42
|
FPS: "FPS",
|
|
37
43
|
};
|
|
38
44
|
|
|
45
|
+
export const blendingMap = {
|
|
46
|
+
"THREE.NoBlending": THREE.NoBlending,
|
|
47
|
+
"THREE.NormalBlending": THREE.NormalBlending,
|
|
48
|
+
"THREE.AdditiveBlending": THREE.AdditiveBlending,
|
|
49
|
+
"THREE.SubtractiveBlending": THREE.SubtractiveBlending,
|
|
50
|
+
"THREE.MultiplyBlending": THREE.MultiplyBlending,
|
|
51
|
+
};
|
|
52
|
+
|
|
39
53
|
export const getDefaultParticleSystemConfig = () =>
|
|
40
54
|
JSON.parse(JSON.stringify(DEFAULT_PARTICLE_SYSTEM_CONFIG));
|
|
41
55
|
|
|
@@ -86,11 +100,38 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
|
|
|
86
100
|
rotation: { x: 0.0, y: 0.0 }, // TODO: add z rotation
|
|
87
101
|
scale: { x: 1.0, y: 1.0 },
|
|
88
102
|
},
|
|
103
|
+
box: {
|
|
104
|
+
scale: { x: 1.0, y: 1.0, z: 1.0 },
|
|
105
|
+
emitFrom: EmitFrom.VOLUME,
|
|
106
|
+
},
|
|
89
107
|
},
|
|
90
108
|
map: null,
|
|
109
|
+
renderer: {
|
|
110
|
+
blending: THREE.NormalBlending,
|
|
111
|
+
transparent: true,
|
|
112
|
+
depthTest: true,
|
|
113
|
+
depthWrite: false,
|
|
114
|
+
},
|
|
115
|
+
velocityOverLifetime: {
|
|
116
|
+
isActive: false,
|
|
117
|
+
linear: {
|
|
118
|
+
x: { min: 0, max: 0 },
|
|
119
|
+
y: { min: 0, max: 0 },
|
|
120
|
+
z: { min: 0, max: 0 },
|
|
121
|
+
},
|
|
122
|
+
orbital: {
|
|
123
|
+
x: { min: 0, max: 0 },
|
|
124
|
+
y: { min: 0, max: 0 },
|
|
125
|
+
z: { min: 0, max: 0 },
|
|
126
|
+
},
|
|
127
|
+
},
|
|
91
128
|
sizeOverLifetime: {
|
|
92
129
|
isActive: false,
|
|
93
|
-
curveFunction: CurveFunction.
|
|
130
|
+
curveFunction: CurveFunction.BEZIER,
|
|
131
|
+
bezierPoints: [
|
|
132
|
+
{ x: 0, y: 0, percentage: 0 },
|
|
133
|
+
{ x: 1, y: 1, percentage: 1 },
|
|
134
|
+
],
|
|
94
135
|
},
|
|
95
136
|
/* colorOverLifetime: {
|
|
96
137
|
isActive: false,
|
|
@@ -98,7 +139,26 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
|
|
|
98
139
|
}, */
|
|
99
140
|
opacityOverLifetime: {
|
|
100
141
|
isActive: false,
|
|
101
|
-
curveFunction: CurveFunction.
|
|
142
|
+
curveFunction: CurveFunction.BEZIER,
|
|
143
|
+
bezierPoints: [
|
|
144
|
+
{ x: 0, y: 0, percentage: 0 },
|
|
145
|
+
{ x: 1, y: 1, percentage: 1 },
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
rotationOverLifetime: {
|
|
149
|
+
isActive: false,
|
|
150
|
+
min: 0.0,
|
|
151
|
+
max: 0.0,
|
|
152
|
+
},
|
|
153
|
+
noise: {
|
|
154
|
+
isActive: false,
|
|
155
|
+
useRandomOffset: false,
|
|
156
|
+
strength: 1.0,
|
|
157
|
+
frequency: 0.5,
|
|
158
|
+
octaves: 1,
|
|
159
|
+
positionAmount: 1.0,
|
|
160
|
+
rotationAmount: 0.0,
|
|
161
|
+
sizeAmount: 0.0,
|
|
102
162
|
},
|
|
103
163
|
textureSheetAnimation: {
|
|
104
164
|
tiles: new THREE.Vector2(1.0, 1.0),
|
|
@@ -129,10 +189,11 @@ const createFloat32Attributes = ({
|
|
|
129
189
|
};
|
|
130
190
|
|
|
131
191
|
const calculatePositionAndVelocity = (
|
|
132
|
-
{ shape, sphere, cone, circle, rectangle },
|
|
192
|
+
{ shape, sphere, cone, circle, rectangle, box },
|
|
133
193
|
startSpeed,
|
|
134
194
|
position,
|
|
135
|
-
velocity
|
|
195
|
+
velocity,
|
|
196
|
+
velocityOverLifetime
|
|
136
197
|
) => {
|
|
137
198
|
switch (shape) {
|
|
138
199
|
case Shape.SPHERE:
|
|
@@ -170,6 +231,30 @@ const calculatePositionAndVelocity = (
|
|
|
170
231
|
rectangle
|
|
171
232
|
);
|
|
172
233
|
break;
|
|
234
|
+
|
|
235
|
+
case Shape.BOX:
|
|
236
|
+
calculateRandomPositionAndVelocityOnBox(
|
|
237
|
+
position,
|
|
238
|
+
velocity,
|
|
239
|
+
startSpeed,
|
|
240
|
+
box
|
|
241
|
+
);
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (velocityOverLifetime.isActive) {
|
|
246
|
+
velocity.x += THREE.MathUtils.randFloat(
|
|
247
|
+
velocityOverLifetime.linear.x.min,
|
|
248
|
+
velocityOverLifetime.linear.x.max
|
|
249
|
+
);
|
|
250
|
+
velocity.y += THREE.MathUtils.randFloat(
|
|
251
|
+
velocityOverLifetime.linear.y.min,
|
|
252
|
+
velocityOverLifetime.linear.y.max
|
|
253
|
+
);
|
|
254
|
+
velocity.z += THREE.MathUtils.randFloat(
|
|
255
|
+
velocityOverLifetime.linear.z.min,
|
|
256
|
+
velocityOverLifetime.linear.z.max
|
|
257
|
+
);
|
|
173
258
|
}
|
|
174
259
|
};
|
|
175
260
|
|
|
@@ -187,9 +272,28 @@ export const createParticleSystem = (
|
|
|
187
272
|
worldEuler: new THREE.Euler(),
|
|
188
273
|
gravityVelocity: new THREE.Vector3(0, 0, 0),
|
|
189
274
|
startValues: {},
|
|
275
|
+
lifetimeValues: {},
|
|
276
|
+
creationTimes: [],
|
|
277
|
+
noise: null,
|
|
190
278
|
};
|
|
191
279
|
|
|
192
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
|
+
|
|
193
297
|
const {
|
|
194
298
|
transform,
|
|
195
299
|
duration,
|
|
@@ -207,11 +311,17 @@ export const createParticleSystem = (
|
|
|
207
311
|
emission,
|
|
208
312
|
shape,
|
|
209
313
|
map,
|
|
314
|
+
renderer,
|
|
315
|
+
noise,
|
|
316
|
+
velocityOverLifetime,
|
|
210
317
|
onUpdate,
|
|
211
318
|
onComplete,
|
|
212
319
|
textureSheetAnimation,
|
|
213
320
|
} = normalizedConfig;
|
|
214
321
|
|
|
322
|
+
if (typeof renderer.blending === "string")
|
|
323
|
+
renderer.blending = blendingMap[renderer.blending];
|
|
324
|
+
|
|
215
325
|
const startPositions = Array.from(
|
|
216
326
|
{ length: maxParticles },
|
|
217
327
|
() => new THREE.Vector3()
|
|
@@ -221,6 +331,8 @@ export const createParticleSystem = (
|
|
|
221
331
|
() => new THREE.Vector3()
|
|
222
332
|
);
|
|
223
333
|
|
|
334
|
+
generalData.creationTimes = Array.from({ length: maxParticles }, () => 0);
|
|
335
|
+
|
|
224
336
|
const startValueKeys = ["startSize", "startOpacity"];
|
|
225
337
|
startValueKeys.forEach((key) => {
|
|
226
338
|
generalData.startValues[key] = Array.from({ length: maxParticles }, () =>
|
|
@@ -231,6 +343,37 @@ export const createParticleSystem = (
|
|
|
231
343
|
);
|
|
232
344
|
});
|
|
233
345
|
|
|
346
|
+
const lifetimeValueKeys = ["rotationOverLifetime"];
|
|
347
|
+
lifetimeValueKeys.forEach((key) => {
|
|
348
|
+
if (normalizedConfig[key].isActive)
|
|
349
|
+
generalData.lifetimeValues[key] = Array.from(
|
|
350
|
+
{ length: maxParticles },
|
|
351
|
+
() =>
|
|
352
|
+
THREE.MathUtils.randFloat(
|
|
353
|
+
normalizedConfig[key].min,
|
|
354
|
+
normalizedConfig[key].max
|
|
355
|
+
)
|
|
356
|
+
);
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
generalData.noise = {
|
|
360
|
+
isActive: noise.isActive,
|
|
361
|
+
strength: noise.strength,
|
|
362
|
+
positionAmount: noise.positionAmount,
|
|
363
|
+
rotationAmount: noise.rotationAmount,
|
|
364
|
+
sizeAmount: noise.sizeAmount,
|
|
365
|
+
sampler: noise.isActive
|
|
366
|
+
? new FBM({
|
|
367
|
+
seed: Math.random(),
|
|
368
|
+
scale: noise.frequency,
|
|
369
|
+
octaves: noise.octaves,
|
|
370
|
+
})
|
|
371
|
+
: null,
|
|
372
|
+
offsets: noise.useRandomOffset
|
|
373
|
+
? Array.from({ length: maxParticles }, () => Math.random() * 100)
|
|
374
|
+
: null,
|
|
375
|
+
};
|
|
376
|
+
|
|
234
377
|
const material = new THREE.ShaderMaterial({
|
|
235
378
|
uniforms: {
|
|
236
379
|
elapsed: {
|
|
@@ -251,10 +394,10 @@ export const createParticleSystem = (
|
|
|
251
394
|
},
|
|
252
395
|
vertexShader: ParticleSystemVertexShader,
|
|
253
396
|
fragmentShader: ParticleSystemFragmentShader,
|
|
254
|
-
transparent:
|
|
255
|
-
blending:
|
|
256
|
-
depthTest:
|
|
257
|
-
depthWrite:
|
|
397
|
+
transparent: renderer.transparent,
|
|
398
|
+
blending: renderer.blending,
|
|
399
|
+
depthTest: renderer.depthTest,
|
|
400
|
+
depthWrite: renderer.depthWrite,
|
|
258
401
|
});
|
|
259
402
|
|
|
260
403
|
const geometry = new THREE.BufferGeometry();
|
|
@@ -264,7 +407,8 @@ export const createParticleSystem = (
|
|
|
264
407
|
shape,
|
|
265
408
|
startSpeed,
|
|
266
409
|
startPositions[i],
|
|
267
|
-
velocities[i]
|
|
410
|
+
velocities[i],
|
|
411
|
+
velocityOverLifetime
|
|
268
412
|
);
|
|
269
413
|
|
|
270
414
|
geometry.setFromPoints(
|
|
@@ -283,7 +427,6 @@ export const createParticleSystem = (
|
|
|
283
427
|
};
|
|
284
428
|
|
|
285
429
|
createFloat32AttributesRequest("isActive", false);
|
|
286
|
-
createFloat32AttributesRequest("creationTime", 0);
|
|
287
430
|
createFloat32AttributesRequest("lifetime", 0);
|
|
288
431
|
createFloat32AttributesRequest("startLifetime", () =>
|
|
289
432
|
THREE.MathUtils.randFloat(startLifetime.min, startLifetime.max)
|
|
@@ -339,8 +482,10 @@ export const createParticleSystem = (
|
|
|
339
482
|
|
|
340
483
|
const activateParticle = ({ particleIndex, activationTime }) => {
|
|
341
484
|
geometry.attributes.isActive.array[particleIndex] = true;
|
|
342
|
-
|
|
343
|
-
|
|
485
|
+
generalData.creationTimes[particleIndex] = activationTime;
|
|
486
|
+
|
|
487
|
+
if (generalData.noise.offsets)
|
|
488
|
+
generalData.noise.offsets[particleIndex] = Math.random() * 100;
|
|
344
489
|
|
|
345
490
|
const colorRandomRatio = Math.random();
|
|
346
491
|
|
|
@@ -379,6 +524,13 @@ export const createParticleSystem = (
|
|
|
379
524
|
THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
|
|
380
525
|
);
|
|
381
526
|
|
|
527
|
+
if (normalizedConfig.rotationOverLifetime.isActive)
|
|
528
|
+
generalData.lifetimeValues.rotationOverLifetime[particleIndex] =
|
|
529
|
+
THREE.MathUtils.randFloat(
|
|
530
|
+
normalizedConfig.rotationOverLifetime.min,
|
|
531
|
+
normalizedConfig.rotationOverLifetime.max
|
|
532
|
+
);
|
|
533
|
+
|
|
382
534
|
geometry.attributes.rotation.needsUpdate = true;
|
|
383
535
|
geometry.attributes.colorB.needsUpdate = true;
|
|
384
536
|
|
|
@@ -386,7 +538,8 @@ export const createParticleSystem = (
|
|
|
386
538
|
shape,
|
|
387
539
|
startSpeed,
|
|
388
540
|
startPositions[particleIndex],
|
|
389
|
-
velocities[particleIndex]
|
|
541
|
+
velocities[particleIndex],
|
|
542
|
+
velocityOverLifetime
|
|
390
543
|
);
|
|
391
544
|
const positionIndex = Math.floor(particleIndex * 3);
|
|
392
545
|
geometry.attributes.position.array[positionIndex] =
|
|
@@ -395,13 +548,17 @@ export const createParticleSystem = (
|
|
|
395
548
|
startPositions[particleIndex].y;
|
|
396
549
|
geometry.attributes.position.array[positionIndex + 2] =
|
|
397
550
|
startPositions[particleIndex].z;
|
|
398
|
-
|
|
551
|
+
geometry.attributes.position.needsUpdate = true;
|
|
399
552
|
|
|
400
553
|
geometry.attributes.lifetime.array[particleIndex] = 0;
|
|
401
554
|
geometry.attributes.lifetime.needsUpdate = true;
|
|
402
555
|
|
|
403
556
|
applyModifiers({
|
|
557
|
+
delta: 0,
|
|
558
|
+
elapsed: 0,
|
|
559
|
+
noise: generalData.noise,
|
|
404
560
|
startValues: generalData.startValues,
|
|
561
|
+
lifetimeValues: generalData.lifetimeValues,
|
|
405
562
|
normalizedConfig,
|
|
406
563
|
attributes: particleSystem.geometry.attributes,
|
|
407
564
|
particleLifetimePercentage: 0,
|
|
@@ -489,12 +646,13 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
|
|
|
489
646
|
particleSystem.material.uniforms.elapsed.value = elapsed;
|
|
490
647
|
|
|
491
648
|
particleSystem.getWorldPosition(currentWorldPosition);
|
|
492
|
-
if (lastWorldPosition.x !== -99999)
|
|
649
|
+
if (lastWorldPosition.x !== -99999) {
|
|
493
650
|
worldPositionChange.set(
|
|
494
651
|
currentWorldPosition.x - lastWorldPosition.x,
|
|
495
652
|
currentWorldPosition.y - lastWorldPosition.y,
|
|
496
653
|
currentWorldPosition.z - lastWorldPosition.z
|
|
497
654
|
);
|
|
655
|
+
}
|
|
498
656
|
generalData.distanceFromLastEmitByDistance += worldPositionChange.length();
|
|
499
657
|
particleSystem.getWorldPosition(lastWorldPosition);
|
|
500
658
|
|
|
@@ -519,59 +677,65 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
|
|
|
519
677
|
particleSystem.updateMatrixWorld();
|
|
520
678
|
}
|
|
521
679
|
|
|
522
|
-
|
|
523
|
-
(
|
|
524
|
-
|
|
525
|
-
|
|
680
|
+
generalData.creationTimes.forEach((entry, index) => {
|
|
681
|
+
if (particleSystem.geometry.attributes.isActive.array[index]) {
|
|
682
|
+
const particleLifetime = now - entry;
|
|
683
|
+
if (
|
|
684
|
+
particleLifetime >
|
|
685
|
+
particleSystem.geometry.attributes.startLifetime.array[index]
|
|
686
|
+
)
|
|
687
|
+
deactivateParticle(index);
|
|
688
|
+
else {
|
|
689
|
+
const velocity = velocities[index];
|
|
690
|
+
velocity.x -= gravityVelocity.x;
|
|
691
|
+
velocity.y -= gravityVelocity.y;
|
|
692
|
+
velocity.z -= gravityVelocity.z;
|
|
693
|
+
|
|
526
694
|
if (
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
const positionIndex = index * 3;
|
|
544
|
-
const positionArr =
|
|
545
|
-
particleSystem.geometry.attributes.position.array;
|
|
546
|
-
if (simulationSpace === SimulationSpace.WORLD) {
|
|
547
|
-
positionArr[positionIndex] -= worldPositionChange.x;
|
|
548
|
-
positionArr[positionIndex + 1] -= worldPositionChange.y;
|
|
549
|
-
positionArr[positionIndex + 2] -= worldPositionChange.z;
|
|
550
|
-
}
|
|
551
|
-
positionArr[positionIndex] += velocity.x * delta;
|
|
552
|
-
positionArr[positionIndex + 1] += velocity.y * delta;
|
|
553
|
-
positionArr[positionIndex + 2] += velocity.z * delta;
|
|
554
|
-
particleSystem.geometry.attributes.position.needsUpdate = true;
|
|
695
|
+
gravity !== 0 ||
|
|
696
|
+
velocity.x !== 0 ||
|
|
697
|
+
velocity.y !== 0 ||
|
|
698
|
+
velocity.z !== 0 ||
|
|
699
|
+
worldPositionChange.x !== 0 ||
|
|
700
|
+
worldPositionChange.y !== 0 ||
|
|
701
|
+
worldPositionChange.z !== 0
|
|
702
|
+
) {
|
|
703
|
+
const positionIndex = index * 3;
|
|
704
|
+
const positionArr =
|
|
705
|
+
particleSystem.geometry.attributes.position.array;
|
|
706
|
+
|
|
707
|
+
if (simulationSpace === SimulationSpace.WORLD) {
|
|
708
|
+
positionArr[positionIndex] -= worldPositionChange.x;
|
|
709
|
+
positionArr[positionIndex + 1] -= worldPositionChange.y;
|
|
710
|
+
positionArr[positionIndex + 2] -= worldPositionChange.z;
|
|
555
711
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
particleSystem.geometry.attributes.
|
|
560
|
-
|
|
561
|
-
const particleLifetimePercentage =
|
|
562
|
-
particleLifetime /
|
|
563
|
-
particleSystem.geometry.attributes.startLifetime.array[index];
|
|
564
|
-
applyModifiers({
|
|
565
|
-
startValues: generalData.startValues,
|
|
566
|
-
normalizedConfig,
|
|
567
|
-
attributes: particleSystem.geometry.attributes,
|
|
568
|
-
particleLifetimePercentage,
|
|
569
|
-
particleIndex: index,
|
|
570
|
-
});
|
|
712
|
+
positionArr[positionIndex] += velocity.x * delta;
|
|
713
|
+
positionArr[positionIndex + 1] += velocity.y * delta;
|
|
714
|
+
positionArr[positionIndex + 2] += velocity.z * delta;
|
|
715
|
+
particleSystem.geometry.attributes.position.needsUpdate = true;
|
|
571
716
|
}
|
|
717
|
+
|
|
718
|
+
particleSystem.geometry.attributes.lifetime.array[index] =
|
|
719
|
+
particleLifetime;
|
|
720
|
+
particleSystem.geometry.attributes.lifetime.needsUpdate = true;
|
|
721
|
+
|
|
722
|
+
const particleLifetimePercentage =
|
|
723
|
+
particleLifetime /
|
|
724
|
+
particleSystem.geometry.attributes.startLifetime.array[index];
|
|
725
|
+
applyModifiers({
|
|
726
|
+
delta,
|
|
727
|
+
elapsed,
|
|
728
|
+
noise: generalData.noise,
|
|
729
|
+
startValues: generalData.startValues,
|
|
730
|
+
lifetimeValues: generalData.lifetimeValues,
|
|
731
|
+
normalizedConfig,
|
|
732
|
+
attributes: particleSystem.geometry.attributes,
|
|
733
|
+
particleLifetimePercentage,
|
|
734
|
+
particleIndex: index,
|
|
735
|
+
});
|
|
572
736
|
}
|
|
573
737
|
}
|
|
574
|
-
);
|
|
738
|
+
});
|
|
575
739
|
|
|
576
740
|
if (looping || lifetime < duration * 1000) {
|
|
577
741
|
const emissionDelta = now - lastEmissionTime;
|