@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 +14 -1
- package/package.json +4 -1
- package/src/js/effects/shaders/particle-system-fragment-shader.glsl.js +13 -3
- package/src/js/effects/shaders/particle-system-vertex-shader.glsl.js +11 -5
- package/src/js/effects/three-particles-utils.js +161 -0
- package/src/js/effects/three-particles.js +241 -225
package/README.md
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
# THREE Particles
|
|
2
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
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.
|
|
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
|
|
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
|
|
14
|
+
varying float vLifetime;
|
|
15
|
+
varying float vStartLifetime;
|
|
13
16
|
varying float vRotation;
|
|
17
|
+
varying float vStartFrame;
|
|
14
18
|
|
|
15
19
|
void main()
|
|
16
20
|
{
|
|
17
21
|
vColor = vec4(colorR, colorG, colorB, colorA);
|
|
18
|
-
|
|
22
|
+
vLifetime = lifetime;
|
|
23
|
+
vStartLifetime = startLifetime;
|
|
19
24
|
vRotation = rotation;
|
|
25
|
+
vStartFrame = startFrame;
|
|
20
26
|
|
|
21
27
|
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
|
|
22
28
|
gl_PointSize = startSize * (100.0 / length(mvPosition.xyz));
|
|
23
29
|
gl_Position = projectionMatrix * mvPosition;
|
|
24
30
|
}
|
|
25
|
-
|
|
31
|
+
`;
|
|
26
32
|
|
|
27
|
-
export default ParticleSystemVertexShader
|
|
33
|
+
export default ParticleSystemVertexShader;
|
|
@@ -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
|
|
25
|
-
|
|
26
|
-
|
|
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
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 }, () => ({
|
|
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("
|
|
191
|
-
createFloat32AttributesRequest("
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
270
|
-
geometry.attributes.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
302
|
-
|
|
332
|
+
startOpacity.min,
|
|
333
|
+
startOpacity.max
|
|
303
334
|
);
|
|
304
335
|
geometry.attributes.colorA.needsUpdate = true;
|
|
305
336
|
|
|
306
|
-
geometry.attributes.
|
|
307
|
-
THREE.MathUtils.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
)
|
|
311
|
-
geometry.attributes.
|
|
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
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
|
|
338
|
-
|
|
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
|
|
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
|
|
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
|
|
455
|
+
const particleLifetime = now - float32Helper - entry;
|
|
437
456
|
if (
|
|
438
|
-
|
|
439
|
-
particleSystem.geometry.attributes.
|
|
457
|
+
particleLifetime >
|
|
458
|
+
particleSystem.geometry.attributes.startLifetime.array[index]
|
|
440
459
|
)
|
|
441
460
|
deactivateParticle(index);
|
|
442
461
|
else {
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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[
|
|
460
|
-
positionArr[
|
|
461
|
-
positionArr[
|
|
475
|
+
positionArr[positionIndex] -= worldPositionChange.x;
|
|
476
|
+
positionArr[positionIndex + 1] -= worldPositionChange.y;
|
|
477
|
+
positionArr[positionIndex + 2] -= worldPositionChange.z;
|
|
462
478
|
}
|
|
463
|
-
positionArr[
|
|
464
|
-
positionArr[
|
|
465
|
-
positionArr[
|
|
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.
|
|
470
|
-
|
|
471
|
-
particleSystem.geometry.attributes.
|
|
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
|
-
|
|
477
|
-
particleSystem.geometry.attributes.
|
|
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 ||
|
|
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
|
-
|
|
548
|
+
lifetime,
|
|
533
549
|
iterationCount: iterationCount + 1,
|
|
534
550
|
});
|
|
535
551
|
} else if (onComplete)
|