@newkrok/three-particles 2.6.3 → 2.6.4
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/dist/index.d.ts +1782 -7
- package/dist/index.js +1578 -6
- package/dist/index.js.map +1 -0
- package/dist/three-particles.min.js +1 -2
- package/dist/three-particles.min.js.map +1 -0
- package/package.json +5 -7
- package/dist/bundle-report.json +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/js/effects/three-particles/index.d.ts +0 -7
- package/dist/js/effects/three-particles/index.d.ts.map +0 -1
- package/dist/js/effects/three-particles/index.js +0 -6
- package/dist/js/effects/three-particles/shaders/particle-system-fragment-shader.glsl.d.ts +0 -3
- package/dist/js/effects/three-particles/shaders/particle-system-fragment-shader.glsl.d.ts.map +0 -1
- package/dist/js/effects/three-particles/shaders/particle-system-fragment-shader.glsl.js +0 -71
- package/dist/js/effects/three-particles/shaders/particle-system-vertex-shader.glsl.d.ts +0 -3
- package/dist/js/effects/three-particles/shaders/particle-system-vertex-shader.glsl.d.ts.map +0 -1
- package/dist/js/effects/three-particles/shaders/particle-system-vertex-shader.glsl.js +0 -37
- package/dist/js/effects/three-particles/three-particles-bezier.d.ts +0 -5
- package/dist/js/effects/three-particles/three-particles-bezier.d.ts.map +0 -1
- package/dist/js/effects/three-particles/three-particles-bezier.js +0 -62
- package/dist/js/effects/three-particles/three-particles-curves.d.ts +0 -108
- package/dist/js/effects/three-particles/three-particles-curves.d.ts.map +0 -1
- package/dist/js/effects/three-particles/three-particles-curves.js +0 -62
- package/dist/js/effects/three-particles/three-particles-enums.d.ts +0 -115
- package/dist/js/effects/three-particles/three-particles-enums.d.ts.map +0 -1
- package/dist/js/effects/three-particles/three-particles-enums.js +0 -1
- package/dist/js/effects/three-particles/three-particles-modifiers.d.ts +0 -73
- package/dist/js/effects/three-particles/three-particles-modifiers.d.ts.map +0 -1
- package/dist/js/effects/three-particles/three-particles-modifiers.js +0 -168
- package/dist/js/effects/three-particles/three-particles-utils.d.ts +0 -159
- package/dist/js/effects/three-particles/three-particles-utils.d.ts.map +0 -1
- package/dist/js/effects/three-particles/three-particles-utils.js +0 -302
- package/dist/js/effects/three-particles/three-particles.d.ts +0 -107
- package/dist/js/effects/three-particles/three-particles.d.ts.map +0 -1
- package/dist/js/effects/three-particles/three-particles.js +0 -972
- package/dist/js/effects/three-particles/types.d.ts +0 -1223
- package/dist/js/effects/three-particles/types.d.ts.map +0 -1
- package/dist/js/effects/three-particles/types.js +0 -1
- package/dist/three-particles.min.js.LICENSE.txt +0 -6
|
@@ -1,972 +0,0 @@
|
|
|
1
|
-
import { ObjectUtils } from '@newkrok/three-utils';
|
|
2
|
-
import * as THREE from 'three';
|
|
3
|
-
import { Gyroscope } from 'three/examples/jsm/misc/Gyroscope.js';
|
|
4
|
-
import { FBM } from 'three-noise/build/three-noise.module.js';
|
|
5
|
-
import ParticleSystemFragmentShader from './shaders/particle-system-fragment-shader.glsl.js';
|
|
6
|
-
import ParticleSystemVertexShader from './shaders/particle-system-vertex-shader.glsl.js';
|
|
7
|
-
import { removeBezierCurveFunction } from './three-particles-bezier.js';
|
|
8
|
-
import { applyModifiers } from './three-particles-modifiers.js';
|
|
9
|
-
import { calculateRandomPositionAndVelocityOnBox, calculateRandomPositionAndVelocityOnCircle, calculateRandomPositionAndVelocityOnCone, calculateRandomPositionAndVelocityOnRectangle, calculateRandomPositionAndVelocityOnSphere, calculateValue, getCurveFunctionFromConfig, isLifeTimeCurve, createDefaultParticleTexture, } from './three-particles-utils.js';
|
|
10
|
-
export * from './types.js';
|
|
11
|
-
let _particleSystemId = 0;
|
|
12
|
-
let createdParticleSystems = [];
|
|
13
|
-
// Pre-allocated objects for updateParticleSystemInstance to avoid GC pressure
|
|
14
|
-
const _lastWorldPositionSnapshot = new THREE.Vector3();
|
|
15
|
-
const _distanceStep = { x: 0, y: 0, z: 0 };
|
|
16
|
-
const _tempPosition = { x: 0, y: 0, z: 0 };
|
|
17
|
-
const _modifierParams = {
|
|
18
|
-
delta: 0,
|
|
19
|
-
generalData: null,
|
|
20
|
-
normalizedConfig: null,
|
|
21
|
-
attributes: null,
|
|
22
|
-
particleLifetimePercentage: 0,
|
|
23
|
-
particleIndex: 0,
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Mapping of blending mode string identifiers to Three.js blending constants.
|
|
27
|
-
*
|
|
28
|
-
* Used for converting serialized particle system configurations (e.g., from JSON)
|
|
29
|
-
* to actual Three.js blending mode constants.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```typescript
|
|
33
|
-
* import { blendingMap } from '@newkrok/three-particles';
|
|
34
|
-
*
|
|
35
|
-
* // Convert string to Three.js constant
|
|
36
|
-
* const blending = blendingMap['THREE.AdditiveBlending'];
|
|
37
|
-
* // blending === THREE.AdditiveBlending
|
|
38
|
-
* ```
|
|
39
|
-
*/
|
|
40
|
-
export const blendingMap = {
|
|
41
|
-
'THREE.NoBlending': THREE.NoBlending,
|
|
42
|
-
'THREE.NormalBlending': THREE.NormalBlending,
|
|
43
|
-
'THREE.AdditiveBlending': THREE.AdditiveBlending,
|
|
44
|
-
'THREE.SubtractiveBlending': THREE.SubtractiveBlending,
|
|
45
|
-
'THREE.MultiplyBlending': THREE.MultiplyBlending,
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* Returns a deep copy of the default particle system configuration.
|
|
49
|
-
*
|
|
50
|
-
* This is useful when you want to start with default settings and modify specific properties
|
|
51
|
-
* without affecting the internal default configuration object.
|
|
52
|
-
*
|
|
53
|
-
* @returns A new object containing all default particle system settings
|
|
54
|
-
*
|
|
55
|
-
* @example
|
|
56
|
-
* ```typescript
|
|
57
|
-
* import { getDefaultParticleSystemConfig, createParticleSystem } from '@newkrok/three-particles';
|
|
58
|
-
*
|
|
59
|
-
* // Get default config and modify it
|
|
60
|
-
* const config = getDefaultParticleSystemConfig();
|
|
61
|
-
* config.emission.rateOverTime = 100;
|
|
62
|
-
* config.startColor.min = { r: 1, g: 0, b: 0 };
|
|
63
|
-
*
|
|
64
|
-
* const { instance } = createParticleSystem(config);
|
|
65
|
-
* scene.add(instance);
|
|
66
|
-
* ```
|
|
67
|
-
*/
|
|
68
|
-
export const getDefaultParticleSystemConfig = () => JSON.parse(JSON.stringify(DEFAULT_PARTICLE_SYSTEM_CONFIG));
|
|
69
|
-
const DEFAULT_PARTICLE_SYSTEM_CONFIG = {
|
|
70
|
-
transform: {
|
|
71
|
-
position: new THREE.Vector3(),
|
|
72
|
-
rotation: new THREE.Vector3(),
|
|
73
|
-
scale: new THREE.Vector3(1, 1, 1),
|
|
74
|
-
},
|
|
75
|
-
duration: 5.0,
|
|
76
|
-
looping: true,
|
|
77
|
-
startDelay: 0,
|
|
78
|
-
startLifetime: 5.0,
|
|
79
|
-
startSpeed: 1.0,
|
|
80
|
-
startSize: 1.0,
|
|
81
|
-
startOpacity: 1.0,
|
|
82
|
-
startRotation: 0.0,
|
|
83
|
-
startColor: {
|
|
84
|
-
min: { r: 1.0, g: 1.0, b: 1.0 },
|
|
85
|
-
max: { r: 1.0, g: 1.0, b: 1.0 },
|
|
86
|
-
},
|
|
87
|
-
gravity: 0.0,
|
|
88
|
-
simulationSpace: "LOCAL" /* SimulationSpace.LOCAL */,
|
|
89
|
-
maxParticles: 100.0,
|
|
90
|
-
emission: {
|
|
91
|
-
rateOverTime: 10.0,
|
|
92
|
-
rateOverDistance: 0.0,
|
|
93
|
-
bursts: [],
|
|
94
|
-
},
|
|
95
|
-
shape: {
|
|
96
|
-
shape: "SPHERE" /* Shape.SPHERE */,
|
|
97
|
-
sphere: {
|
|
98
|
-
radius: 1.0,
|
|
99
|
-
radiusThickness: 1.0,
|
|
100
|
-
arc: 360.0,
|
|
101
|
-
},
|
|
102
|
-
cone: {
|
|
103
|
-
angle: 25.0,
|
|
104
|
-
radius: 1.0,
|
|
105
|
-
radiusThickness: 1.0,
|
|
106
|
-
arc: 360.0,
|
|
107
|
-
},
|
|
108
|
-
circle: {
|
|
109
|
-
radius: 1.0,
|
|
110
|
-
radiusThickness: 1.0,
|
|
111
|
-
arc: 360.0,
|
|
112
|
-
},
|
|
113
|
-
rectangle: {
|
|
114
|
-
rotation: { x: 0.0, y: 0.0 }, // TODO: add z rotation
|
|
115
|
-
scale: { x: 1.0, y: 1.0 },
|
|
116
|
-
},
|
|
117
|
-
box: {
|
|
118
|
-
scale: { x: 1.0, y: 1.0, z: 1.0 },
|
|
119
|
-
emitFrom: "VOLUME" /* EmitFrom.VOLUME */,
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
map: undefined,
|
|
123
|
-
renderer: {
|
|
124
|
-
blending: THREE.NormalBlending,
|
|
125
|
-
discardBackgroundColor: false,
|
|
126
|
-
backgroundColorTolerance: 1.0,
|
|
127
|
-
backgroundColor: { r: 1.0, g: 1.0, b: 1.0 },
|
|
128
|
-
transparent: true,
|
|
129
|
-
depthTest: true,
|
|
130
|
-
depthWrite: false,
|
|
131
|
-
},
|
|
132
|
-
velocityOverLifetime: {
|
|
133
|
-
isActive: false,
|
|
134
|
-
linear: {
|
|
135
|
-
x: 0,
|
|
136
|
-
y: 0,
|
|
137
|
-
z: 0,
|
|
138
|
-
},
|
|
139
|
-
orbital: {
|
|
140
|
-
x: 0,
|
|
141
|
-
y: 0,
|
|
142
|
-
z: 0,
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
sizeOverLifetime: {
|
|
146
|
-
isActive: false,
|
|
147
|
-
lifetimeCurve: {
|
|
148
|
-
type: "BEZIER" /* LifeTimeCurve.BEZIER */,
|
|
149
|
-
scale: 1,
|
|
150
|
-
bezierPoints: [
|
|
151
|
-
{ x: 0, y: 0, percentage: 0 },
|
|
152
|
-
{ x: 1, y: 1, percentage: 1 },
|
|
153
|
-
],
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
colorOverLifetime: {
|
|
157
|
-
isActive: false,
|
|
158
|
-
r: {
|
|
159
|
-
type: "BEZIER" /* LifeTimeCurve.BEZIER */,
|
|
160
|
-
scale: 1,
|
|
161
|
-
bezierPoints: [
|
|
162
|
-
{ x: 0, y: 1, percentage: 0 },
|
|
163
|
-
{ x: 1, y: 1, percentage: 1 },
|
|
164
|
-
],
|
|
165
|
-
},
|
|
166
|
-
g: {
|
|
167
|
-
type: "BEZIER" /* LifeTimeCurve.BEZIER */,
|
|
168
|
-
scale: 1,
|
|
169
|
-
bezierPoints: [
|
|
170
|
-
{ x: 0, y: 1, percentage: 0 },
|
|
171
|
-
{ x: 1, y: 1, percentage: 1 },
|
|
172
|
-
],
|
|
173
|
-
},
|
|
174
|
-
b: {
|
|
175
|
-
type: "BEZIER" /* LifeTimeCurve.BEZIER */,
|
|
176
|
-
scale: 1,
|
|
177
|
-
bezierPoints: [
|
|
178
|
-
{ x: 0, y: 1, percentage: 0 },
|
|
179
|
-
{ x: 1, y: 1, percentage: 1 },
|
|
180
|
-
],
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
opacityOverLifetime: {
|
|
184
|
-
isActive: false,
|
|
185
|
-
lifetimeCurve: {
|
|
186
|
-
type: "BEZIER" /* LifeTimeCurve.BEZIER */,
|
|
187
|
-
scale: 1,
|
|
188
|
-
bezierPoints: [
|
|
189
|
-
{ x: 0, y: 0, percentage: 0 },
|
|
190
|
-
{ x: 1, y: 1, percentage: 1 },
|
|
191
|
-
],
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
rotationOverLifetime: {
|
|
195
|
-
isActive: false,
|
|
196
|
-
min: 0.0,
|
|
197
|
-
max: 0.0,
|
|
198
|
-
},
|
|
199
|
-
noise: {
|
|
200
|
-
isActive: false,
|
|
201
|
-
useRandomOffset: false,
|
|
202
|
-
strength: 1.0,
|
|
203
|
-
frequency: 0.5,
|
|
204
|
-
octaves: 1,
|
|
205
|
-
positionAmount: 1.0,
|
|
206
|
-
rotationAmount: 0.0,
|
|
207
|
-
sizeAmount: 0.0,
|
|
208
|
-
},
|
|
209
|
-
textureSheetAnimation: {
|
|
210
|
-
tiles: new THREE.Vector2(1.0, 1.0),
|
|
211
|
-
timeMode: "LIFETIME" /* TimeMode.LIFETIME */,
|
|
212
|
-
fps: 30.0,
|
|
213
|
-
startFrame: 0,
|
|
214
|
-
},
|
|
215
|
-
};
|
|
216
|
-
const createFloat32Attributes = ({ geometry, propertyName, maxParticles, factory, }) => {
|
|
217
|
-
const array = new Float32Array(maxParticles);
|
|
218
|
-
if (typeof factory === 'function') {
|
|
219
|
-
for (let i = 0; i < maxParticles; i++) {
|
|
220
|
-
array[i] = factory(undefined, i);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
array.fill(factory);
|
|
225
|
-
}
|
|
226
|
-
geometry.setAttribute(propertyName, new THREE.BufferAttribute(array, 1));
|
|
227
|
-
};
|
|
228
|
-
const calculatePositionAndVelocity = (generalData, { shape, sphere, cone, circle, rectangle, box }, startSpeed, position, velocity) => {
|
|
229
|
-
const calculatedStartSpeed = calculateValue(generalData.particleSystemId, startSpeed, generalData.normalizedLifetimePercentage);
|
|
230
|
-
switch (shape) {
|
|
231
|
-
case "SPHERE" /* Shape.SPHERE */:
|
|
232
|
-
calculateRandomPositionAndVelocityOnSphere(position, generalData.wrapperQuaternion, velocity, calculatedStartSpeed, sphere);
|
|
233
|
-
break;
|
|
234
|
-
case "CONE" /* Shape.CONE */:
|
|
235
|
-
calculateRandomPositionAndVelocityOnCone(position, generalData.wrapperQuaternion, velocity, calculatedStartSpeed, cone);
|
|
236
|
-
break;
|
|
237
|
-
case "CIRCLE" /* Shape.CIRCLE */:
|
|
238
|
-
calculateRandomPositionAndVelocityOnCircle(position, generalData.wrapperQuaternion, velocity, calculatedStartSpeed, circle);
|
|
239
|
-
break;
|
|
240
|
-
case "RECTANGLE" /* Shape.RECTANGLE */:
|
|
241
|
-
calculateRandomPositionAndVelocityOnRectangle(position, generalData.wrapperQuaternion, velocity, calculatedStartSpeed, rectangle);
|
|
242
|
-
break;
|
|
243
|
-
case "BOX" /* Shape.BOX */:
|
|
244
|
-
calculateRandomPositionAndVelocityOnBox(position, generalData.wrapperQuaternion, velocity, calculatedStartSpeed, box);
|
|
245
|
-
break;
|
|
246
|
-
}
|
|
247
|
-
};
|
|
248
|
-
const destroyParticleSystem = (particleSystem) => {
|
|
249
|
-
createdParticleSystems = createdParticleSystems.filter(({ particleSystem: savedParticleSystem, wrapper, generalData: { particleSystemId }, }) => {
|
|
250
|
-
if (savedParticleSystem !== particleSystem &&
|
|
251
|
-
wrapper !== particleSystem) {
|
|
252
|
-
return true;
|
|
253
|
-
}
|
|
254
|
-
removeBezierCurveFunction(particleSystemId);
|
|
255
|
-
savedParticleSystem.geometry.dispose();
|
|
256
|
-
if (Array.isArray(savedParticleSystem.material))
|
|
257
|
-
savedParticleSystem.material.forEach((material) => material.dispose());
|
|
258
|
-
else
|
|
259
|
-
savedParticleSystem.material.dispose();
|
|
260
|
-
if (savedParticleSystem.parent)
|
|
261
|
-
savedParticleSystem.parent.remove(savedParticleSystem);
|
|
262
|
-
return false;
|
|
263
|
-
});
|
|
264
|
-
};
|
|
265
|
-
/**
|
|
266
|
-
* Creates a new particle system with the specified configuration.
|
|
267
|
-
*
|
|
268
|
-
* This is the primary function for instantiating particle effects. It handles the complete
|
|
269
|
-
* setup of a particle system including geometry creation, material configuration, shader setup,
|
|
270
|
-
* and initialization of all particle properties.
|
|
271
|
-
*
|
|
272
|
-
* @param config - Configuration object for the particle system. If not provided, uses default settings.
|
|
273
|
-
* See {@link ParticleSystemConfig} for all available options.
|
|
274
|
-
* @param externalNow - Optional custom timestamp in milliseconds. If not provided, uses `Date.now()`.
|
|
275
|
-
* Useful for synchronized particle systems or testing.
|
|
276
|
-
*
|
|
277
|
-
* @returns A {@link ParticleSystem} object containing:
|
|
278
|
-
* - `instance`: The THREE.Object3D that should be added to your scene
|
|
279
|
-
* - `resumeEmitter()`: Function to resume particle emission
|
|
280
|
-
* - `pauseEmitter()`: Function to pause particle emission
|
|
281
|
-
* - `dispose()`: Function to clean up resources and remove the particle system
|
|
282
|
-
*
|
|
283
|
-
* @example
|
|
284
|
-
* ```typescript
|
|
285
|
-
* import { createParticleSystem, updateParticleSystems } from '@newkrok/three-particles';
|
|
286
|
-
*
|
|
287
|
-
* // Create a basic particle system with default settings
|
|
288
|
-
* const { instance, dispose } = createParticleSystem();
|
|
289
|
-
* scene.add(instance);
|
|
290
|
-
*
|
|
291
|
-
* // Create a custom fire effect
|
|
292
|
-
* const fireEffect = createParticleSystem({
|
|
293
|
-
* duration: 2.0,
|
|
294
|
-
* looping: true,
|
|
295
|
-
* startLifetime: { min: 0.5, max: 1.5 },
|
|
296
|
-
* startSpeed: { min: 2, max: 4 },
|
|
297
|
-
* startSize: { min: 0.5, max: 1.5 },
|
|
298
|
-
* startColor: {
|
|
299
|
-
* min: { r: 1.0, g: 0.3, b: 0.0 },
|
|
300
|
-
* max: { r: 1.0, g: 0.8, b: 0.0 }
|
|
301
|
-
* },
|
|
302
|
-
* emission: { rateOverTime: 50 },
|
|
303
|
-
* shape: {
|
|
304
|
-
* shape: Shape.CONE,
|
|
305
|
-
* cone: { angle: 10, radius: 0.2 }
|
|
306
|
-
* }
|
|
307
|
-
* });
|
|
308
|
-
* scene.add(fireEffect.instance);
|
|
309
|
-
*
|
|
310
|
-
* // In your animation loop
|
|
311
|
-
* function animate(time) {
|
|
312
|
-
* updateParticleSystems({ now: time, delta: deltaTime, elapsed: elapsedTime });
|
|
313
|
-
* renderer.render(scene, camera);
|
|
314
|
-
* }
|
|
315
|
-
*
|
|
316
|
-
* // Clean up when done
|
|
317
|
-
* fireEffect.dispose();
|
|
318
|
-
* ```
|
|
319
|
-
*
|
|
320
|
-
* @see {@link updateParticleSystems} - Required function to call in your animation loop
|
|
321
|
-
* @see {@link ParticleSystemConfig} - Complete configuration options
|
|
322
|
-
*/
|
|
323
|
-
export const createParticleSystem = (config = DEFAULT_PARTICLE_SYSTEM_CONFIG, externalNow) => {
|
|
324
|
-
const now = externalNow || Date.now();
|
|
325
|
-
const generalData = {
|
|
326
|
-
particleSystemId: _particleSystemId++,
|
|
327
|
-
normalizedLifetimePercentage: 0,
|
|
328
|
-
distanceFromLastEmitByDistance: 0,
|
|
329
|
-
lastWorldPosition: new THREE.Vector3(-99999),
|
|
330
|
-
currentWorldPosition: new THREE.Vector3(-99999),
|
|
331
|
-
worldPositionChange: new THREE.Vector3(),
|
|
332
|
-
worldQuaternion: new THREE.Quaternion(),
|
|
333
|
-
wrapperQuaternion: new THREE.Quaternion(),
|
|
334
|
-
lastWorldQuaternion: new THREE.Quaternion(-99999),
|
|
335
|
-
worldEuler: new THREE.Euler(),
|
|
336
|
-
gravityVelocity: new THREE.Vector3(0, 0, 0),
|
|
337
|
-
startValues: {},
|
|
338
|
-
linearVelocityData: undefined,
|
|
339
|
-
orbitalVelocityData: undefined,
|
|
340
|
-
lifetimeValues: {},
|
|
341
|
-
creationTimes: [],
|
|
342
|
-
noise: {
|
|
343
|
-
isActive: false,
|
|
344
|
-
strength: 0,
|
|
345
|
-
noisePower: 0,
|
|
346
|
-
positionAmount: 0,
|
|
347
|
-
rotationAmount: 0,
|
|
348
|
-
sizeAmount: 0,
|
|
349
|
-
},
|
|
350
|
-
isEnabled: true,
|
|
351
|
-
};
|
|
352
|
-
const normalizedConfig = ObjectUtils.deepMerge(DEFAULT_PARTICLE_SYSTEM_CONFIG, config, { applyToFirstObject: false, skippedProperties: [] });
|
|
353
|
-
let particleMap = normalizedConfig.map || createDefaultParticleTexture();
|
|
354
|
-
const { transform, duration, looping, startDelay, startLifetime, startSpeed, startSize, startRotation, startColor, startOpacity, gravity, simulationSpace, maxParticles, emission, shape, renderer, noise, velocityOverLifetime, onUpdate, onComplete, textureSheetAnimation, } = normalizedConfig;
|
|
355
|
-
if (typeof renderer?.blending === 'string')
|
|
356
|
-
renderer.blending = blendingMap[renderer.blending];
|
|
357
|
-
const startPositions = Array.from({ length: maxParticles }, () => new THREE.Vector3());
|
|
358
|
-
const velocities = Array.from({ length: maxParticles }, () => new THREE.Vector3());
|
|
359
|
-
generalData.creationTimes = Array.from({ length: maxParticles }, () => 0);
|
|
360
|
-
// Free list for O(1) inactive particle lookup (stack, top = end of array)
|
|
361
|
-
const freeList = Array.from({ length: maxParticles }, (_, i) => maxParticles - 1 - i);
|
|
362
|
-
if (velocityOverLifetime.isActive) {
|
|
363
|
-
generalData.linearVelocityData = Array.from({ length: maxParticles }, () => ({
|
|
364
|
-
speed: new THREE.Vector3(velocityOverLifetime.linear.x
|
|
365
|
-
? calculateValue(generalData.particleSystemId, velocityOverLifetime.linear.x, 0)
|
|
366
|
-
: 0, velocityOverLifetime.linear.y
|
|
367
|
-
? calculateValue(generalData.particleSystemId, velocityOverLifetime.linear.y, 0)
|
|
368
|
-
: 0, velocityOverLifetime.linear.z
|
|
369
|
-
? calculateValue(generalData.particleSystemId, velocityOverLifetime.linear.z, 0)
|
|
370
|
-
: 0),
|
|
371
|
-
valueModifiers: {
|
|
372
|
-
x: isLifeTimeCurve(velocityOverLifetime.linear.x || 0)
|
|
373
|
-
? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.linear.x)
|
|
374
|
-
: undefined,
|
|
375
|
-
y: isLifeTimeCurve(velocityOverLifetime.linear.y || 0)
|
|
376
|
-
? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.linear.y)
|
|
377
|
-
: undefined,
|
|
378
|
-
z: isLifeTimeCurve(velocityOverLifetime.linear.z || 0)
|
|
379
|
-
? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.linear.z)
|
|
380
|
-
: undefined,
|
|
381
|
-
},
|
|
382
|
-
}));
|
|
383
|
-
generalData.orbitalVelocityData = Array.from({ length: maxParticles }, () => ({
|
|
384
|
-
speed: new THREE.Vector3(velocityOverLifetime.orbital.x
|
|
385
|
-
? calculateValue(generalData.particleSystemId, velocityOverLifetime.orbital.x, 0)
|
|
386
|
-
: 0, velocityOverLifetime.orbital.y
|
|
387
|
-
? calculateValue(generalData.particleSystemId, velocityOverLifetime.orbital.y, 0)
|
|
388
|
-
: 0, velocityOverLifetime.orbital.z
|
|
389
|
-
? calculateValue(generalData.particleSystemId, velocityOverLifetime.orbital.z, 0)
|
|
390
|
-
: 0),
|
|
391
|
-
valueModifiers: {
|
|
392
|
-
x: isLifeTimeCurve(velocityOverLifetime.orbital.x || 0)
|
|
393
|
-
? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.orbital.x)
|
|
394
|
-
: undefined,
|
|
395
|
-
y: isLifeTimeCurve(velocityOverLifetime.orbital.y || 0)
|
|
396
|
-
? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.orbital.y)
|
|
397
|
-
: undefined,
|
|
398
|
-
z: isLifeTimeCurve(velocityOverLifetime.orbital.z || 0)
|
|
399
|
-
? getCurveFunctionFromConfig(generalData.particleSystemId, velocityOverLifetime.orbital.z)
|
|
400
|
-
: undefined,
|
|
401
|
-
},
|
|
402
|
-
positionOffset: new THREE.Vector3(),
|
|
403
|
-
}));
|
|
404
|
-
}
|
|
405
|
-
const startValueKeys = [
|
|
406
|
-
'startSize',
|
|
407
|
-
'startOpacity',
|
|
408
|
-
];
|
|
409
|
-
startValueKeys.forEach((key) => {
|
|
410
|
-
generalData.startValues[key] = Array.from({ length: maxParticles }, () => calculateValue(generalData.particleSystemId, normalizedConfig[key], 0));
|
|
411
|
-
});
|
|
412
|
-
generalData.startValues.startColorR = Array.from({ length: maxParticles }, () => 0);
|
|
413
|
-
generalData.startValues.startColorG = Array.from({ length: maxParticles }, () => 0);
|
|
414
|
-
generalData.startValues.startColorB = Array.from({ length: maxParticles }, () => 0);
|
|
415
|
-
const lifetimeValueKeys = [
|
|
416
|
-
'rotationOverLifetime',
|
|
417
|
-
];
|
|
418
|
-
lifetimeValueKeys.forEach((key) => {
|
|
419
|
-
const value = normalizedConfig[key];
|
|
420
|
-
if (value.isActive)
|
|
421
|
-
generalData.lifetimeValues[key] = Array.from({ length: maxParticles }, () => THREE.MathUtils.randFloat(value.min, value.max));
|
|
422
|
-
});
|
|
423
|
-
generalData.noise = {
|
|
424
|
-
isActive: noise.isActive,
|
|
425
|
-
strength: noise.strength,
|
|
426
|
-
noisePower: 0.15 * noise.strength,
|
|
427
|
-
positionAmount: noise.positionAmount,
|
|
428
|
-
rotationAmount: noise.rotationAmount,
|
|
429
|
-
sizeAmount: noise.sizeAmount,
|
|
430
|
-
sampler: noise.isActive
|
|
431
|
-
? new FBM({
|
|
432
|
-
seed: Math.random(),
|
|
433
|
-
scale: noise.frequency,
|
|
434
|
-
octaves: noise.octaves,
|
|
435
|
-
})
|
|
436
|
-
: undefined,
|
|
437
|
-
offsets: noise.useRandomOffset
|
|
438
|
-
? Array.from({ length: maxParticles }, () => Math.random() * 100)
|
|
439
|
-
: undefined,
|
|
440
|
-
};
|
|
441
|
-
// Initialize burst states if bursts are configured
|
|
442
|
-
if (emission.bursts && emission.bursts.length > 0) {
|
|
443
|
-
generalData.burstStates = emission.bursts.map(() => ({
|
|
444
|
-
cyclesExecuted: 0,
|
|
445
|
-
lastCycleTime: 0,
|
|
446
|
-
probabilityPassed: false,
|
|
447
|
-
}));
|
|
448
|
-
}
|
|
449
|
-
const material = new THREE.ShaderMaterial({
|
|
450
|
-
uniforms: {
|
|
451
|
-
elapsed: {
|
|
452
|
-
value: 0.0,
|
|
453
|
-
},
|
|
454
|
-
map: {
|
|
455
|
-
value: particleMap,
|
|
456
|
-
},
|
|
457
|
-
tiles: {
|
|
458
|
-
value: textureSheetAnimation.tiles,
|
|
459
|
-
},
|
|
460
|
-
fps: {
|
|
461
|
-
value: textureSheetAnimation.fps,
|
|
462
|
-
},
|
|
463
|
-
useFPSForFrameIndex: {
|
|
464
|
-
value: textureSheetAnimation.timeMode === "FPS" /* TimeMode.FPS */,
|
|
465
|
-
},
|
|
466
|
-
backgroundColor: {
|
|
467
|
-
value: renderer.backgroundColor,
|
|
468
|
-
},
|
|
469
|
-
discardBackgroundColor: {
|
|
470
|
-
value: renderer.discardBackgroundColor,
|
|
471
|
-
},
|
|
472
|
-
backgroundColorTolerance: {
|
|
473
|
-
value: renderer.backgroundColorTolerance,
|
|
474
|
-
},
|
|
475
|
-
},
|
|
476
|
-
vertexShader: ParticleSystemVertexShader,
|
|
477
|
-
fragmentShader: ParticleSystemFragmentShader,
|
|
478
|
-
transparent: renderer.transparent,
|
|
479
|
-
blending: renderer.blending,
|
|
480
|
-
depthTest: renderer.depthTest,
|
|
481
|
-
depthWrite: renderer.depthWrite,
|
|
482
|
-
});
|
|
483
|
-
const geometry = new THREE.BufferGeometry();
|
|
484
|
-
for (let i = 0; i < maxParticles; i++)
|
|
485
|
-
calculatePositionAndVelocity(generalData, shape, startSpeed, startPositions[i], velocities[i]);
|
|
486
|
-
const positionArray = new Float32Array(maxParticles * 3);
|
|
487
|
-
for (let i = 0; i < maxParticles; i++) {
|
|
488
|
-
positionArray[i * 3] = startPositions[i].x;
|
|
489
|
-
positionArray[i * 3 + 1] = startPositions[i].y;
|
|
490
|
-
positionArray[i * 3 + 2] = startPositions[i].z;
|
|
491
|
-
}
|
|
492
|
-
geometry.setAttribute('position', new THREE.BufferAttribute(positionArray, 3));
|
|
493
|
-
createFloat32Attributes({
|
|
494
|
-
geometry,
|
|
495
|
-
propertyName: 'isActive',
|
|
496
|
-
maxParticles,
|
|
497
|
-
factory: 0,
|
|
498
|
-
});
|
|
499
|
-
createFloat32Attributes({
|
|
500
|
-
geometry,
|
|
501
|
-
propertyName: 'lifetime',
|
|
502
|
-
maxParticles,
|
|
503
|
-
factory: 0,
|
|
504
|
-
});
|
|
505
|
-
createFloat32Attributes({
|
|
506
|
-
geometry,
|
|
507
|
-
propertyName: 'startLifetime',
|
|
508
|
-
maxParticles,
|
|
509
|
-
factory: () => calculateValue(generalData.particleSystemId, startLifetime, 0) * 1000,
|
|
510
|
-
});
|
|
511
|
-
createFloat32Attributes({
|
|
512
|
-
geometry,
|
|
513
|
-
propertyName: 'startFrame',
|
|
514
|
-
maxParticles,
|
|
515
|
-
factory: () => textureSheetAnimation.startFrame
|
|
516
|
-
? calculateValue(generalData.particleSystemId, textureSheetAnimation.startFrame, 0)
|
|
517
|
-
: 0,
|
|
518
|
-
});
|
|
519
|
-
createFloat32Attributes({
|
|
520
|
-
geometry,
|
|
521
|
-
propertyName: 'opacity',
|
|
522
|
-
maxParticles,
|
|
523
|
-
factory: () => calculateValue(generalData.particleSystemId, startOpacity, 0),
|
|
524
|
-
});
|
|
525
|
-
createFloat32Attributes({
|
|
526
|
-
geometry,
|
|
527
|
-
propertyName: 'rotation',
|
|
528
|
-
maxParticles,
|
|
529
|
-
factory: () => calculateValue(generalData.particleSystemId, startRotation, 0),
|
|
530
|
-
});
|
|
531
|
-
createFloat32Attributes({
|
|
532
|
-
geometry,
|
|
533
|
-
propertyName: 'size',
|
|
534
|
-
maxParticles,
|
|
535
|
-
factory: (_, index) => generalData.startValues.startSize[index],
|
|
536
|
-
});
|
|
537
|
-
createFloat32Attributes({
|
|
538
|
-
geometry,
|
|
539
|
-
propertyName: 'rotation',
|
|
540
|
-
maxParticles,
|
|
541
|
-
factory: 0,
|
|
542
|
-
});
|
|
543
|
-
const colorRandomRatio = Math.random();
|
|
544
|
-
createFloat32Attributes({
|
|
545
|
-
geometry,
|
|
546
|
-
propertyName: 'colorR',
|
|
547
|
-
maxParticles,
|
|
548
|
-
factory: () => startColor.min.r +
|
|
549
|
-
colorRandomRatio * (startColor.max.r - startColor.min.r),
|
|
550
|
-
});
|
|
551
|
-
createFloat32Attributes({
|
|
552
|
-
geometry,
|
|
553
|
-
propertyName: 'colorG',
|
|
554
|
-
maxParticles,
|
|
555
|
-
factory: () => startColor.min.g +
|
|
556
|
-
colorRandomRatio * (startColor.max.g - startColor.min.g),
|
|
557
|
-
});
|
|
558
|
-
createFloat32Attributes({
|
|
559
|
-
geometry,
|
|
560
|
-
propertyName: 'colorB',
|
|
561
|
-
maxParticles,
|
|
562
|
-
factory: () => startColor.min.b +
|
|
563
|
-
colorRandomRatio * (startColor.max.b - startColor.min.b),
|
|
564
|
-
});
|
|
565
|
-
createFloat32Attributes({
|
|
566
|
-
geometry,
|
|
567
|
-
propertyName: 'colorA',
|
|
568
|
-
maxParticles,
|
|
569
|
-
factory: 0,
|
|
570
|
-
});
|
|
571
|
-
const deactivateParticle = (particleIndex) => {
|
|
572
|
-
geometry.attributes.isActive.array[particleIndex] = 0;
|
|
573
|
-
geometry.attributes.colorA.array[particleIndex] = 0;
|
|
574
|
-
geometry.attributes.colorA.needsUpdate = true;
|
|
575
|
-
freeList.push(particleIndex);
|
|
576
|
-
};
|
|
577
|
-
const activateParticle = ({ particleIndex, activationTime, position, }) => {
|
|
578
|
-
geometry.attributes.isActive.array[particleIndex] = 1;
|
|
579
|
-
generalData.creationTimes[particleIndex] = activationTime;
|
|
580
|
-
if (generalData.noise.offsets)
|
|
581
|
-
generalData.noise.offsets[particleIndex] = Math.random() * 100;
|
|
582
|
-
const colorRandomRatio = Math.random();
|
|
583
|
-
geometry.attributes.colorR.array[particleIndex] =
|
|
584
|
-
startColor.min.r +
|
|
585
|
-
colorRandomRatio * (startColor.max.r - startColor.min.r);
|
|
586
|
-
geometry.attributes.colorR.needsUpdate = true;
|
|
587
|
-
geometry.attributes.colorG.array[particleIndex] =
|
|
588
|
-
startColor.min.g +
|
|
589
|
-
colorRandomRatio * (startColor.max.g - startColor.min.g);
|
|
590
|
-
geometry.attributes.colorG.needsUpdate = true;
|
|
591
|
-
geometry.attributes.colorB.array[particleIndex] =
|
|
592
|
-
startColor.min.b +
|
|
593
|
-
colorRandomRatio * (startColor.max.b - startColor.min.b);
|
|
594
|
-
geometry.attributes.colorB.needsUpdate = true;
|
|
595
|
-
generalData.startValues.startColorR[particleIndex] =
|
|
596
|
-
geometry.attributes.colorR.array[particleIndex];
|
|
597
|
-
generalData.startValues.startColorG[particleIndex] =
|
|
598
|
-
geometry.attributes.colorG.array[particleIndex];
|
|
599
|
-
generalData.startValues.startColorB[particleIndex] =
|
|
600
|
-
geometry.attributes.colorB.array[particleIndex];
|
|
601
|
-
geometry.attributes.startFrame.array[particleIndex] =
|
|
602
|
-
textureSheetAnimation.startFrame
|
|
603
|
-
? calculateValue(generalData.particleSystemId, textureSheetAnimation.startFrame, 0)
|
|
604
|
-
: 0;
|
|
605
|
-
geometry.attributes.startFrame.needsUpdate = true;
|
|
606
|
-
geometry.attributes.startLifetime.array[particleIndex] =
|
|
607
|
-
calculateValue(generalData.particleSystemId, startLifetime, generalData.normalizedLifetimePercentage) * 1000;
|
|
608
|
-
geometry.attributes.startLifetime.needsUpdate = true;
|
|
609
|
-
generalData.startValues.startSize[particleIndex] = calculateValue(generalData.particleSystemId, startSize, generalData.normalizedLifetimePercentage);
|
|
610
|
-
geometry.attributes.size.array[particleIndex] =
|
|
611
|
-
generalData.startValues.startSize[particleIndex];
|
|
612
|
-
geometry.attributes.size.needsUpdate = true;
|
|
613
|
-
generalData.startValues.startOpacity[particleIndex] = calculateValue(generalData.particleSystemId, startOpacity, generalData.normalizedLifetimePercentage);
|
|
614
|
-
geometry.attributes.colorA.array[particleIndex] =
|
|
615
|
-
generalData.startValues.startOpacity[particleIndex];
|
|
616
|
-
geometry.attributes.colorA.needsUpdate = true;
|
|
617
|
-
geometry.attributes.rotation.array[particleIndex] = calculateValue(generalData.particleSystemId, startRotation, generalData.normalizedLifetimePercentage);
|
|
618
|
-
geometry.attributes.rotation.needsUpdate = true;
|
|
619
|
-
if (normalizedConfig.rotationOverLifetime.isActive)
|
|
620
|
-
generalData.lifetimeValues.rotationOverLifetime[particleIndex] =
|
|
621
|
-
THREE.MathUtils.randFloat(normalizedConfig.rotationOverLifetime.min, normalizedConfig.rotationOverLifetime.max);
|
|
622
|
-
calculatePositionAndVelocity(generalData, shape, startSpeed, startPositions[particleIndex], velocities[particleIndex]);
|
|
623
|
-
const positionIndex = Math.floor(particleIndex * 3);
|
|
624
|
-
geometry.attributes.position.array[positionIndex] =
|
|
625
|
-
position.x + startPositions[particleIndex].x;
|
|
626
|
-
geometry.attributes.position.array[positionIndex + 1] =
|
|
627
|
-
position.y + startPositions[particleIndex].y;
|
|
628
|
-
geometry.attributes.position.array[positionIndex + 2] =
|
|
629
|
-
position.z + startPositions[particleIndex].z;
|
|
630
|
-
geometry.attributes.position.needsUpdate = true;
|
|
631
|
-
if (generalData.linearVelocityData) {
|
|
632
|
-
generalData.linearVelocityData[particleIndex].speed.set(normalizedConfig.velocityOverLifetime.linear.x
|
|
633
|
-
? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.linear.x, 0)
|
|
634
|
-
: 0, normalizedConfig.velocityOverLifetime.linear.y
|
|
635
|
-
? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.linear.y, 0)
|
|
636
|
-
: 0, normalizedConfig.velocityOverLifetime.linear.z
|
|
637
|
-
? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.linear.z, 0)
|
|
638
|
-
: 0);
|
|
639
|
-
}
|
|
640
|
-
if (generalData.orbitalVelocityData) {
|
|
641
|
-
generalData.orbitalVelocityData[particleIndex].speed.set(normalizedConfig.velocityOverLifetime.orbital.x
|
|
642
|
-
? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.orbital.x, 0)
|
|
643
|
-
: 0, normalizedConfig.velocityOverLifetime.orbital.y
|
|
644
|
-
? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.orbital.y, 0)
|
|
645
|
-
: 0, normalizedConfig.velocityOverLifetime.orbital.z
|
|
646
|
-
? calculateValue(generalData.particleSystemId, normalizedConfig.velocityOverLifetime.orbital.z, 0)
|
|
647
|
-
: 0);
|
|
648
|
-
generalData.orbitalVelocityData[particleIndex].positionOffset.set(startPositions[particleIndex].x, startPositions[particleIndex].y, startPositions[particleIndex].z);
|
|
649
|
-
}
|
|
650
|
-
geometry.attributes.lifetime.array[particleIndex] = 0;
|
|
651
|
-
geometry.attributes.lifetime.needsUpdate = true;
|
|
652
|
-
applyModifiers({
|
|
653
|
-
delta: 0,
|
|
654
|
-
generalData,
|
|
655
|
-
normalizedConfig,
|
|
656
|
-
attributes: particleSystem.geometry.attributes,
|
|
657
|
-
particleLifetimePercentage: 0,
|
|
658
|
-
particleIndex,
|
|
659
|
-
});
|
|
660
|
-
};
|
|
661
|
-
let particleSystem = new THREE.Points(geometry, material);
|
|
662
|
-
particleSystem.position.copy(transform.position);
|
|
663
|
-
particleSystem.rotation.x = THREE.MathUtils.degToRad(transform.rotation.x);
|
|
664
|
-
particleSystem.rotation.y = THREE.MathUtils.degToRad(transform.rotation.y);
|
|
665
|
-
particleSystem.rotation.z = THREE.MathUtils.degToRad(transform.rotation.z);
|
|
666
|
-
particleSystem.scale.copy(transform.scale);
|
|
667
|
-
const calculatedCreationTime = now + calculateValue(generalData.particleSystemId, startDelay) * 1000;
|
|
668
|
-
let wrapper;
|
|
669
|
-
if (normalizedConfig.simulationSpace === "WORLD" /* SimulationSpace.WORLD */) {
|
|
670
|
-
wrapper = new Gyroscope();
|
|
671
|
-
wrapper.add(particleSystem);
|
|
672
|
-
}
|
|
673
|
-
const instanceData = {
|
|
674
|
-
particleSystem,
|
|
675
|
-
wrapper,
|
|
676
|
-
elapsedUniform: material.uniforms.elapsed,
|
|
677
|
-
generalData,
|
|
678
|
-
onUpdate,
|
|
679
|
-
onComplete,
|
|
680
|
-
creationTime: calculatedCreationTime,
|
|
681
|
-
lastEmissionTime: calculatedCreationTime,
|
|
682
|
-
duration,
|
|
683
|
-
looping,
|
|
684
|
-
simulationSpace,
|
|
685
|
-
gravity,
|
|
686
|
-
emission,
|
|
687
|
-
normalizedConfig,
|
|
688
|
-
iterationCount: 0,
|
|
689
|
-
velocities,
|
|
690
|
-
freeList,
|
|
691
|
-
deactivateParticle,
|
|
692
|
-
activateParticle,
|
|
693
|
-
};
|
|
694
|
-
createdParticleSystems.push(instanceData);
|
|
695
|
-
const resumeEmitter = () => (generalData.isEnabled = true);
|
|
696
|
-
const pauseEmitter = () => (generalData.isEnabled = false);
|
|
697
|
-
const dispose = () => destroyParticleSystem(particleSystem);
|
|
698
|
-
const update = (cycleData) => updateParticleSystemInstance(instanceData, cycleData);
|
|
699
|
-
return {
|
|
700
|
-
instance: wrapper || particleSystem,
|
|
701
|
-
resumeEmitter,
|
|
702
|
-
pauseEmitter,
|
|
703
|
-
dispose,
|
|
704
|
-
update,
|
|
705
|
-
};
|
|
706
|
-
};
|
|
707
|
-
/**
|
|
708
|
-
* Updates all active particle systems created with {@link createParticleSystem}.
|
|
709
|
-
*
|
|
710
|
-
* This function must be called once per frame in your animation loop to animate all particles.
|
|
711
|
-
* It handles particle emission, movement, lifetime tracking, modifier application, and cleanup
|
|
712
|
-
* of expired particle systems.
|
|
713
|
-
*
|
|
714
|
-
* @param cycleData - Object containing timing information for the current frame:
|
|
715
|
-
* - `now`: Current timestamp in milliseconds (typically from `performance.now()` or `Date.now()`)
|
|
716
|
-
* - `delta`: Time elapsed since the last frame in seconds
|
|
717
|
-
* - `elapsed`: Total time elapsed since the animation started in seconds
|
|
718
|
-
*
|
|
719
|
-
* @example
|
|
720
|
-
* ```typescript
|
|
721
|
-
* import { createParticleSystem, updateParticleSystems } from '@newkrok/three-particles';
|
|
722
|
-
*
|
|
723
|
-
* const { instance } = createParticleSystem({
|
|
724
|
-
* // your config
|
|
725
|
-
* });
|
|
726
|
-
* scene.add(instance);
|
|
727
|
-
*
|
|
728
|
-
* // Animation loop
|
|
729
|
-
* let lastTime = 0;
|
|
730
|
-
* let elapsedTime = 0;
|
|
731
|
-
*
|
|
732
|
-
* function animate(currentTime) {
|
|
733
|
-
* requestAnimationFrame(animate);
|
|
734
|
-
*
|
|
735
|
-
* const delta = (currentTime - lastTime) / 1000; // Convert to seconds
|
|
736
|
-
* elapsedTime += delta;
|
|
737
|
-
* lastTime = currentTime;
|
|
738
|
-
*
|
|
739
|
-
* // Update all particle systems
|
|
740
|
-
* updateParticleSystems({
|
|
741
|
-
* now: currentTime,
|
|
742
|
-
* delta: delta,
|
|
743
|
-
* elapsed: elapsedTime
|
|
744
|
-
* });
|
|
745
|
-
*
|
|
746
|
-
* renderer.render(scene, camera);
|
|
747
|
-
* }
|
|
748
|
-
*
|
|
749
|
-
* animate(0);
|
|
750
|
-
* ```
|
|
751
|
-
*
|
|
752
|
-
* @example
|
|
753
|
-
* ```typescript
|
|
754
|
-
* // Using Three.js Clock for timing
|
|
755
|
-
* import * as THREE from 'three';
|
|
756
|
-
* import { updateParticleSystems } from '@newkrok/three-particles';
|
|
757
|
-
*
|
|
758
|
-
* const clock = new THREE.Clock();
|
|
759
|
-
*
|
|
760
|
-
* function animate() {
|
|
761
|
-
* requestAnimationFrame(animate);
|
|
762
|
-
*
|
|
763
|
-
* const delta = clock.getDelta();
|
|
764
|
-
* const elapsed = clock.getElapsedTime();
|
|
765
|
-
*
|
|
766
|
-
* updateParticleSystems({
|
|
767
|
-
* now: performance.now(),
|
|
768
|
-
* delta: delta,
|
|
769
|
-
* elapsed: elapsed
|
|
770
|
-
* });
|
|
771
|
-
*
|
|
772
|
-
* renderer.render(scene, camera);
|
|
773
|
-
* }
|
|
774
|
-
* ```
|
|
775
|
-
*
|
|
776
|
-
* @see {@link createParticleSystem} - Creates particle systems to be updated
|
|
777
|
-
* @see {@link CycleData} - Timing data structure
|
|
778
|
-
*/
|
|
779
|
-
const updateParticleSystemInstance = (props, { now, delta, elapsed }) => {
|
|
780
|
-
const { onUpdate, generalData, onComplete, particleSystem, wrapper, elapsedUniform, creationTime, lastEmissionTime, duration, looping, emission, normalizedConfig, iterationCount, velocities, freeList, deactivateParticle, activateParticle, simulationSpace, gravity, } = props;
|
|
781
|
-
const lifetime = now - creationTime;
|
|
782
|
-
const normalizedLifetime = lifetime % (duration * 1000);
|
|
783
|
-
generalData.normalizedLifetimePercentage = Math.max(Math.min(normalizedLifetime / (duration * 1000), 1), 0);
|
|
784
|
-
const { lastWorldPosition, currentWorldPosition, worldPositionChange, lastWorldQuaternion, worldQuaternion, worldEuler, gravityVelocity, isEnabled, } = generalData;
|
|
785
|
-
if (wrapper?.parent)
|
|
786
|
-
generalData.wrapperQuaternion.copy(wrapper.parent.quaternion);
|
|
787
|
-
_lastWorldPositionSnapshot.copy(lastWorldPosition);
|
|
788
|
-
elapsedUniform.value = elapsed;
|
|
789
|
-
particleSystem.getWorldPosition(currentWorldPosition);
|
|
790
|
-
if (lastWorldPosition.x !== -99999) {
|
|
791
|
-
worldPositionChange.set(currentWorldPosition.x - lastWorldPosition.x, currentWorldPosition.y - lastWorldPosition.y, currentWorldPosition.z - lastWorldPosition.z);
|
|
792
|
-
}
|
|
793
|
-
if (isEnabled) {
|
|
794
|
-
generalData.distanceFromLastEmitByDistance += worldPositionChange.length();
|
|
795
|
-
}
|
|
796
|
-
particleSystem.getWorldPosition(lastWorldPosition);
|
|
797
|
-
particleSystem.getWorldQuaternion(worldQuaternion);
|
|
798
|
-
if (lastWorldQuaternion.x === -99999 ||
|
|
799
|
-
lastWorldQuaternion.x !== worldQuaternion.x ||
|
|
800
|
-
lastWorldQuaternion.y !== worldQuaternion.y ||
|
|
801
|
-
lastWorldQuaternion.z !== worldQuaternion.z) {
|
|
802
|
-
worldEuler.setFromQuaternion(worldQuaternion);
|
|
803
|
-
lastWorldQuaternion.copy(worldQuaternion);
|
|
804
|
-
gravityVelocity.set(lastWorldPosition.x, lastWorldPosition.y + gravity, lastWorldPosition.z);
|
|
805
|
-
particleSystem.worldToLocal(gravityVelocity);
|
|
806
|
-
}
|
|
807
|
-
const creationTimes = generalData.creationTimes;
|
|
808
|
-
const attributes = particleSystem.geometry.attributes;
|
|
809
|
-
const isActiveArr = attributes.isActive.array;
|
|
810
|
-
const startLifetimeArr = attributes.startLifetime.array;
|
|
811
|
-
const positionArr = attributes.position.array;
|
|
812
|
-
const lifetimeArr = attributes.lifetime.array;
|
|
813
|
-
const creationTimesLength = creationTimes.length;
|
|
814
|
-
let positionNeedsUpdate = false;
|
|
815
|
-
let lifetimeNeedsUpdate = false;
|
|
816
|
-
_modifierParams.delta = delta;
|
|
817
|
-
_modifierParams.generalData = generalData;
|
|
818
|
-
_modifierParams.normalizedConfig = normalizedConfig;
|
|
819
|
-
_modifierParams.attributes = attributes;
|
|
820
|
-
for (let index = 0; index < creationTimesLength; index++) {
|
|
821
|
-
if (isActiveArr[index]) {
|
|
822
|
-
const particleLifetime = now - creationTimes[index];
|
|
823
|
-
if (particleLifetime > startLifetimeArr[index]) {
|
|
824
|
-
deactivateParticle(index);
|
|
825
|
-
}
|
|
826
|
-
else {
|
|
827
|
-
const velocity = velocities[index];
|
|
828
|
-
velocity.x -= gravityVelocity.x * delta;
|
|
829
|
-
velocity.y -= gravityVelocity.y * delta;
|
|
830
|
-
velocity.z -= gravityVelocity.z * delta;
|
|
831
|
-
if (gravity !== 0 ||
|
|
832
|
-
velocity.x !== 0 ||
|
|
833
|
-
velocity.y !== 0 ||
|
|
834
|
-
velocity.z !== 0 ||
|
|
835
|
-
worldPositionChange.x !== 0 ||
|
|
836
|
-
worldPositionChange.y !== 0 ||
|
|
837
|
-
worldPositionChange.z !== 0) {
|
|
838
|
-
const positionIndex = index * 3;
|
|
839
|
-
if (simulationSpace === "WORLD" /* SimulationSpace.WORLD */) {
|
|
840
|
-
positionArr[positionIndex] -= worldPositionChange.x;
|
|
841
|
-
positionArr[positionIndex + 1] -= worldPositionChange.y;
|
|
842
|
-
positionArr[positionIndex + 2] -= worldPositionChange.z;
|
|
843
|
-
}
|
|
844
|
-
positionArr[positionIndex] += velocity.x * delta;
|
|
845
|
-
positionArr[positionIndex + 1] += velocity.y * delta;
|
|
846
|
-
positionArr[positionIndex + 2] += velocity.z * delta;
|
|
847
|
-
positionNeedsUpdate = true;
|
|
848
|
-
}
|
|
849
|
-
lifetimeArr[index] = particleLifetime;
|
|
850
|
-
lifetimeNeedsUpdate = true;
|
|
851
|
-
_modifierParams.particleLifetimePercentage =
|
|
852
|
-
particleLifetime / startLifetimeArr[index];
|
|
853
|
-
_modifierParams.particleIndex = index;
|
|
854
|
-
applyModifiers(_modifierParams);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
if (positionNeedsUpdate)
|
|
859
|
-
attributes.position.needsUpdate = true;
|
|
860
|
-
if (lifetimeNeedsUpdate)
|
|
861
|
-
attributes.lifetime.needsUpdate = true;
|
|
862
|
-
if (isEnabled && (looping || lifetime < duration * 1000)) {
|
|
863
|
-
const emissionDelta = now - lastEmissionTime;
|
|
864
|
-
const neededParticlesByTime = emission.rateOverTime
|
|
865
|
-
? Math.floor(calculateValue(generalData.particleSystemId, emission.rateOverTime, generalData.normalizedLifetimePercentage) *
|
|
866
|
-
(emissionDelta / 1000))
|
|
867
|
-
: 0;
|
|
868
|
-
const rateOverDistance = emission.rateOverDistance
|
|
869
|
-
? calculateValue(generalData.particleSystemId, emission.rateOverDistance, generalData.normalizedLifetimePercentage)
|
|
870
|
-
: 0;
|
|
871
|
-
const neededParticlesByDistance = rateOverDistance > 0 && generalData.distanceFromLastEmitByDistance > 0
|
|
872
|
-
? Math.floor(generalData.distanceFromLastEmitByDistance / (1 / rateOverDistance))
|
|
873
|
-
: 0;
|
|
874
|
-
const useDistanceStep = neededParticlesByDistance > 0;
|
|
875
|
-
if (useDistanceStep) {
|
|
876
|
-
_distanceStep.x =
|
|
877
|
-
(currentWorldPosition.x - _lastWorldPositionSnapshot.x) /
|
|
878
|
-
neededParticlesByDistance;
|
|
879
|
-
_distanceStep.y =
|
|
880
|
-
(currentWorldPosition.y - _lastWorldPositionSnapshot.y) /
|
|
881
|
-
neededParticlesByDistance;
|
|
882
|
-
_distanceStep.z =
|
|
883
|
-
(currentWorldPosition.z - _lastWorldPositionSnapshot.z) /
|
|
884
|
-
neededParticlesByDistance;
|
|
885
|
-
}
|
|
886
|
-
let neededParticles = neededParticlesByTime + neededParticlesByDistance;
|
|
887
|
-
if (rateOverDistance > 0 && neededParticlesByDistance >= 1) {
|
|
888
|
-
generalData.distanceFromLastEmitByDistance = 0;
|
|
889
|
-
}
|
|
890
|
-
// Process burst emissions
|
|
891
|
-
if (emission.bursts && generalData.burstStates) {
|
|
892
|
-
const bursts = emission.bursts;
|
|
893
|
-
const burstStates = generalData.burstStates;
|
|
894
|
-
const currentIterationTime = normalizedLifetime;
|
|
895
|
-
for (let i = 0; i < bursts.length; i++) {
|
|
896
|
-
const burst = bursts[i];
|
|
897
|
-
const state = burstStates[i];
|
|
898
|
-
const burstTimeMs = burst.time * 1000;
|
|
899
|
-
const cycles = burst.cycles ?? 1;
|
|
900
|
-
const intervalMs = (burst.interval ?? 0) * 1000;
|
|
901
|
-
const probability = burst.probability ?? 1;
|
|
902
|
-
// Check if we've looped and need to reset burst states
|
|
903
|
-
if (looping &&
|
|
904
|
-
currentIterationTime < burstTimeMs &&
|
|
905
|
-
state.cyclesExecuted > 0) {
|
|
906
|
-
state.cyclesExecuted = 0;
|
|
907
|
-
state.lastCycleTime = 0;
|
|
908
|
-
state.probabilityPassed = false;
|
|
909
|
-
}
|
|
910
|
-
// Check if all cycles for this burst have been executed
|
|
911
|
-
if (state.cyclesExecuted >= cycles)
|
|
912
|
-
continue;
|
|
913
|
-
// Calculate the time for the next cycle
|
|
914
|
-
const nextCycleTime = burstTimeMs + state.cyclesExecuted * intervalMs;
|
|
915
|
-
// Check if it's time for the next cycle
|
|
916
|
-
if (currentIterationTime >= nextCycleTime) {
|
|
917
|
-
// On first cycle, determine if probability check passes
|
|
918
|
-
if (state.cyclesExecuted === 0) {
|
|
919
|
-
state.probabilityPassed = Math.random() < probability;
|
|
920
|
-
}
|
|
921
|
-
// Only emit if probability check passed
|
|
922
|
-
if (state.probabilityPassed) {
|
|
923
|
-
const burstCount = Math.floor(calculateValue(generalData.particleSystemId, burst.count, generalData.normalizedLifetimePercentage));
|
|
924
|
-
neededParticles += burstCount;
|
|
925
|
-
}
|
|
926
|
-
state.cyclesExecuted++;
|
|
927
|
-
state.lastCycleTime = currentIterationTime;
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
if (neededParticles > 0) {
|
|
932
|
-
let generatedParticlesByDistanceNeeds = 0;
|
|
933
|
-
for (let i = 0; i < neededParticles; i++) {
|
|
934
|
-
if (freeList.length === 0)
|
|
935
|
-
break;
|
|
936
|
-
const particleIndex = freeList.pop();
|
|
937
|
-
_tempPosition.x = 0;
|
|
938
|
-
_tempPosition.y = 0;
|
|
939
|
-
_tempPosition.z = 0;
|
|
940
|
-
if (useDistanceStep &&
|
|
941
|
-
generatedParticlesByDistanceNeeds < neededParticlesByDistance) {
|
|
942
|
-
_tempPosition.x = _distanceStep.x * generatedParticlesByDistanceNeeds;
|
|
943
|
-
_tempPosition.y = _distanceStep.y * generatedParticlesByDistanceNeeds;
|
|
944
|
-
_tempPosition.z = _distanceStep.z * generatedParticlesByDistanceNeeds;
|
|
945
|
-
generatedParticlesByDistanceNeeds++;
|
|
946
|
-
}
|
|
947
|
-
activateParticle({
|
|
948
|
-
particleIndex,
|
|
949
|
-
activationTime: now,
|
|
950
|
-
position: _tempPosition,
|
|
951
|
-
});
|
|
952
|
-
props.lastEmissionTime = now;
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
if (onUpdate)
|
|
956
|
-
onUpdate({
|
|
957
|
-
particleSystem,
|
|
958
|
-
delta,
|
|
959
|
-
elapsed,
|
|
960
|
-
lifetime,
|
|
961
|
-
normalizedLifetime,
|
|
962
|
-
iterationCount: iterationCount + 1,
|
|
963
|
-
});
|
|
964
|
-
}
|
|
965
|
-
else if (onComplete)
|
|
966
|
-
onComplete({
|
|
967
|
-
particleSystem,
|
|
968
|
-
});
|
|
969
|
-
};
|
|
970
|
-
export const updateParticleSystems = (cycleData) => {
|
|
971
|
-
createdParticleSystems.forEach((props) => updateParticleSystemInstance(props, cycleData));
|
|
972
|
-
};
|