@newkrok/three-particles 0.2.2 → 0.4.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 +1 -1
- package/package.json +4 -2
- package/src/js/effects/{shaders → three-particles/shaders}/particle-system-fragment-shader.glsl.js +0 -0
- package/src/js/effects/{shaders → three-particles/shaders}/particle-system-vertex-shader.glsl.js +2 -2
- package/src/js/effects/three-particles/three-particles-curves.js +74 -0
- package/src/js/effects/three-particles/three-particles-modifiers.js +106 -0
- package/src/js/effects/{three-particles-utils.js → three-particles/three-particles-utils.js} +10 -5
- package/src/js/effects/three-particles.js +291 -102
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.4.1",
|
|
4
4
|
"description": "Particle system for ThreeJS",
|
|
5
5
|
"main": "src/js/three-particles.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,9 @@
|
|
|
23
23
|
},
|
|
24
24
|
"homepage": "https://github.com/NewKrok/three-particles#readme",
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"
|
|
26
|
+
"easing-functions": "1.0.1",
|
|
27
|
+
"three": "0.136.0",
|
|
28
|
+
"three-noise": "^1.1.1"
|
|
27
29
|
},
|
|
28
30
|
"scripts": {
|
|
29
31
|
"test": "echo \"Error: no test specified\" && exit 1"
|
package/src/js/effects/{shaders → three-particles/shaders}/particle-system-fragment-shader.glsl.js
RENAMED
|
File without changes
|
package/src/js/effects/{shaders → three-particles/shaders}/particle-system-vertex-shader.glsl.js
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const ParticleSystemVertexShader = `
|
|
2
|
-
attribute float
|
|
2
|
+
attribute float size;
|
|
3
3
|
attribute float colorR;
|
|
4
4
|
attribute float colorG;
|
|
5
5
|
attribute float colorB;
|
|
@@ -25,7 +25,7 @@ const ParticleSystemVertexShader = `
|
|
|
25
25
|
vStartFrame = startFrame;
|
|
26
26
|
|
|
27
27
|
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
28
|
-
gl_PointSize =
|
|
28
|
+
gl_PointSize = size * (100.0 / length(mvPosition.xyz));
|
|
29
29
|
gl_Position = projectionMatrix * mvPosition;
|
|
30
30
|
}
|
|
31
31
|
`;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import Easing from "easing-functions";
|
|
2
|
+
|
|
3
|
+
export const CurveFunction = {
|
|
4
|
+
LINEAR: "LINEAR",
|
|
5
|
+
QUADRATIC_IN: "QUADRATIC_IN",
|
|
6
|
+
QUADRATIC_OUT: "QUADRATIC_OUT",
|
|
7
|
+
QUADRATIC_IN_OUT: "QUADRATIC_IN_OUT",
|
|
8
|
+
CUBIC_IN: "CUBIC_IN",
|
|
9
|
+
CUBIC_OUT: "CUBIC_OUT",
|
|
10
|
+
CUBIC_IN_OUT: "CUBIC_IN_OUT",
|
|
11
|
+
QUARTIC_IN: "QUARTIC_IN",
|
|
12
|
+
QUARTIC_OUT: "QUARTIC_OUT",
|
|
13
|
+
QUARTIC_IN_OUT: "QUARTIC_IN_OUT",
|
|
14
|
+
QUINTIC_IN: "QUINTIC_IN",
|
|
15
|
+
QUINTIC_OUT: "QUINTIC_OUT",
|
|
16
|
+
QUINTIC_IN_OUT: "QUINTIC_IN_OUT",
|
|
17
|
+
SINUSOIDAL_IN: "SINUSOIDAL_IN",
|
|
18
|
+
SINUSOIDAL_OUT: "SINUSOIDAL_OUT",
|
|
19
|
+
SINUSOIDAL_IN_OUT: "SINUSOIDAL_IN_OUT",
|
|
20
|
+
EXPONENTIAL_IN: "EXPONENTIAL_IN",
|
|
21
|
+
EXPONENTIAL_OUT: "EXPONENTIAL_OUT",
|
|
22
|
+
EXPONENTIAL_IN_OUT: "EXPONENTIAL_IN_OUT",
|
|
23
|
+
CIRCULAR_IN: "CIRCULAR_IN",
|
|
24
|
+
CIRCULAR_OUT: "CIRCULAR_OUT",
|
|
25
|
+
CIRCULAR_IN_OUT: "CIRCULAR_IN_OUT",
|
|
26
|
+
ELASTIC_IN: "ELASTIC_IN",
|
|
27
|
+
ELASTIC_OUT: "ELASTIC_OUT",
|
|
28
|
+
ELASTIC_IN_OUT: "ELASTIC_IN_OUT",
|
|
29
|
+
BACK_IN: "BACK_IN",
|
|
30
|
+
BACK_OUT: "BACK_OUT",
|
|
31
|
+
BACK_IN_OUT: "BACK_IN_OUT",
|
|
32
|
+
BOUNCE_IN: "BOUNCE_IN",
|
|
33
|
+
BOUNCE_OUT: "BOUNCE_OUT",
|
|
34
|
+
BOUNCE_IN_OUT: "BOUNCE_IN_OUT",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const CurveFunctionMap = {
|
|
38
|
+
[CurveFunction.LINEAR]: Easing.Linear.None,
|
|
39
|
+
[CurveFunction.QUADRATIC_IN]: Easing.Quadratic.In,
|
|
40
|
+
[CurveFunction.QUADRATIC_OUT]: Easing.Quadratic.Out,
|
|
41
|
+
[CurveFunction.QUADRATIC_IN_OUT]: Easing.Quadratic.InOut,
|
|
42
|
+
[CurveFunction.CUBIC_IN]: Easing.Cubic.In,
|
|
43
|
+
[CurveFunction.CUBIC_OUT]: Easing.Cubic.Out,
|
|
44
|
+
[CurveFunction.CUBIC_IN_OUT]: Easing.Cubic.InOut,
|
|
45
|
+
[CurveFunction.QUARTIC_IN]: Easing.Quartic.In,
|
|
46
|
+
[CurveFunction.QUARTIC_OUT]: Easing.Quartic.Out,
|
|
47
|
+
[CurveFunction.QUARTIC_IN_OUT]: Easing.Quartic.InOut,
|
|
48
|
+
[CurveFunction.QUINTIC_IN]: Easing.Quintic.In,
|
|
49
|
+
[CurveFunction.QUINTIC_OUT]: Easing.Quintic.Out,
|
|
50
|
+
[CurveFunction.QUINTIC_IN_OUT]: Easing.Quintic.InOut,
|
|
51
|
+
[CurveFunction.SINUSOIDAL_IN]: Easing.Sinusoidal.In,
|
|
52
|
+
[CurveFunction.SINUSOIDAL_OUT]: Easing.Sinusoidal.Out,
|
|
53
|
+
[CurveFunction.SINUSOIDAL_IN_OUT]: Easing.Sinusoidal.InOut,
|
|
54
|
+
[CurveFunction.EXPONENTIAL_IN]: Easing.Exponential.In,
|
|
55
|
+
[CurveFunction.EXPONENTIAL_OUT]: Easing.Exponential.Out,
|
|
56
|
+
[CurveFunction.EXPONENTIAL_IN_OUT]: Easing.Exponential.InOut,
|
|
57
|
+
[CurveFunction.CIRCULAR_IN]: Easing.Circular.In,
|
|
58
|
+
[CurveFunction.CIRCULAR_OUT]: Easing.Circular.Out,
|
|
59
|
+
[CurveFunction.CIRCULAR_IN_OUT]: Easing.Circular.InOut,
|
|
60
|
+
[CurveFunction.ELASTIC_IN]: Easing.Elastic.In,
|
|
61
|
+
[CurveFunction.ELASTIC_OUT]: Easing.Elastic.Out,
|
|
62
|
+
[CurveFunction.ELASTIC_IN_OUT]: Easing.Elastic.InOut,
|
|
63
|
+
[CurveFunction.BACK_IN]: Easing.Back.In,
|
|
64
|
+
[CurveFunction.BACK_OUT]: Easing.Back.Out,
|
|
65
|
+
[CurveFunction.BACK_IN_OUT]: Easing.Back.InOut,
|
|
66
|
+
[CurveFunction.BOUNCE_IN]: Easing.Bounce.In,
|
|
67
|
+
[CurveFunction.BOUNCE_OUT]: Easing.Bounce.Out,
|
|
68
|
+
[CurveFunction.BOUNCE_IN_OUT]: Easing.Bounce.InOut,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const getCurveFunction = (curveFunction) =>
|
|
72
|
+
typeof curveFunction === "function"
|
|
73
|
+
? curveFunction
|
|
74
|
+
: CurveFunctionMap[curveFunction];
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import * as THREE from "three/build/three.module.js";
|
|
2
|
+
|
|
3
|
+
import { getCurveFunction } from "./three-particles-curves.js";
|
|
4
|
+
import { size } from "lodash";
|
|
5
|
+
|
|
6
|
+
const noiseInput = new THREE.Vector3(0, 0, 0);
|
|
7
|
+
|
|
8
|
+
const curveModifiers = [
|
|
9
|
+
// {key:"colorOverLifetime", attributeKeys:["colorR", "colorG", "colorB"]},
|
|
10
|
+
{
|
|
11
|
+
key: "opacityOverLifetime",
|
|
12
|
+
attributeKeys: ["colorA"],
|
|
13
|
+
startValueKeys: ["startOpacity"],
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
key: "sizeOverLifetime",
|
|
17
|
+
attributeKeys: ["size"],
|
|
18
|
+
startValueKeys: ["startSize"],
|
|
19
|
+
},
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export const applyModifiers = ({
|
|
23
|
+
delta,
|
|
24
|
+
noise,
|
|
25
|
+
startValues,
|
|
26
|
+
lifetimeValues,
|
|
27
|
+
normalizedConfig,
|
|
28
|
+
attributes,
|
|
29
|
+
particleLifetimePercentage,
|
|
30
|
+
particleIndex,
|
|
31
|
+
forceUpdate = false,
|
|
32
|
+
}) => {
|
|
33
|
+
curveModifiers.forEach(({ key, attributeKeys, startValueKeys }) => {
|
|
34
|
+
const curveModifier = normalizedConfig[key];
|
|
35
|
+
if (curveModifier.isActive) {
|
|
36
|
+
const multiplier = getCurveFunction(curveModifier.curveFunction)(
|
|
37
|
+
particleLifetimePercentage
|
|
38
|
+
);
|
|
39
|
+
attributeKeys.forEach((attributeKey, index) => {
|
|
40
|
+
attributes[attributeKey].array[particleIndex] =
|
|
41
|
+
startValues[startValueKeys[index]][particleIndex] * multiplier;
|
|
42
|
+
attributes[attributeKey].needsUpdate = true;
|
|
43
|
+
});
|
|
44
|
+
} else if (forceUpdate) {
|
|
45
|
+
attributeKeys.forEach((attributeKey, index) => {
|
|
46
|
+
attributes[attributeKey].array[particleIndex] =
|
|
47
|
+
startValues[startValueKeys[index]][particleIndex];
|
|
48
|
+
attributes[attributeKey].needsUpdate = true;
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (lifetimeValues.rotationOverLifetime) {
|
|
54
|
+
attributes.rotation.array[particleIndex] +=
|
|
55
|
+
lifetimeValues.rotationOverLifetime[particleIndex] * delta * 0.02;
|
|
56
|
+
attributes.rotation.needsUpdate = true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (noise.isActive) {
|
|
60
|
+
const {
|
|
61
|
+
sampler,
|
|
62
|
+
strength,
|
|
63
|
+
offsets,
|
|
64
|
+
positionAmount,
|
|
65
|
+
rotationAmount,
|
|
66
|
+
sizeAmount,
|
|
67
|
+
} = noise;
|
|
68
|
+
const positionIndex = particleIndex * 3;
|
|
69
|
+
const positionArr = attributes.position.array;
|
|
70
|
+
let noiseOnPosition;
|
|
71
|
+
|
|
72
|
+
const noisePosition =
|
|
73
|
+
(particleLifetimePercentage + (offsets ? offsets[particleIndex] : 0)) *
|
|
74
|
+
10 *
|
|
75
|
+
strength;
|
|
76
|
+
const noisePower = 0.15 * strength;
|
|
77
|
+
|
|
78
|
+
noiseInput.set(noisePosition, 0, 0);
|
|
79
|
+
noiseOnPosition = sampler.get3(noiseInput);
|
|
80
|
+
positionArr[positionIndex] += noiseOnPosition * noisePower * positionAmount;
|
|
81
|
+
|
|
82
|
+
if (rotationAmount !== 0) {
|
|
83
|
+
attributes.rotation.array[particleIndex] +=
|
|
84
|
+
noiseOnPosition * noisePower * rotationAmount;
|
|
85
|
+
attributes.rotation.needsUpdate = true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (sizeAmount !== 0) {
|
|
89
|
+
attributes.size.array[particleIndex] +=
|
|
90
|
+
noiseOnPosition * noisePower * sizeAmount;
|
|
91
|
+
attributes.size.needsUpdate = true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
noiseInput.set(noisePosition, noisePosition, 0);
|
|
95
|
+
noiseOnPosition = sampler.get3(noiseInput);
|
|
96
|
+
positionArr[positionIndex + 1] +=
|
|
97
|
+
noiseOnPosition * noisePower * positionAmount;
|
|
98
|
+
|
|
99
|
+
noiseInput.set(noisePosition, noisePosition, noisePosition);
|
|
100
|
+
noiseOnPosition = sampler.get3(noiseInput);
|
|
101
|
+
positionArr[positionIndex + 2] +=
|
|
102
|
+
noiseOnPosition * noisePower * positionAmount;
|
|
103
|
+
|
|
104
|
+
attributes.position.needsUpdate = true;
|
|
105
|
+
}
|
|
106
|
+
};
|
package/src/js/effects/{three-particles-utils.js → three-particles/three-particles-utils.js}
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as THREE from "three/build/three.module.js";
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const patchObject = (
|
|
4
4
|
objectA,
|
|
5
5
|
objectB,
|
|
6
6
|
config = { skippedProperties: [], applyToFirstObject: false }
|
|
@@ -9,9 +9,14 @@ export const deepMerge = (
|
|
|
9
9
|
Object.keys(objectA).forEach((key) => {
|
|
10
10
|
if (!config.skippedProperties || !config.skippedProperties.includes(key)) {
|
|
11
11
|
if (typeof objectA[key] === "object" && objectA[key] && objectB[key]) {
|
|
12
|
-
result[key] =
|
|
12
|
+
result[key] = patchObject(objectA[key], objectB[key], config);
|
|
13
13
|
} else {
|
|
14
|
-
result[key] =
|
|
14
|
+
result[key] =
|
|
15
|
+
objectB[key] === 0
|
|
16
|
+
? 0
|
|
17
|
+
: objectB[key] === false
|
|
18
|
+
? false
|
|
19
|
+
: objectB[key] || objectA[key];
|
|
15
20
|
if (config.applyToFirstObject) objectA[key] = result[key];
|
|
16
21
|
}
|
|
17
22
|
}
|
|
@@ -100,7 +105,7 @@ export const calculateRandomPositionAndVelocityOnCone = (
|
|
|
100
105
|
sinNormalizedAngle *
|
|
101
106
|
speedMultiplierByPosition *
|
|
102
107
|
randomizedSpeed,
|
|
103
|
-
|
|
108
|
+
Math.cos(normalizedAngle) * randomizedSpeed
|
|
104
109
|
);
|
|
105
110
|
};
|
|
106
111
|
|
|
@@ -157,5 +162,5 @@ export const calculateRandomPositionAndVelocityOnRectangle = (
|
|
|
157
162
|
startSpeed.min,
|
|
158
163
|
startSpeed.max
|
|
159
164
|
);
|
|
160
|
-
velocity.set(0, 0,
|
|
165
|
+
velocity.set(0, 0, randomizedSpeed);
|
|
161
166
|
};
|
|
@@ -5,14 +5,14 @@ import {
|
|
|
5
5
|
calculateRandomPositionAndVelocityOnCone,
|
|
6
6
|
calculateRandomPositionAndVelocityOnRectangle,
|
|
7
7
|
calculateRandomPositionAndVelocityOnSphere,
|
|
8
|
-
|
|
9
|
-
} from "./three-particles-utils.js";
|
|
8
|
+
patchObject,
|
|
9
|
+
} from "./three-particles/three-particles-utils.js";
|
|
10
10
|
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
import { CurveFunction } from "./three-particles/three-particles-curves.js";
|
|
12
|
+
import { FBM } from "three-noise/build/three-noise.module.js";
|
|
13
|
+
import ParticleSystemFragmentShader from "./three-particles/shaders/particle-system-fragment-shader.glsl.js";
|
|
14
|
+
import ParticleSystemVertexShader from "./three-particles/shaders/particle-system-vertex-shader.glsl.js";
|
|
15
|
+
import { applyModifiers } from "./three-particles/three-particles-modifiers.js";
|
|
16
16
|
|
|
17
17
|
let createdParticleSystems = [];
|
|
18
18
|
|
|
@@ -34,10 +34,23 @@ export const TimeMode = {
|
|
|
34
34
|
FPS: "FPS",
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
export const blendingMap = {
|
|
38
|
+
"THREE.NoBlending": THREE.NoBlending,
|
|
39
|
+
"THREE.NormalBlending": THREE.NormalBlending,
|
|
40
|
+
"THREE.AdditiveBlending": THREE.AdditiveBlending,
|
|
41
|
+
"THREE.SubtractiveBlending": THREE.SubtractiveBlending,
|
|
42
|
+
"THREE.MultiplyBlending": THREE.MultiplyBlending,
|
|
43
|
+
};
|
|
44
|
+
|
|
37
45
|
export const getDefaultParticleSystemConfig = () =>
|
|
38
46
|
JSON.parse(JSON.stringify(DEFAULT_PARTICLE_SYSTEM_CONFIG));
|
|
39
47
|
|
|
40
48
|
const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
|
|
49
|
+
transform: {
|
|
50
|
+
position: { x: 0, y: 0, z: 0 },
|
|
51
|
+
rotation: { x: 0, y: 0, z: 0 },
|
|
52
|
+
scale: { x: 1, y: 1, z: 1 },
|
|
53
|
+
},
|
|
41
54
|
duration: 5.0,
|
|
42
55
|
looping: true,
|
|
43
56
|
startDelay: { min: 0.0, max: 0.0 },
|
|
@@ -81,6 +94,52 @@ const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
|
|
|
81
94
|
},
|
|
82
95
|
},
|
|
83
96
|
map: null,
|
|
97
|
+
renderer: {
|
|
98
|
+
blending: THREE.NormalBlending,
|
|
99
|
+
transparent: true,
|
|
100
|
+
depthTest: true,
|
|
101
|
+
depthWrite: false,
|
|
102
|
+
},
|
|
103
|
+
velocityOverLifetime: {
|
|
104
|
+
isActive: false,
|
|
105
|
+
linear: {
|
|
106
|
+
x: { min: 0, max: 0 },
|
|
107
|
+
y: { min: 0, max: 0 },
|
|
108
|
+
z: { min: 0, max: 0 },
|
|
109
|
+
},
|
|
110
|
+
orbital: {
|
|
111
|
+
x: { min: 0, max: 0 },
|
|
112
|
+
y: { min: 0, max: 0 },
|
|
113
|
+
z: { min: 0, max: 0 },
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
sizeOverLifetime: {
|
|
117
|
+
isActive: false,
|
|
118
|
+
curveFunction: CurveFunction.LINEAR,
|
|
119
|
+
},
|
|
120
|
+
/* colorOverLifetime: {
|
|
121
|
+
isActive: false,
|
|
122
|
+
curveFunction: CurveFunction.LINEAR,
|
|
123
|
+
}, */
|
|
124
|
+
opacityOverLifetime: {
|
|
125
|
+
isActive: false,
|
|
126
|
+
curveFunction: CurveFunction.LINEAR,
|
|
127
|
+
},
|
|
128
|
+
rotationOverLifetime: {
|
|
129
|
+
isActive: false,
|
|
130
|
+
min: 0.0,
|
|
131
|
+
max: 0.0,
|
|
132
|
+
},
|
|
133
|
+
noise: {
|
|
134
|
+
isActive: false,
|
|
135
|
+
useRandomOffset: false,
|
|
136
|
+
strength: 1.0,
|
|
137
|
+
frequency: 0.5,
|
|
138
|
+
octaves: 1,
|
|
139
|
+
positionAmount: 1.0,
|
|
140
|
+
rotationAmount: 0.0,
|
|
141
|
+
sizeAmount: 0.0,
|
|
142
|
+
},
|
|
84
143
|
textureSheetAnimation: {
|
|
85
144
|
tiles: new THREE.Vector2(1.0, 1.0),
|
|
86
145
|
timeMode: TimeMode.LIFETIME,
|
|
@@ -113,7 +172,8 @@ const calculatePositionAndVelocity = (
|
|
|
113
172
|
{ shape, sphere, cone, circle, rectangle },
|
|
114
173
|
startSpeed,
|
|
115
174
|
position,
|
|
116
|
-
velocity
|
|
175
|
+
velocity,
|
|
176
|
+
velocityOverLifetime
|
|
117
177
|
) => {
|
|
118
178
|
switch (shape) {
|
|
119
179
|
case Shape.SPHERE:
|
|
@@ -152,17 +212,45 @@ const calculatePositionAndVelocity = (
|
|
|
152
212
|
);
|
|
153
213
|
break;
|
|
154
214
|
}
|
|
215
|
+
|
|
216
|
+
if (velocityOverLifetime.isActive) {
|
|
217
|
+
velocity.x += THREE.MathUtils.randFloat(
|
|
218
|
+
velocityOverLifetime.linear.x.min,
|
|
219
|
+
velocityOverLifetime.linear.x.max
|
|
220
|
+
);
|
|
221
|
+
velocity.y += THREE.MathUtils.randFloat(
|
|
222
|
+
velocityOverLifetime.linear.y.min,
|
|
223
|
+
velocityOverLifetime.linear.y.max
|
|
224
|
+
);
|
|
225
|
+
velocity.z += THREE.MathUtils.randFloat(
|
|
226
|
+
velocityOverLifetime.linear.z.min,
|
|
227
|
+
velocityOverLifetime.linear.z.max
|
|
228
|
+
);
|
|
229
|
+
}
|
|
155
230
|
};
|
|
156
231
|
|
|
157
232
|
export const createParticleSystem = (
|
|
158
233
|
config = DEFAULT_PARTICLE_SYSTEM_CONFIG
|
|
159
234
|
) => {
|
|
160
235
|
const now = Date.now();
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
236
|
+
const generalData = {
|
|
237
|
+
distanceFromLastEmitByDistance: 0,
|
|
238
|
+
lastWorldPosition: new THREE.Vector3(-99999),
|
|
239
|
+
currentWorldPosition: new THREE.Vector3(-99999),
|
|
240
|
+
worldPositionChange: new THREE.Vector3(),
|
|
241
|
+
worldQuaternion: new THREE.Quaternion(),
|
|
242
|
+
lastWorldQuaternion: new THREE.Quaternion(-99999),
|
|
243
|
+
worldEuler: new THREE.Euler(),
|
|
244
|
+
gravityVelocity: new THREE.Vector3(0, 0, 0),
|
|
245
|
+
startValues: {},
|
|
246
|
+
lifetimeValues: {},
|
|
247
|
+
creationTimes: [],
|
|
248
|
+
noise: null,
|
|
249
|
+
};
|
|
164
250
|
|
|
251
|
+
const normalizedConfig = patchObject(DEFAULT_PARTICLE_SYSTEM_CONFIG, config);
|
|
165
252
|
const {
|
|
253
|
+
transform,
|
|
166
254
|
duration,
|
|
167
255
|
looping,
|
|
168
256
|
startDelay,
|
|
@@ -178,10 +266,16 @@ export const createParticleSystem = (
|
|
|
178
266
|
emission,
|
|
179
267
|
shape,
|
|
180
268
|
map,
|
|
269
|
+
renderer,
|
|
270
|
+
noise,
|
|
271
|
+
velocityOverLifetime,
|
|
181
272
|
onUpdate,
|
|
182
273
|
onComplete,
|
|
183
274
|
textureSheetAnimation,
|
|
184
|
-
} =
|
|
275
|
+
} = normalizedConfig;
|
|
276
|
+
|
|
277
|
+
if (typeof renderer.blending === "string")
|
|
278
|
+
renderer.blending = blendingMap[renderer.blending];
|
|
185
279
|
|
|
186
280
|
const startPositions = Array.from(
|
|
187
281
|
{ length: maxParticles },
|
|
@@ -192,6 +286,49 @@ export const createParticleSystem = (
|
|
|
192
286
|
() => new THREE.Vector3()
|
|
193
287
|
);
|
|
194
288
|
|
|
289
|
+
generalData.creationTimes = Array.from({ length: maxParticles }, () => 0);
|
|
290
|
+
|
|
291
|
+
const startValueKeys = ["startSize", "startOpacity"];
|
|
292
|
+
startValueKeys.forEach((key) => {
|
|
293
|
+
generalData.startValues[key] = Array.from({ length: maxParticles }, () =>
|
|
294
|
+
THREE.MathUtils.randFloat(
|
|
295
|
+
normalizedConfig[key].min,
|
|
296
|
+
normalizedConfig[key].max
|
|
297
|
+
)
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
const lifetimeValueKeys = ["rotationOverLifetime"];
|
|
302
|
+
lifetimeValueKeys.forEach((key) => {
|
|
303
|
+
if (normalizedConfig[key].isActive)
|
|
304
|
+
generalData.lifetimeValues[key] = Array.from(
|
|
305
|
+
{ length: maxParticles },
|
|
306
|
+
() =>
|
|
307
|
+
THREE.MathUtils.randFloat(
|
|
308
|
+
normalizedConfig[key].min,
|
|
309
|
+
normalizedConfig[key].max
|
|
310
|
+
)
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
generalData.noise = {
|
|
315
|
+
isActive: noise.isActive,
|
|
316
|
+
strength: noise.strength,
|
|
317
|
+
positionAmount: noise.positionAmount,
|
|
318
|
+
rotationAmount: noise.rotationAmount,
|
|
319
|
+
sizeAmount: noise.sizeAmount,
|
|
320
|
+
sampler: noise.isActive
|
|
321
|
+
? new FBM({
|
|
322
|
+
seed: Math.random(),
|
|
323
|
+
scale: noise.frequency,
|
|
324
|
+
octaves: noise.octaves,
|
|
325
|
+
})
|
|
326
|
+
: null,
|
|
327
|
+
offsets: noise.useRandomOffset
|
|
328
|
+
? Array.from({ length: maxParticles }, () => Math.random() * 100)
|
|
329
|
+
: null,
|
|
330
|
+
};
|
|
331
|
+
|
|
195
332
|
const material = new THREE.ShaderMaterial({
|
|
196
333
|
uniforms: {
|
|
197
334
|
elapsed: {
|
|
@@ -212,10 +349,10 @@ export const createParticleSystem = (
|
|
|
212
349
|
},
|
|
213
350
|
vertexShader: ParticleSystemVertexShader,
|
|
214
351
|
fragmentShader: ParticleSystemFragmentShader,
|
|
215
|
-
transparent:
|
|
216
|
-
blending:
|
|
217
|
-
depthTest:
|
|
218
|
-
depthWrite:
|
|
352
|
+
transparent: renderer.transparent,
|
|
353
|
+
blending: renderer.blending,
|
|
354
|
+
depthTest: renderer.depthTest,
|
|
355
|
+
depthWrite: renderer.depthWrite,
|
|
219
356
|
});
|
|
220
357
|
|
|
221
358
|
const geometry = new THREE.BufferGeometry();
|
|
@@ -225,7 +362,8 @@ export const createParticleSystem = (
|
|
|
225
362
|
shape,
|
|
226
363
|
startSpeed,
|
|
227
364
|
startPositions[i],
|
|
228
|
-
velocities[i]
|
|
365
|
+
velocities[i],
|
|
366
|
+
velocityOverLifetime
|
|
229
367
|
);
|
|
230
368
|
|
|
231
369
|
geometry.setFromPoints(
|
|
@@ -244,7 +382,6 @@ export const createParticleSystem = (
|
|
|
244
382
|
};
|
|
245
383
|
|
|
246
384
|
createFloat32AttributesRequest("isActive", false);
|
|
247
|
-
createFloat32AttributesRequest("creationTime", 0);
|
|
248
385
|
createFloat32AttributesRequest("lifetime", 0);
|
|
249
386
|
createFloat32AttributesRequest("startLifetime", () =>
|
|
250
387
|
THREE.MathUtils.randFloat(startLifetime.min, startLifetime.max)
|
|
@@ -258,22 +395,16 @@ export const createParticleSystem = (
|
|
|
258
395
|
|
|
259
396
|
createFloat32AttributesRequest("opacity", 0);
|
|
260
397
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
THREE.Math.degToRad(
|
|
267
|
-
THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
|
|
268
|
-
),
|
|
269
|
-
});
|
|
398
|
+
createFloat32AttributesRequest("rotation", () =>
|
|
399
|
+
THREE.Math.degToRad(
|
|
400
|
+
THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
|
|
401
|
+
)
|
|
402
|
+
);
|
|
270
403
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
factory: () => THREE.MathUtils.randFloat(startSize.min, startSize.max),
|
|
276
|
-
});
|
|
404
|
+
createFloat32AttributesRequest(
|
|
405
|
+
"size",
|
|
406
|
+
(_, index) => generalData.startValues.startSize[index]
|
|
407
|
+
);
|
|
277
408
|
|
|
278
409
|
createFloat32AttributesRequest("rotation", 0);
|
|
279
410
|
|
|
@@ -300,16 +431,16 @@ export const createParticleSystem = (
|
|
|
300
431
|
|
|
301
432
|
const deactivateParticle = (particleIndex) => {
|
|
302
433
|
geometry.attributes.isActive.array[particleIndex] = false;
|
|
303
|
-
geometry.attributes.lifetime.array[particleIndex] = 0;
|
|
304
|
-
geometry.attributes.lifetime.needsUpdate = true;
|
|
305
434
|
geometry.attributes.colorA.array[particleIndex] = 0;
|
|
306
435
|
geometry.attributes.colorA.needsUpdate = true;
|
|
307
436
|
};
|
|
308
437
|
|
|
309
438
|
const activateParticle = ({ particleIndex, activationTime }) => {
|
|
310
439
|
geometry.attributes.isActive.array[particleIndex] = true;
|
|
311
|
-
|
|
312
|
-
|
|
440
|
+
generalData.creationTimes[particleIndex] = activationTime;
|
|
441
|
+
|
|
442
|
+
if (generalData.noise.offsets)
|
|
443
|
+
generalData.noise.offsets[particleIndex] = Math.random() * 100;
|
|
313
444
|
|
|
314
445
|
const colorRandomRatio = Math.random();
|
|
315
446
|
|
|
@@ -328,12 +459,6 @@ export const createParticleSystem = (
|
|
|
328
459
|
colorRandomRatio * (startColor.max.b - startColor.min.b);
|
|
329
460
|
geometry.attributes.colorB.needsUpdate = true;
|
|
330
461
|
|
|
331
|
-
geometry.attributes.colorA.array[particleIndex] = THREE.MathUtils.randFloat(
|
|
332
|
-
startOpacity.min,
|
|
333
|
-
startOpacity.max
|
|
334
|
-
);
|
|
335
|
-
geometry.attributes.colorA.needsUpdate = true;
|
|
336
|
-
|
|
337
462
|
geometry.attributes.startFrame.array[particleIndex] =
|
|
338
463
|
THREE.MathUtils.randInt(
|
|
339
464
|
textureSheetAnimation.startFrame.min,
|
|
@@ -345,20 +470,31 @@ export const createParticleSystem = (
|
|
|
345
470
|
THREE.MathUtils.randFloat(startLifetime.min, startLifetime.max) * 1000;
|
|
346
471
|
geometry.attributes.startLifetime.needsUpdate = true;
|
|
347
472
|
|
|
348
|
-
|
|
473
|
+
generalData.startValues.startSize[particleIndex] =
|
|
349
474
|
THREE.MathUtils.randFloat(startSize.min, startSize.max);
|
|
350
|
-
|
|
475
|
+
generalData.startValues.startOpacity[particleIndex] =
|
|
476
|
+
THREE.MathUtils.randFloat(startOpacity.min, startOpacity.max);
|
|
351
477
|
|
|
352
478
|
geometry.attributes.rotation.array[particleIndex] = THREE.Math.degToRad(
|
|
353
479
|
THREE.MathUtils.randFloat(startRotation.min, startRotation.max)
|
|
354
480
|
);
|
|
481
|
+
|
|
482
|
+
if (normalizedConfig.rotationOverLifetime.isActive)
|
|
483
|
+
generalData.lifetimeValues.rotationOverLifetime[particleIndex] =
|
|
484
|
+
THREE.MathUtils.randFloat(
|
|
485
|
+
normalizedConfig.rotationOverLifetime.min,
|
|
486
|
+
normalizedConfig.rotationOverLifetime.max
|
|
487
|
+
);
|
|
488
|
+
|
|
355
489
|
geometry.attributes.rotation.needsUpdate = true;
|
|
490
|
+
geometry.attributes.colorB.needsUpdate = true;
|
|
356
491
|
|
|
357
492
|
calculatePositionAndVelocity(
|
|
358
493
|
shape,
|
|
359
494
|
startSpeed,
|
|
360
495
|
startPositions[particleIndex],
|
|
361
|
-
velocities[particleIndex]
|
|
496
|
+
velocities[particleIndex],
|
|
497
|
+
velocityOverLifetime
|
|
362
498
|
);
|
|
363
499
|
const positionIndex = Math.floor(particleIndex * 3);
|
|
364
500
|
geometry.attributes.position.array[positionIndex] =
|
|
@@ -367,23 +503,39 @@ export const createParticleSystem = (
|
|
|
367
503
|
startPositions[particleIndex].y;
|
|
368
504
|
geometry.attributes.position.array[positionIndex + 2] =
|
|
369
505
|
startPositions[particleIndex].z;
|
|
370
|
-
particleSystem.geometry.attributes.position.needsUpdate = true;
|
|
371
506
|
|
|
372
507
|
geometry.attributes.lifetime.array[particleIndex] = 0;
|
|
373
508
|
geometry.attributes.lifetime.needsUpdate = true;
|
|
509
|
+
|
|
510
|
+
applyModifiers({
|
|
511
|
+
delta: 0,
|
|
512
|
+
elapsed: 0,
|
|
513
|
+
noise: generalData.noise,
|
|
514
|
+
startValues: generalData.startValues,
|
|
515
|
+
lifetimeValues: generalData.lifetimeValues,
|
|
516
|
+
normalizedConfig,
|
|
517
|
+
attributes: particleSystem.geometry.attributes,
|
|
518
|
+
particleLifetimePercentage: 0,
|
|
519
|
+
particleIndex,
|
|
520
|
+
forceUpdate: true,
|
|
521
|
+
});
|
|
374
522
|
};
|
|
375
523
|
|
|
376
524
|
const particleSystem = new THREE.Points(geometry, material);
|
|
377
525
|
particleSystem.sortParticles = true;
|
|
378
526
|
|
|
527
|
+
particleSystem.position.copy(transform.position);
|
|
528
|
+
particleSystem.rotation.x = THREE.Math.degToRad(transform.rotation.x);
|
|
529
|
+
particleSystem.rotation.y = THREE.Math.degToRad(transform.rotation.y);
|
|
530
|
+
particleSystem.rotation.z = THREE.Math.degToRad(transform.rotation.z);
|
|
531
|
+
particleSystem.scale.copy(transform.scale);
|
|
532
|
+
|
|
379
533
|
const calculatedCreationTime =
|
|
380
534
|
now + THREE.MathUtils.randFloat(startDelay.min, startDelay.max) * 1000;
|
|
381
535
|
|
|
382
536
|
createdParticleSystems.push({
|
|
383
537
|
particleSystem,
|
|
384
538
|
generalData,
|
|
385
|
-
lastWorldPosition,
|
|
386
|
-
worldPositionChange,
|
|
387
539
|
onUpdate,
|
|
388
540
|
onComplete,
|
|
389
541
|
creationTime: calculatedCreationTime,
|
|
@@ -393,6 +545,7 @@ export const createParticleSystem = (
|
|
|
393
545
|
simulationSpace,
|
|
394
546
|
gravity,
|
|
395
547
|
emission,
|
|
548
|
+
normalizedConfig,
|
|
396
549
|
iterationCount: 0,
|
|
397
550
|
velocities,
|
|
398
551
|
deactivateParticle,
|
|
@@ -417,8 +570,6 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
|
|
|
417
570
|
const {
|
|
418
571
|
onUpdate,
|
|
419
572
|
generalData,
|
|
420
|
-
lastWorldPosition,
|
|
421
|
-
worldPositionChange,
|
|
422
573
|
onComplete,
|
|
423
574
|
particleSystem,
|
|
424
575
|
creationTime,
|
|
@@ -426,6 +577,7 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
|
|
|
426
577
|
duration,
|
|
427
578
|
looping,
|
|
428
579
|
emission,
|
|
580
|
+
normalizedConfig,
|
|
429
581
|
iterationCount,
|
|
430
582
|
velocities,
|
|
431
583
|
deactivateParticle,
|
|
@@ -433,69 +585,106 @@ export const updateParticleSystems = ({ now, delta, elapsed }) => {
|
|
|
433
585
|
simulationSpace,
|
|
434
586
|
gravity,
|
|
435
587
|
} = props;
|
|
588
|
+
|
|
589
|
+
const {
|
|
590
|
+
lastWorldPosition,
|
|
591
|
+
currentWorldPosition,
|
|
592
|
+
worldPositionChange,
|
|
593
|
+
lastWorldQuaternion,
|
|
594
|
+
worldQuaternion,
|
|
595
|
+
worldEuler,
|
|
596
|
+
gravityVelocity,
|
|
597
|
+
} = generalData;
|
|
598
|
+
|
|
436
599
|
const lifetime = now - creationTime;
|
|
437
600
|
particleSystem.material.uniforms.elapsed.value = elapsed;
|
|
438
601
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
lastWorldPosition.y !== -99999 &&
|
|
442
|
-
lastWorldPosition.z !== -99999
|
|
443
|
-
)
|
|
602
|
+
particleSystem.getWorldPosition(currentWorldPosition);
|
|
603
|
+
if (lastWorldPosition.x !== -99999)
|
|
444
604
|
worldPositionChange.set(
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
605
|
+
currentWorldPosition.x - lastWorldPosition.x,
|
|
606
|
+
currentWorldPosition.y - lastWorldPosition.y,
|
|
607
|
+
currentWorldPosition.z - lastWorldPosition.z
|
|
448
608
|
);
|
|
449
609
|
generalData.distanceFromLastEmitByDistance += worldPositionChange.length();
|
|
450
|
-
|
|
610
|
+
particleSystem.getWorldPosition(lastWorldPosition);
|
|
611
|
+
|
|
612
|
+
particleSystem.getWorldQuaternion(worldQuaternion);
|
|
613
|
+
if (
|
|
614
|
+
lastWorldQuaternion.x === -99999 ||
|
|
615
|
+
lastWorldQuaternion.x != worldQuaternion.x ||
|
|
616
|
+
lastWorldQuaternion.y != worldQuaternion.y ||
|
|
617
|
+
lastWorldQuaternion.z != worldQuaternion.z
|
|
618
|
+
) {
|
|
619
|
+
worldEuler.setFromQuaternion(worldQuaternion);
|
|
620
|
+
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();
|
|
628
|
+
particleSystem.worldToLocal(gravityVelocity);
|
|
629
|
+
particleSystem.position.set(tempPosX, tempPosY, tempPosZ);
|
|
630
|
+
particleSystem.updateMatrixWorld();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
generalData.creationTimes.forEach((entry, index) => {
|
|
634
|
+
if (particleSystem.geometry.attributes.isActive.array[index]) {
|
|
635
|
+
const particleLifetime = now - entry;
|
|
636
|
+
if (
|
|
637
|
+
particleLifetime >
|
|
638
|
+
particleSystem.geometry.attributes.startLifetime.array[index]
|
|
639
|
+
)
|
|
640
|
+
deactivateParticle(index);
|
|
641
|
+
else {
|
|
642
|
+
const velocity = velocities[index];
|
|
643
|
+
velocity.x -= gravityVelocity.x;
|
|
644
|
+
velocity.y -= gravityVelocity.y;
|
|
645
|
+
velocity.z -= gravityVelocity.z;
|
|
451
646
|
|
|
452
|
-
particleSystem.geometry.attributes.creationTime.array.forEach(
|
|
453
|
-
(entry, index) => {
|
|
454
|
-
if (particleSystem.geometry.attributes.isActive.array[index]) {
|
|
455
|
-
const particleLifetime = now - float32Helper - entry;
|
|
456
647
|
if (
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if (
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
velocity.z !== 0
|
|
470
|
-
) {
|
|
471
|
-
const positionIndex = index * 3;
|
|
472
|
-
const positionArr =
|
|
473
|
-
particleSystem.geometry.attributes.position.array;
|
|
474
|
-
if (simulationSpace === SimulationSpace.WORLD) {
|
|
475
|
-
positionArr[positionIndex] -= worldPositionChange.x;
|
|
476
|
-
positionArr[positionIndex + 1] -= worldPositionChange.y;
|
|
477
|
-
positionArr[positionIndex + 2] -= worldPositionChange.z;
|
|
478
|
-
}
|
|
479
|
-
positionArr[positionIndex] += velocity.x * delta;
|
|
480
|
-
positionArr[positionIndex + 1] += velocity.y * delta;
|
|
481
|
-
positionArr[positionIndex + 2] += velocity.z * delta;
|
|
482
|
-
particleSystem.geometry.attributes.position.needsUpdate = true;
|
|
648
|
+
gravity !== 0 ||
|
|
649
|
+
velocity.x !== 0 ||
|
|
650
|
+
velocity.y !== 0 ||
|
|
651
|
+
velocity.z !== 0
|
|
652
|
+
) {
|
|
653
|
+
const positionIndex = index * 3;
|
|
654
|
+
const positionArr =
|
|
655
|
+
particleSystem.geometry.attributes.position.array;
|
|
656
|
+
if (simulationSpace === SimulationSpace.WORLD) {
|
|
657
|
+
positionArr[positionIndex] -= worldPositionChange.x;
|
|
658
|
+
positionArr[positionIndex + 1] -= worldPositionChange.y;
|
|
659
|
+
positionArr[positionIndex + 2] -= worldPositionChange.z;
|
|
483
660
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
particleSystem.geometry.attributes.
|
|
488
|
-
|
|
489
|
-
// TEMP
|
|
490
|
-
particleSystem.geometry.attributes.colorA.array[index] =
|
|
491
|
-
1 -
|
|
492
|
-
particleLifetime /
|
|
493
|
-
particleSystem.geometry.attributes.startLifetime.array[index];
|
|
494
|
-
particleSystem.geometry.attributes.colorA.needsUpdate = true;
|
|
661
|
+
positionArr[positionIndex] += velocity.x * delta;
|
|
662
|
+
positionArr[positionIndex + 1] += velocity.y * delta;
|
|
663
|
+
positionArr[positionIndex + 2] += velocity.z * delta;
|
|
664
|
+
particleSystem.geometry.attributes.position.needsUpdate = true;
|
|
495
665
|
}
|
|
666
|
+
|
|
667
|
+
particleSystem.geometry.attributes.lifetime.array[index] =
|
|
668
|
+
particleLifetime;
|
|
669
|
+
particleSystem.geometry.attributes.lifetime.needsUpdate = true;
|
|
670
|
+
|
|
671
|
+
const particleLifetimePercentage =
|
|
672
|
+
particleLifetime /
|
|
673
|
+
particleSystem.geometry.attributes.startLifetime.array[index];
|
|
674
|
+
applyModifiers({
|
|
675
|
+
delta,
|
|
676
|
+
elapsed,
|
|
677
|
+
noise: generalData.noise,
|
|
678
|
+
startValues: generalData.startValues,
|
|
679
|
+
lifetimeValues: generalData.lifetimeValues,
|
|
680
|
+
normalizedConfig,
|
|
681
|
+
attributes: particleSystem.geometry.attributes,
|
|
682
|
+
particleLifetimePercentage,
|
|
683
|
+
particleIndex: index,
|
|
684
|
+
});
|
|
496
685
|
}
|
|
497
686
|
}
|
|
498
|
-
);
|
|
687
|
+
});
|
|
499
688
|
|
|
500
689
|
if (looping || lifetime < duration * 1000) {
|
|
501
690
|
const emissionDelta = now - lastEmissionTime;
|