littlejsengine 1.18.29 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FAQ.md +841 -815
- package/README.md +211 -199
- package/dist/littlejs.d.ts +4383 -812
- package/dist/littlejs.esm.js +31848 -16680
- package/dist/littlejs.esm.min.js +14 -1
- package/dist/littlejs.js +31230 -16166
- package/dist/littlejs.min.js +13 -1
- package/dist/littlejs.release.js +27419 -15457
- package/package.json +10 -5
- package/plugins/audioEffects.js +375 -0
- package/plugins/box2d.js +2516 -2073
- package/plugins/drawUtilities.js +206 -31
- package/plugins/gltf.js +740 -0
- package/plugins/jsconfig.json +19 -0
- package/plugins/lightSystem.js +719 -330
- package/plugins/math3d.js +911 -0
- package/plugins/medalSystem.js +118 -72
- package/plugins/newgrounds.js +400 -199
- package/plugins/pathFinder.js +208 -175
- package/plugins/pluginExport.js +196 -115
- package/plugins/postProcess.js +284 -166
- package/plugins/render3d.js +4072 -0
- package/plugins/render3dDebug.js +109 -0
- package/plugins/render3dExtras.js +1754 -0
- package/plugins/render3dVoxels.js +502 -0
- package/plugins/textureSheet.js +86 -48
- package/plugins/threejs.js +183 -158
- package/plugins/tweakables.js +411 -0
- package/plugins/tweenSystem.js +646 -526
- package/plugins/uiSystem.js +2024 -1687
- package/plugins/zzfxm.js +8 -15
- package/src/engine.js +345 -194
- package/src/engineAudio.js +1022 -777
- package/src/engineBuild.mjs +289 -253
- package/src/engineDebug.js +912 -735
- package/src/engineDraw.js +1774 -1510
- package/src/engineEditor.js +2431 -0
- package/src/engineExport.js +419 -396
- package/src/engineInput.js +1462 -1293
- package/src/engineLogo.js +4 -3
- package/src/engineMath.js +1274 -1183
- package/src/engineObject.js +674 -551
- package/src/engineParticles.js +583 -532
- package/src/engineRelease.js +67 -45
- package/src/engineSettings.js +808 -726
- package/src/engineTileLayer.js +995 -741
- package/src/engineUtilities.js +32 -13
- package/src/engineWebGL.js +1162 -941
- package/src/jsconfig.json +17 -13
- package/plugins/box2d.wasm.js +0 -630
- package/plugins/box2d.wasm.wasm +0 -0
package/src/engineParticles.js
CHANGED
|
@@ -1,533 +1,584 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LittleJS Particle System
|
|
3
|
-
* - Fast and flexible particle effects system
|
|
4
|
-
* - ParticleEmitter spawns and manages lightweight Particle objects
|
|
5
|
-
* - Particles support color gradients, fading, rotation, and scaling
|
|
6
|
-
* - Physics simulation with velocity, gravity, and damping
|
|
7
|
-
* - Collision detection with tile layers
|
|
8
|
-
* - Additive blending for glowing effects
|
|
9
|
-
* - Cone-based emission with randomization
|
|
10
|
-
* - Particle design tool available for easy emitter creation
|
|
11
|
-
* @namespace Particles
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
'use strict';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @callback ParticleCallback - Function that processes a particle
|
|
18
|
-
* @param {Particle} particle
|
|
19
|
-
* @memberof Particles
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @callback ParticleCollideCallback -
|
|
24
|
-
* @param {Particle} particle
|
|
25
|
-
* @param {number} tileData
|
|
26
|
-
* @param {Vector2} pos
|
|
27
|
-
* @
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*
|
|
33
|
-
* @
|
|
34
|
-
* @
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* let
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* rgb(1,1,1,
|
|
43
|
-
* 1,
|
|
44
|
-
* .
|
|
45
|
-
* .
|
|
46
|
-
*
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
* @param {
|
|
53
|
-
* @param {number
|
|
54
|
-
* @param {number}
|
|
55
|
-
* @param {number} [
|
|
56
|
-
* @param {number} [
|
|
57
|
-
* @param {
|
|
58
|
-
* @param {
|
|
59
|
-
* @param {Color} [
|
|
60
|
-
* @param {Color} [
|
|
61
|
-
* @param {Color} [
|
|
62
|
-
* @param {
|
|
63
|
-
* @param {number} [
|
|
64
|
-
* @param {number} [
|
|
65
|
-
* @param {number} [
|
|
66
|
-
* @param {number} [
|
|
67
|
-
* @param {number} [
|
|
68
|
-
* @param {number} [
|
|
69
|
-
* @param {number} [
|
|
70
|
-
* @param {number} [
|
|
71
|
-
* @param {number} [
|
|
72
|
-
* @param {number} [
|
|
73
|
-
* @param {
|
|
74
|
-
* @param {boolean} [
|
|
75
|
-
* @param {boolean} [
|
|
76
|
-
* @param {
|
|
77
|
-
* @param {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
this.
|
|
171
|
-
/** @property {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
this.
|
|
177
|
-
/** @property {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
{
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
this.
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
particle
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
this.
|
|
407
|
-
|
|
408
|
-
this.
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
this.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS Particle System
|
|
3
|
+
* - Fast and flexible particle effects system
|
|
4
|
+
* - ParticleEmitter spawns and manages lightweight Particle objects
|
|
5
|
+
* - Particles support color gradients, fading, rotation, and scaling
|
|
6
|
+
* - Physics simulation with velocity, gravity, and damping
|
|
7
|
+
* - Collision detection with tile layers
|
|
8
|
+
* - Additive blending for glowing effects
|
|
9
|
+
* - Cone-based emission with randomization
|
|
10
|
+
* - Particle design tool available for easy emitter creation
|
|
11
|
+
* @namespace Particles
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @callback ParticleCallback - Function that processes a particle
|
|
18
|
+
* @param {Particle} particle
|
|
19
|
+
* @memberof Particles
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @callback ParticleCollideCallback - Decides whether a particle stops at a tile, it is a filter rather than a notice
|
|
24
|
+
* @param {Particle} particle
|
|
25
|
+
* @param {number} tileData
|
|
26
|
+
* @param {Vector2} pos
|
|
27
|
+
* @return {boolean} - true to stop the particle there; a callback that returns nothing lets it pass through
|
|
28
|
+
* @memberof Particles
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Particle Emitter - Spawns particles with the given settings
|
|
33
|
+
* @extends EngineObject
|
|
34
|
+
* @memberof Particles
|
|
35
|
+
* @example
|
|
36
|
+
* // create a particle emitter
|
|
37
|
+
* let pos = vec2(2,3);
|
|
38
|
+
* let particleEmitter = new ParticleEmitter
|
|
39
|
+
* (
|
|
40
|
+
* pos, 0, 1, 0, 500, PI, // pos, angle, emitSize, emitTime, emitRate, emitCone
|
|
41
|
+
* tile(0, 16), // tileInfo
|
|
42
|
+
* rgb(1,1,1,1), rgb(0,0,0,1), // colorStartA, colorStartB
|
|
43
|
+
* rgb(1,1,1,0), rgb(0,0,0,0), // colorEndA, colorEndB
|
|
44
|
+
* 1, .2, .2, .1, .05, // particleTime, sizeStart, sizeEnd, particleSpeed, particleAngleSpeed
|
|
45
|
+
* .99, 1, 1, PI, .05, // damping, angleDamping, gravityScale, particleCone, fadeRate
|
|
46
|
+
* .5, 1 // randomness, collide
|
|
47
|
+
* );
|
|
48
|
+
*/
|
|
49
|
+
class ParticleEmitter extends EngineObject
|
|
50
|
+
{
|
|
51
|
+
/** Create a particle system with the given settings
|
|
52
|
+
* @param {Vector2} pos - World space position of the emitter
|
|
53
|
+
* @param {number} [angle] - Angle to emit the particles
|
|
54
|
+
* @param {number|Vector2} [emitSize] - World space size of the emitter (float for circle diameter, vec2 for rect)
|
|
55
|
+
* @param {number} [emitTime] - How long to stay alive (0 is forever)
|
|
56
|
+
* @param {number} [emitRate] - How many particles per second to spawn, does not emit if 0
|
|
57
|
+
* @param {number} [emitConeAngle=PI] - Half angle of the cone around the emitter's angle that particles move along, PI is every direction
|
|
58
|
+
* @param {TileInfo} [tileInfo] - Tile info to render particles (undefined is untextured)
|
|
59
|
+
* @param {Color} [colorStartA=WHITE] - Color at start of life 1, randomized between start colors
|
|
60
|
+
* @param {Color} [colorStartB=WHITE] - Color at start of life 2, randomized between start colors
|
|
61
|
+
* @param {Color} [colorEndA=CLEAR_WHITE] - Color at end of life 1, randomized between end colors
|
|
62
|
+
* @param {Color} [colorEndB=CLEAR_WHITE] - Color at end of life 2, randomized between end colors
|
|
63
|
+
* @param {number} [particleTime] - How long particles live
|
|
64
|
+
* @param {number} [sizeStart] - How big are particles at start
|
|
65
|
+
* @param {number} [sizeEnd] - How big are particles at end
|
|
66
|
+
* @param {number} [speed] - How fast are particles when spawned, in world units per frame (at 60fps, so multiply units/sec by 1/60)
|
|
67
|
+
* @param {number} [angleSpeed] - How fast are particles rotating, in radians per frame (at 60fps)
|
|
68
|
+
* @param {number} [damping] - How much to dampen particle speed, per-frame velocity multiplier (1 = no damping, .9 = lose 10% speed each frame)
|
|
69
|
+
* @param {number} [angleDamping] - How much to dampen particle angular speed, per-frame multiplier (1 = no damping)
|
|
70
|
+
* @param {number} [gravityScale] - How much gravity effect particles
|
|
71
|
+
* @param {number} [particleConeAngle] - Half angle each side of the emitter's angle for a particle's start angle, PI is any angle
|
|
72
|
+
* @param {number} [fadeRate] - Fraction of life spent fading: half at fade-in (start), half at fade-out (end). e.g. .2 = 10% fade-in, 80% full opacity, 10% fade-out
|
|
73
|
+
* @param {number} [randomness] - Apply extra randomness percent
|
|
74
|
+
* @param {boolean} [collideLevel] - Do particles collide with the level's tiles, world space emitters only
|
|
75
|
+
* @param {boolean} [additive] - Should particles use additive blend
|
|
76
|
+
* @param {boolean} [randomColorLinear] - Should color be randomized linearly or across each component
|
|
77
|
+
* @param {number} [renderOrder] - Render order for particles (additive is above other stuff by default)
|
|
78
|
+
* @param {boolean} [localSpace] - Should it be in local space of emitter (world space is default)
|
|
79
|
+
*/
|
|
80
|
+
constructor
|
|
81
|
+
(
|
|
82
|
+
pos,
|
|
83
|
+
angle,
|
|
84
|
+
emitSize = 0,
|
|
85
|
+
emitTime = 0,
|
|
86
|
+
emitRate = 100,
|
|
87
|
+
emitConeAngle = PI,
|
|
88
|
+
tileInfo,
|
|
89
|
+
colorStartA = WHITE,
|
|
90
|
+
colorStartB = WHITE,
|
|
91
|
+
colorEndA = CLEAR_WHITE,
|
|
92
|
+
colorEndB = CLEAR_WHITE,
|
|
93
|
+
particleTime = .5,
|
|
94
|
+
sizeStart = .1,
|
|
95
|
+
sizeEnd = 1,
|
|
96
|
+
speed = .1,
|
|
97
|
+
angleSpeed = .05,
|
|
98
|
+
damping = 1,
|
|
99
|
+
angleDamping = 1,
|
|
100
|
+
gravityScale = 0,
|
|
101
|
+
particleConeAngle = PI,
|
|
102
|
+
fadeRate = .1,
|
|
103
|
+
randomness = .2,
|
|
104
|
+
collideLevel = false,
|
|
105
|
+
additive = false,
|
|
106
|
+
randomColorLinear = true,
|
|
107
|
+
renderOrder = additive ? 1e9 : 0,
|
|
108
|
+
localSpace = false
|
|
109
|
+
)
|
|
110
|
+
{
|
|
111
|
+
super(pos, vec2(), tileInfo, angle, undefined, renderOrder);
|
|
112
|
+
|
|
113
|
+
// emitter settings
|
|
114
|
+
/** @property {boolean} - Should particles be emitted in a circle */
|
|
115
|
+
this.emitCircle = typeof emitSize === 'number';
|
|
116
|
+
/** @property {Vector2} - World space size of the emitter, x is the diameter when emitCircle is set */
|
|
117
|
+
this.emitSize = typeof emitSize === 'number' ? vec2(emitSize) : emitSize.copy();
|
|
118
|
+
/** @property {number} - How long to stay alive (0 is forever) */
|
|
119
|
+
this.emitTime = emitTime;
|
|
120
|
+
/** @property {number} - How many particles per second to spawn, does not emit if 0 */
|
|
121
|
+
this.emitRate = emitRate;
|
|
122
|
+
/** @property {number} - Half angle of the cone around the emitter's angle that particles move along, PI is every direction */
|
|
123
|
+
this.emitConeAngle = emitConeAngle;
|
|
124
|
+
|
|
125
|
+
// color settings
|
|
126
|
+
/** @property {Color} - Color at start of life 1, randomized between start colors */
|
|
127
|
+
this.colorStartA = colorStartA.copy();
|
|
128
|
+
/** @property {Color} - Color at start of life 2, randomized between start colors */
|
|
129
|
+
this.colorStartB = colorStartB.copy();
|
|
130
|
+
/** @property {Color} - Color at end of life 1, randomized between end colors */
|
|
131
|
+
this.colorEndA = colorEndA.copy();
|
|
132
|
+
/** @property {Color} - Color at end of life 2, randomized between end colors */
|
|
133
|
+
this.colorEndB = colorEndB.copy();
|
|
134
|
+
/** @property {boolean} - Should color be randomized linearly or across each component */
|
|
135
|
+
this.randomColorLinear = randomColorLinear;
|
|
136
|
+
|
|
137
|
+
// particle settings
|
|
138
|
+
/** @property {number} - How long particles live */
|
|
139
|
+
this.particleTime = particleTime;
|
|
140
|
+
/** @property {number} - How big are particles at start */
|
|
141
|
+
this.sizeStart = sizeStart;
|
|
142
|
+
/** @property {number} - How big are particles at end */
|
|
143
|
+
this.sizeEnd = sizeEnd;
|
|
144
|
+
/** @property {number} - Particle speed when spawned, in world units per frame (at 60fps) */
|
|
145
|
+
this.speed = speed;
|
|
146
|
+
/** @property {number} - Particle angular speed when spawned, in radians per frame (at 60fps) */
|
|
147
|
+
this.angleSpeed = angleSpeed;
|
|
148
|
+
/** @property {number} - Per-frame velocity multiplier (1 = no damping, .9 = lose 10% speed each frame) */
|
|
149
|
+
this.damping = damping;
|
|
150
|
+
/** @property {number} - Per-frame angular velocity multiplier (1 = no damping) */
|
|
151
|
+
this.angleDamping = angleDamping;
|
|
152
|
+
/** @property {number} - How much gravity affects particles */
|
|
153
|
+
this.gravityScale = gravityScale;
|
|
154
|
+
/** @property {number} - Half angle each side of the emitter's angle for a particle's start angle, PI is any angle */
|
|
155
|
+
this.particleConeAngle = particleConeAngle;
|
|
156
|
+
/** @property {number} - Fraction of life spent fading, split half at start and half at end (e.g. .2 = 10% fade-in + 10% fade-out) */
|
|
157
|
+
this.fadeRate = fadeRate;
|
|
158
|
+
/** @property {number} - Apply extra randomness percent */
|
|
159
|
+
this.randomness = randomness;
|
|
160
|
+
/** @property {boolean} - Do particles collide with the level's tiles */
|
|
161
|
+
this.collideLevel = collideLevel;
|
|
162
|
+
/** @property {boolean} - Should particles use additive blend */
|
|
163
|
+
this.additive = additive;
|
|
164
|
+
/** @property {boolean} - Should it be in local space of emitter */
|
|
165
|
+
this.localSpace = localSpace;
|
|
166
|
+
/** @property {number} - If non zero the particle is drawn as a trail, stretched in the direction of velocity */
|
|
167
|
+
this.trailScale = 0;
|
|
168
|
+
/** @property {ParticleCallback|undefined} - Callback when particle is created
|
|
169
|
+
* @type {ParticleCallback|undefined} */
|
|
170
|
+
this.particleCreateCallback = undefined;
|
|
171
|
+
/** @property {ParticleCallback|undefined} - Callback when particle is destroyed
|
|
172
|
+
* @type {ParticleCallback|undefined} */
|
|
173
|
+
this.particleDestroyCallback = undefined;
|
|
174
|
+
/** @property {ParticleCollideCallback|undefined} - Callback when particle collides
|
|
175
|
+
* @type {ParticleCollideCallback|undefined} */
|
|
176
|
+
this.particleCollideCallback = undefined;
|
|
177
|
+
/** @property {ParticleCallback|undefined} - Callback each time a particle updates, after it moves
|
|
178
|
+
* @type {ParticleCallback|undefined} */
|
|
179
|
+
this.particleUpdateCallback = undefined;
|
|
180
|
+
/** @property {number} - Percentage of velocity to pass to particles (0-1) */
|
|
181
|
+
this.velocityInheritance = 0;
|
|
182
|
+
/** @property {number} - Particles owed to the emit rate, starts at one so the first comes out at once */
|
|
183
|
+
this.emitTimeBuffer = 1;
|
|
184
|
+
/** @property {Array<Particle>} - Array of particles for this emitter
|
|
185
|
+
* @type {Array<Particle>} */
|
|
186
|
+
this.particles = [];
|
|
187
|
+
|
|
188
|
+
// track previous position and angle, set on the first update once a parent has placed the emitter
|
|
189
|
+
/** @type {Vector2|undefined} */
|
|
190
|
+
this.previousPos = undefined;
|
|
191
|
+
this.previousAngle = this.angle;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Update the emitter to spawn particles, called automatically by engine once each frame */
|
|
195
|
+
update()
|
|
196
|
+
{
|
|
197
|
+
// physics sanity checks
|
|
198
|
+
ASSERT(this.angleDamping >= 0 && this.angleDamping <= 1, 'angleDamping must be 0 to 1, the fraction kept each frame');
|
|
199
|
+
ASSERT(this.damping >= 0 && this.damping <= 1, 'damping must be 0 to 1, the fraction of velocity kept each frame');
|
|
200
|
+
|
|
201
|
+
if (!this.previousPos)
|
|
202
|
+
{
|
|
203
|
+
// first update, addChild moves an emitter after it is made
|
|
204
|
+
this.previousPos = this.pos.copy();
|
|
205
|
+
this.previousAngle = this.angle;
|
|
206
|
+
}
|
|
207
|
+
if (this.velocityInheritance)
|
|
208
|
+
{
|
|
209
|
+
// pass emitter velocity to particles
|
|
210
|
+
const p = this.velocityInheritance;
|
|
211
|
+
this.velocity.x = p * (this.pos.x - this.previousPos.x);
|
|
212
|
+
this.velocity.y = p * (this.pos.y - this.previousPos.y);
|
|
213
|
+
this.angleVelocity = p * (this.angle - this.previousAngle);
|
|
214
|
+
}
|
|
215
|
+
// tracked even while velocityInheritance is off, so turning it on does not jump
|
|
216
|
+
this.previousAngle = this.angle;
|
|
217
|
+
this.previousPos.x = this.pos.x;
|
|
218
|
+
this.previousPos.y = this.pos.y;
|
|
219
|
+
|
|
220
|
+
// update emitter
|
|
221
|
+
if (this.isActive())
|
|
222
|
+
{
|
|
223
|
+
// emit particles
|
|
224
|
+
const rate = this.emitRate * particleEmitRateScale;
|
|
225
|
+
if (rate > 0 && rate < Infinity)
|
|
226
|
+
{
|
|
227
|
+
// counted in particles, so a new rate applies at once
|
|
228
|
+
this.emitTimeBuffer += rate * timeDelta;
|
|
229
|
+
for (; this.emitTimeBuffer >= 1; --this.emitTimeBuffer)
|
|
230
|
+
this.emitParticle();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
else if (this.particles.length === 0)
|
|
234
|
+
this.destroy(true);
|
|
235
|
+
|
|
236
|
+
// a local space particle is placed relative to the emitter, but the tile collision is in the world
|
|
237
|
+
ASSERT(!this.localSpace || !this.collideLevel, 'local space particles cannot collide with the level, turn one of them off');
|
|
238
|
+
|
|
239
|
+
// update and remove destroyed particles in place to avoid per-frame array allocation
|
|
240
|
+
const particles = this.particles;
|
|
241
|
+
let alive = 0;
|
|
242
|
+
for (let i = 0; i < particles.length; ++i)
|
|
243
|
+
{
|
|
244
|
+
const p = particles[i];
|
|
245
|
+
p.update();
|
|
246
|
+
if (!p.destroyed) particles[alive++] = p;
|
|
247
|
+
}
|
|
248
|
+
particles.length = alive;
|
|
249
|
+
|
|
250
|
+
if (debugParticles)
|
|
251
|
+
{
|
|
252
|
+
// show emitter bounds
|
|
253
|
+
if (this.emitCircle)
|
|
254
|
+
debugCircle(this.pos, this.emitSize.x, '#0f0', 0, false, false);
|
|
255
|
+
else
|
|
256
|
+
debugRect(this.pos, this.emitSize, '#0f0', 0, this.angle, false, false);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Spawn one particle
|
|
261
|
+
* @return {Particle} */
|
|
262
|
+
emitParticle()
|
|
263
|
+
{
|
|
264
|
+
// spawn a particle
|
|
265
|
+
let pos = this.emitCircle ? // check if circle emitter
|
|
266
|
+
randInCircle(this.emitSize.x/2) // circle emitter
|
|
267
|
+
: vec2(rand(-.5,.5), rand(-.5,.5)) // box emitter
|
|
268
|
+
.multiply(this.emitSize);
|
|
269
|
+
let angle = rand(this.particleConeAngle, -this.particleConeAngle);
|
|
270
|
+
if (!this.localSpace)
|
|
271
|
+
{
|
|
272
|
+
// into the world: a local space particle is turned with the emitter when it draws instead
|
|
273
|
+
this.emitCircle || (pos = pos.rotate(this.angle));
|
|
274
|
+
pos.x += this.pos.x;
|
|
275
|
+
pos.y += this.pos.y;
|
|
276
|
+
angle += this.angle;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// randomness scales each parameter by a percentage
|
|
280
|
+
const randomness = this.randomness;
|
|
281
|
+
const randomizeScale = (v)=> v + v*rand(randomness, -randomness);
|
|
282
|
+
|
|
283
|
+
// randomize particle settings
|
|
284
|
+
const particleTime = randomizeScale(this.particleTime);
|
|
285
|
+
const sizeStart = randomizeScale(this.sizeStart);
|
|
286
|
+
const sizeEnd = randomizeScale(this.sizeEnd);
|
|
287
|
+
const speed = randomizeScale(this.speed);
|
|
288
|
+
const angleSpeed = randomizeScale(this.angleSpeed) * randSign();
|
|
289
|
+
const coneAngle = rand(this.emitConeAngle, -this.emitConeAngle);
|
|
290
|
+
const colorStart = randColor(this.colorStartA, this.colorStartB, this.randomColorLinear);
|
|
291
|
+
const colorEnd = randColor(this.colorEndA, this.colorEndB, this.randomColorLinear);
|
|
292
|
+
const velocityAngle = this.localSpace ? coneAngle : this.angle + coneAngle;
|
|
293
|
+
|
|
294
|
+
// build particle
|
|
295
|
+
const velocity = vec2(speed*sin(velocityAngle), speed*cos(velocityAngle));
|
|
296
|
+
let angleVelocity = angleSpeed;
|
|
297
|
+
if (!this.localSpace && this.velocityInheritance > 0)
|
|
298
|
+
{
|
|
299
|
+
// apply emitter velocity to particle
|
|
300
|
+
velocity.x += this.velocity.x;
|
|
301
|
+
velocity.y += this.velocity.y;
|
|
302
|
+
angleVelocity += this.angleVelocity;
|
|
303
|
+
}
|
|
304
|
+
const particle = new Particle(this, pos, angle, colorStart, colorEnd, particleTime, sizeStart, sizeEnd, velocity, angleVelocity);
|
|
305
|
+
this.particles.push(particle);
|
|
306
|
+
|
|
307
|
+
// call particle create callback
|
|
308
|
+
this.particleCreateCallback?.(particle);
|
|
309
|
+
|
|
310
|
+
// return the newly created particle
|
|
311
|
+
return particle;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Particle emitters do not have physics */
|
|
315
|
+
updatePhysics() {}
|
|
316
|
+
|
|
317
|
+
/** Render all particles for this emitter */
|
|
318
|
+
render()
|
|
319
|
+
{
|
|
320
|
+
// render all particles, the blend switched once for them all, not around each one
|
|
321
|
+
this.additive && setAdditiveBlendMode();
|
|
322
|
+
for (const particle of this.particles)
|
|
323
|
+
particle.render();
|
|
324
|
+
this.additive && setAdditiveBlendMode(false);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** is emitter actively spawning */
|
|
328
|
+
isActive() { return !this.emitTime || this.getAliveTime() < this.emitTime; }
|
|
329
|
+
|
|
330
|
+
/** Destroy the particle emitter
|
|
331
|
+
* @param {boolean} [immediate] - true removes attached effects like particle emitters at once, false lets them finish first */
|
|
332
|
+
destroy(immediate=false)
|
|
333
|
+
{
|
|
334
|
+
if (this.destroyed) return;
|
|
335
|
+
|
|
336
|
+
super.destroy(immediate);
|
|
337
|
+
if (!immediate && this.particles.length > 0)
|
|
338
|
+
{
|
|
339
|
+
// wait for particles to die off
|
|
340
|
+
this.destroyed = false;
|
|
341
|
+
this.emitTime = -1;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
347
|
+
// scratch vectors reused by Particle.render and the tile collision to avoid per-frame allocations
|
|
348
|
+
const particleDrawPos = new Vector2, particleDrawSize = new Vector2, particleCollidePos = new Vector2;
|
|
349
|
+
|
|
350
|
+
// tests if a particle collides with tiles at x, y, through its emitter's collide callback if it has one
|
|
351
|
+
function particleCollideTest(particle, collideCallback, x, y)
|
|
352
|
+
{
|
|
353
|
+
const data = tileCollisionGetData(particleCollidePos.set(x, y));
|
|
354
|
+
if (!collideCallback)
|
|
355
|
+
return data > 0;
|
|
356
|
+
|
|
357
|
+
// the callback gets its own vector, made only when there is a tile to decide on
|
|
358
|
+
return !!data && !!collideCallback(particle, data, vec2(x, y));
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Particle Object - Created automatically by Particle Emitters
|
|
363
|
+
* @memberof Particles
|
|
364
|
+
*/
|
|
365
|
+
class Particle
|
|
366
|
+
{
|
|
367
|
+
/**
|
|
368
|
+
* Create a particle with the passed in settings
|
|
369
|
+
* Typically this is created automatically by a ParticleEmitter
|
|
370
|
+
* @param {ParticleEmitter} emitter - The emitter that created this particle
|
|
371
|
+
* @param {Vector2} pos - World or local space position
|
|
372
|
+
* @param {number} angle - Angle of the particle
|
|
373
|
+
* @param {Color} colorStart - Color at start of life
|
|
374
|
+
* @param {Color} colorEnd - Color at end of life
|
|
375
|
+
* @param {number} lifeTime - How long to live for
|
|
376
|
+
* @param {number} sizeStart - Size at start of life
|
|
377
|
+
* @param {number} sizeEnd - Size at end of life
|
|
378
|
+
* @param {Vector2} [velocity] - Velocity of the particle
|
|
379
|
+
* @param {number} [angleVelocity] - Angular speed of the particle
|
|
380
|
+
*/
|
|
381
|
+
constructor(emitter, pos, angle, colorStart, colorEnd, lifeTime, sizeStart, sizeEnd, velocity = vec2(), angleVelocity = 0)
|
|
382
|
+
{
|
|
383
|
+
/** @property {ParticleEmitter} - The emitter this particle came from */
|
|
384
|
+
this.emitter = emitter;
|
|
385
|
+
/** @property {Vector2} - Position, world space or local to the emitter when localSpace is set */
|
|
386
|
+
this.pos = pos;
|
|
387
|
+
/** @property {number} - Angle in radians */
|
|
388
|
+
this.angle = angle;
|
|
389
|
+
/** @property {Vector2} - Current size, updated as it renders */
|
|
390
|
+
this.size = vec2(sizeStart);
|
|
391
|
+
/** @property {Color} - Current color, updated as it renders */
|
|
392
|
+
this.color = colorStart.copy();
|
|
393
|
+
/** @property {Color} - Color at start of life */
|
|
394
|
+
this.colorStart = colorStart;
|
|
395
|
+
/** @property {Color} - Color at end of life */
|
|
396
|
+
this.colorEnd = colorEnd;
|
|
397
|
+
/** @property {number} - How long this particle lives for */
|
|
398
|
+
this.lifeTime = lifeTime;
|
|
399
|
+
/** @property {number} - Size at start of life */
|
|
400
|
+
this.sizeStart = sizeStart;
|
|
401
|
+
/** @property {number} - Size at end of life */
|
|
402
|
+
this.sizeEnd = sizeEnd;
|
|
403
|
+
/** @property {Vector2} - Velocity in world units per frame (at 60fps) */
|
|
404
|
+
this.velocity = velocity;
|
|
405
|
+
/** @property {number} - Angular speed in radians per frame (at 60fps) */
|
|
406
|
+
this.angleVelocity = angleVelocity;
|
|
407
|
+
/** @property {number} - Engine time it was made at */
|
|
408
|
+
this.spawnTime = time;
|
|
409
|
+
/** @property {boolean} - If true the tile is flipped along the x axis */
|
|
410
|
+
this.mirror = randBool();
|
|
411
|
+
/** @property {EngineObject|undefined} - Tile layer it last landed on, undefined in the air
|
|
412
|
+
* @type {EngineObject|undefined} */
|
|
413
|
+
this.groundObject = undefined;
|
|
414
|
+
/** @property {boolean} - Has this particle been destroyed */
|
|
415
|
+
this.destroyed = false;
|
|
416
|
+
/** @property {TileInfo|undefined} - The emitter's tile, undefined for an untextured one
|
|
417
|
+
* @type {TileInfo|undefined} */
|
|
418
|
+
this.tileInfo = emitter.tileInfo;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Update the particle */
|
|
422
|
+
update()
|
|
423
|
+
{
|
|
424
|
+
if (this.destroyed) return; // gone already, destroyed by the game this frame
|
|
425
|
+
|
|
426
|
+
// emitter properties
|
|
427
|
+
const emitter = this.emitter;
|
|
428
|
+
const damping = emitter.damping;
|
|
429
|
+
const angleDamping = emitter.angleDamping;
|
|
430
|
+
const restitution = emitter.restitution;
|
|
431
|
+
const friction = emitter.friction;
|
|
432
|
+
const gravityScale = emitter.gravityScale;
|
|
433
|
+
const collideLevel = emitter.collideLevel;
|
|
434
|
+
const collideCallback = emitter.particleCollideCallback;
|
|
435
|
+
const updateCallback = emitter.particleUpdateCallback;
|
|
436
|
+
|
|
437
|
+
// destroy particle when its time runs out
|
|
438
|
+
if (this.lifeTime <= 0 || time - this.spawnTime > this.lifeTime) // no lifetime is gone at once, not never
|
|
439
|
+
{
|
|
440
|
+
this.destroy();
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// apply physics; only the tile collision needs where the particle was
|
|
445
|
+
const solve = enablePhysicsSolver && collideLevel;
|
|
446
|
+
const oldX = this.pos.x, oldY = this.pos.y;
|
|
447
|
+
let gravityX = gravity.x * gravityScale, gravityY = gravity.y * gravityScale;
|
|
448
|
+
if (emitter.localSpace && emitter.angle)
|
|
449
|
+
{
|
|
450
|
+
// world gravity turned into the emitter's space, the render turns it back
|
|
451
|
+
const c = cos(emitter.angle), s = sin(emitter.angle);
|
|
452
|
+
[gravityX, gravityY] = [gravityX*c - gravityY*s, gravityX*s + gravityY*c];
|
|
453
|
+
}
|
|
454
|
+
this.velocity.x = this.velocity.x * damping + gravityX;
|
|
455
|
+
this.velocity.y = this.velocity.y * damping + gravityY;
|
|
456
|
+
if (solve)
|
|
457
|
+
{
|
|
458
|
+
// apply max circular speed to prevent going through collision, before the move it protects
|
|
459
|
+
const length2 = this.velocity.lengthSquared();
|
|
460
|
+
if (length2 > objectMaxSpeed*objectMaxSpeed)
|
|
461
|
+
{
|
|
462
|
+
const s = objectMaxSpeed / length2**.5;
|
|
463
|
+
this.velocity.x *= s;
|
|
464
|
+
this.velocity.y *= s;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
this.pos.x += this.velocity.x;
|
|
468
|
+
this.pos.y += this.velocity.y;
|
|
469
|
+
this.angle += this.angleVelocity *= angleDamping;
|
|
470
|
+
|
|
471
|
+
// don't do collision if solver disabled
|
|
472
|
+
if (!solve)
|
|
473
|
+
{
|
|
474
|
+
updateCallback?.(this);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// check collision against tiles
|
|
479
|
+
this.groundObject = undefined;
|
|
480
|
+
if (particleCollideTest(this, collideCallback, this.pos.x, this.pos.y))
|
|
481
|
+
{
|
|
482
|
+
// if already was stuck in collision, don't do anything
|
|
483
|
+
const hitLayer = tileCollisionTest(this.pos);
|
|
484
|
+
if (!particleCollideTest(this, collideCallback, oldX, oldY))
|
|
485
|
+
{
|
|
486
|
+
// test which side we bounced off (or both if a corner)
|
|
487
|
+
const isBlockedX = particleCollideTest(this, collideCallback, this.pos.x, oldY);
|
|
488
|
+
const isBlockedY = particleCollideTest(this, collideCallback, oldX, this.pos.y);
|
|
489
|
+
// collide callback may hit where the layer test does not, so hitLayer can be undefined
|
|
490
|
+
const hitRestitution = hitLayer ? max(restitution, hitLayer.restitution) : restitution;
|
|
491
|
+
const hitFriction = hitLayer ? max(friction, hitLayer.friction) : friction;
|
|
492
|
+
if (isBlockedX)
|
|
493
|
+
{
|
|
494
|
+
// move to previous X position and bounce
|
|
495
|
+
this.pos.x = oldX;
|
|
496
|
+
this.velocity.x *= -hitRestitution;
|
|
497
|
+
this.velocity.y *= hitFriction;
|
|
498
|
+
}
|
|
499
|
+
if (isBlockedY || !isBlockedX)
|
|
500
|
+
{
|
|
501
|
+
const wasFalling = this.velocity.y < 0 && gravity.y < 0 || this.velocity.y > 0 && gravity.y > 0;
|
|
502
|
+
if (wasFalling)
|
|
503
|
+
this.groundObject = hitLayer;
|
|
504
|
+
|
|
505
|
+
// move to previous Y position and bounce
|
|
506
|
+
this.pos.y = oldY;
|
|
507
|
+
this.velocity.y *= -hitRestitution;
|
|
508
|
+
this.velocity.x *= hitFriction;
|
|
509
|
+
}
|
|
510
|
+
debugPhysics && debugRect(this.pos, this.size, '#f00', 0, 0, false, false);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// a collide callback may have destroyed it
|
|
515
|
+
this.destroyed || updateCallback?.(this);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/** Destroy this particle, once: a second call does nothing
|
|
519
|
+
*/
|
|
520
|
+
destroy()
|
|
521
|
+
{
|
|
522
|
+
if (this.destroyed) return;
|
|
523
|
+
const destroyCallback = this.emitter.particleDestroyCallback;
|
|
524
|
+
const c = this.colorEnd;
|
|
525
|
+
this.color.set(c.r, c.g, c.b, c.a);
|
|
526
|
+
this.size.set(this.sizeEnd, this.sizeEnd);
|
|
527
|
+
this.destroyed = true;
|
|
528
|
+
destroyCallback?.(this);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** Render the particle, automatically called each frame */
|
|
532
|
+
render()
|
|
533
|
+
{
|
|
534
|
+
// emitter properties
|
|
535
|
+
const emitter = this.emitter;
|
|
536
|
+
const localSpace = emitter.localSpace;
|
|
537
|
+
const additive = emitter.additive && !glAdditive; // switched here only for a particle drawn on its own
|
|
538
|
+
const trailScale = emitter.trailScale;
|
|
539
|
+
const fadeRate = emitter.fadeRate / 2;
|
|
540
|
+
|
|
541
|
+
// lerp color and size
|
|
542
|
+
const p1 = this.lifeTime > 0 ? min((time - this.spawnTime) / this.lifeTime, 1) : 1, p2 = 1-p1;
|
|
543
|
+
const sizeNow = p2 * this.sizeStart + p1 * this.sizeEnd;
|
|
544
|
+
this.size.set(sizeNow, sizeNow); // kept current for callbacks, drawn from the scratch since a trail stretches it
|
|
545
|
+
const size = particleDrawSize.set(sizeNow, sizeNow);
|
|
546
|
+
const alphaFade = p1 < fadeRate ? p1/fadeRate :
|
|
547
|
+
p1 > 1-fadeRate ? (1-p1)/fadeRate : 1;
|
|
548
|
+
this.color.r = p2 * this.colorStart.r + p1 * this.colorEnd.r;
|
|
549
|
+
this.color.g = p2 * this.colorStart.g + p1 * this.colorEnd.g;
|
|
550
|
+
this.color.b = p2 * this.colorStart.b + p1 * this.colorEnd.b;
|
|
551
|
+
this.color.a = (p2 * this.colorStart.a + p1 * this.colorEnd.a) * alphaFade;
|
|
552
|
+
|
|
553
|
+
// update the position and angle for drawing
|
|
554
|
+
const pos = particleDrawPos.set(this.pos.x, this.pos.y);
|
|
555
|
+
let angle = this.angle;
|
|
556
|
+
if (localSpace)
|
|
557
|
+
{
|
|
558
|
+
// in local space of emitter
|
|
559
|
+
const a = emitter.angle;
|
|
560
|
+
const c = cos(-a), s = sin(-a);
|
|
561
|
+
pos.set(emitter.pos.x + pos.x*c - pos.y*s,
|
|
562
|
+
emitter.pos.y + pos.x*s + pos.y*c);
|
|
563
|
+
angle += a;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// draw the particle
|
|
567
|
+
additive && setAdditiveBlendMode();
|
|
568
|
+
if (trailScale)
|
|
569
|
+
{
|
|
570
|
+
// trail style particles stretch in the direction of motion, and draw as they are at rest
|
|
571
|
+
const velocity = localSpace ?
|
|
572
|
+
this.velocity.rotate(emitter.angle) : this.velocity;
|
|
573
|
+
const speed = velocity.length();
|
|
574
|
+
if (speed)
|
|
575
|
+
{
|
|
576
|
+
size.y = max(size.x, speed * trailScale);
|
|
577
|
+
angle = atan2(velocity.x, velocity.y);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
drawTile(pos, size, this.tileInfo, this.color, angle, this.mirror, undefined, glEnable, false);
|
|
581
|
+
additive && setAdditiveBlendMode(false);
|
|
582
|
+
debugParticles && debugRect(pos, size, '#f005', 0, angle, false, false);
|
|
583
|
+
}
|
|
533
584
|
}
|