@basmilius/sparkle 2.1.0 → 2.2.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.
Files changed (107) hide show
  1. package/dist/index.d.mts +306 -459
  2. package/dist/index.d.mts.map +1 -1
  3. package/dist/index.mjs +1106 -848
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +6 -2
  6. package/src/aurora/index.ts +9 -3
  7. package/src/aurora/layer.ts +57 -29
  8. package/src/balloons/index.ts +9 -3
  9. package/src/balloons/layer.ts +50 -19
  10. package/src/bubbles/index.ts +9 -3
  11. package/src/bubbles/layer.ts +30 -17
  12. package/src/canvas.ts +12 -0
  13. package/src/color.ts +11 -2
  14. package/src/confetti/index.ts +15 -3
  15. package/src/confetti/layer.ts +8 -5
  16. package/src/confetti/particle.ts +12 -11
  17. package/src/donuts/consts.ts +2 -2
  18. package/src/donuts/index.ts +9 -3
  19. package/src/donuts/layer.ts +43 -12
  20. package/src/effect.ts +107 -0
  21. package/src/fade.ts +87 -0
  22. package/src/fireflies/index.ts +9 -3
  23. package/src/fireflies/layer.ts +26 -9
  24. package/src/fireflies/particle.ts +2 -2
  25. package/src/firepit/index.ts +9 -3
  26. package/src/firepit/layer.ts +26 -7
  27. package/src/fireworks/create-explosion.ts +237 -0
  28. package/src/fireworks/explosion.ts +1 -1
  29. package/src/fireworks/index.ts +15 -3
  30. package/src/fireworks/layer.ts +55 -304
  31. package/src/fireworks/spark.ts +2 -2
  32. package/src/fireworks/types.ts +2 -2
  33. package/src/glitter/index.ts +9 -4
  34. package/src/glitter/layer.ts +15 -7
  35. package/src/glitter/types.ts +10 -0
  36. package/src/index.ts +3 -4
  37. package/src/lanterns/index.ts +9 -4
  38. package/src/lanterns/layer.ts +22 -10
  39. package/src/lanterns/types.ts +8 -0
  40. package/src/layer.ts +13 -11
  41. package/src/leaves/index.ts +9 -4
  42. package/src/leaves/layer.ts +21 -14
  43. package/src/leaves/types.ts +9 -0
  44. package/src/lightning/index.ts +9 -4
  45. package/src/lightning/layer.ts +4 -4
  46. package/src/lightning/system.ts +3 -3
  47. package/src/lightning/types.ts +10 -2
  48. package/src/matrix/index.ts +9 -4
  49. package/src/matrix/layer.ts +15 -7
  50. package/src/matrix/types.ts +9 -0
  51. package/src/orbits/index.ts +9 -4
  52. package/src/orbits/layer.ts +51 -21
  53. package/src/orbits/types.ts +12 -1
  54. package/src/particles/index.ts +9 -3
  55. package/src/particles/layer.ts +55 -12
  56. package/src/petals/index.ts +9 -3
  57. package/src/petals/layer.ts +29 -13
  58. package/src/plasma/index.ts +9 -3
  59. package/src/plasma/layer.ts +21 -6
  60. package/src/rain/index.ts +9 -3
  61. package/src/rain/layer.ts +30 -8
  62. package/src/sandstorm/index.ts +9 -3
  63. package/src/sandstorm/layer.ts +26 -9
  64. package/src/scene.ts +201 -0
  65. package/src/shooting-stars/system.ts +26 -24
  66. package/src/shooting-stars/types.ts +2 -1
  67. package/src/simulation-canvas.ts +40 -4
  68. package/src/snow/index.ts +9 -3
  69. package/src/snow/layer.ts +24 -11
  70. package/src/sparklers/index.ts +13 -3
  71. package/src/sparklers/layer.ts +61 -15
  72. package/src/stars/index.ts +9 -3
  73. package/src/stars/layer.ts +28 -22
  74. package/src/streamers/index.ts +9 -3
  75. package/src/streamers/layer.ts +18 -6
  76. package/src/streamers/types.ts +1 -1
  77. package/src/waves/index.ts +9 -3
  78. package/src/waves/layer.ts +42 -45
  79. package/src/waves/types.ts +1 -0
  80. package/src/wormhole/index.ts +9 -3
  81. package/src/wormhole/layer.ts +22 -6
  82. package/src/aurora/simulation.ts +0 -19
  83. package/src/balloons/simulation.ts +0 -19
  84. package/src/bubbles/simulation.ts +0 -20
  85. package/src/confetti/simulation.ts +0 -27
  86. package/src/donuts/simulation.ts +0 -25
  87. package/src/fireflies/simulation.ts +0 -18
  88. package/src/firepit/simulation.ts +0 -17
  89. package/src/fireworks/simulation.ts +0 -18
  90. package/src/glitter/simulation.ts +0 -19
  91. package/src/lanterns/simulation.ts +0 -17
  92. package/src/layered.ts +0 -185
  93. package/src/leaves/simulation.ts +0 -18
  94. package/src/lightning/simulation.ts +0 -17
  95. package/src/matrix/simulation.ts +0 -18
  96. package/src/orbits/simulation.ts +0 -19
  97. package/src/particles/simulation.ts +0 -26
  98. package/src/petals/simulation.ts +0 -18
  99. package/src/plasma/simulation.ts +0 -17
  100. package/src/rain/simulation.ts +0 -21
  101. package/src/sandstorm/simulation.ts +0 -18
  102. package/src/snow/simulation.ts +0 -17
  103. package/src/sparklers/simulation.ts +0 -30
  104. package/src/stars/simulation.ts +0 -22
  105. package/src/streamers/simulation.ts +0 -16
  106. package/src/waves/simulation.ts +0 -18
  107. package/src/wormhole/simulation.ts +0 -17
@@ -1,30 +1,33 @@
1
- import { SimulationLayer } from '../layer';
1
+ import { Effect } from '../effect';
2
2
  import type { Point } from '../point';
3
3
  import { MULBERRY } from './consts';
4
+ import { createExplosion } from './create-explosion';
4
5
  import { Explosion } from './explosion';
5
6
  import { Firework } from './firework';
6
7
  import { Spark } from './spark';
7
- import { EXPLOSION_CONFIGS, type ExplosionType, type FireworkSimulationConfig, type FireworkVariant } from './types';
8
+ import { FIREWORK_VARIANTS, type FireworksConfig, type FireworkVariant } from './types';
8
9
 
9
- export class FireworkLayer extends SimulationLayer {
10
+ export class Fireworks extends Effect<FireworksConfig> {
10
11
  #explosions: Explosion[] = [];
11
12
  #fireworks: Firework[] = [];
12
13
  #sparks: Spark[] = [];
13
14
  #hue: number = 120;
14
15
  #spawnTimer: number = 0;
15
16
  #positionRandom = MULBERRY.fork();
16
- readonly #autoSpawn: boolean;
17
+ #autoSpawn: boolean;
18
+ #variants: FireworkVariant[];
17
19
  readonly #baseSize: number;
18
- readonly #scale: number;
20
+ #scale: number;
19
21
  readonly #tailWidth: number;
20
22
  #width: number = 960;
21
23
  #height: number = 540;
22
24
 
23
- constructor(config: FireworkSimulationConfig = {}) {
25
+ constructor(config: FireworksConfig = {}) {
24
26
  super();
25
27
 
26
28
  const scale = config.scale ?? 1;
27
29
  this.#autoSpawn = config.autoSpawn ?? true;
30
+ this.#variants = config.variants?.length ? [...config.variants] : [...FIREWORK_VARIANTS];
28
31
  this.#baseSize = 5 * scale;
29
32
  this.#scale = scale;
30
33
  this.#tailWidth = 2 * scale;
@@ -35,10 +38,22 @@ export class FireworkLayer extends SimulationLayer {
35
38
  this.#height = height;
36
39
  }
37
40
 
38
- fireExplosion(variant: FireworkVariant, position?: Point): void {
41
+ launch(variant: FireworkVariant, position?: Point): void {
39
42
  const pos = position ?? {x: this.#width / 2, y: this.#height * 0.4};
40
43
  this.#hue = MULBERRY.nextBetween(0, 360);
41
- this.#createExplosion(pos, this.#hue, variant);
44
+ this.#spawnExplosion(pos, this.#hue, variant);
45
+ }
46
+
47
+ configure(config: Partial<FireworksConfig>): void {
48
+ if (config.scale !== undefined) {
49
+ this.#scale = config.scale;
50
+ }
51
+ if (config.autoSpawn !== undefined) {
52
+ this.#autoSpawn = config.autoSpawn;
53
+ }
54
+ if (Array.isArray(config.variants) && config.variants.length > 0) {
55
+ this.#variants = [...config.variants];
56
+ }
42
57
  }
43
58
 
44
59
  tick(dt: number, width: number, height: number): void {
@@ -61,7 +76,10 @@ export class FireworkLayer extends SimulationLayer {
61
76
 
62
77
  for (const firework of this.#fireworks) {
63
78
  firework.tick(dt);
64
- this.#sparks.push(...firework.collectSparks());
79
+ const collected = firework.collectSparks();
80
+ for (let i = 0; i < collected.length; i++) {
81
+ this.#sparks.push(collected[i]);
82
+ }
65
83
  }
66
84
 
67
85
  for (const explosion of this.#explosions) {
@@ -93,10 +111,15 @@ export class FireworkLayer extends SimulationLayer {
93
111
  }
94
112
 
95
113
  if (explosion.checkCrackle()) {
96
- for (let j = 0; j < 8; j++) {
114
+ for (let j = 0; j < 14; j++) {
115
+ const angle = MULBERRY.nextBetween(0, Math.PI * 2);
116
+ const speed = MULBERRY.nextBetween(3, 8);
117
+
97
118
  newSparks.push(new Spark(
98
119
  explosion.position,
99
- explosion.hue + MULBERRY.nextBetween(-30, 30)
120
+ explosion.hue + MULBERRY.nextBetween(-30, 30),
121
+ Math.cos(angle) * speed,
122
+ Math.sin(angle) * speed
100
123
  ));
101
124
  }
102
125
  }
@@ -105,8 +128,21 @@ export class FireworkLayer extends SimulationLayer {
105
128
  this.#explosions.push(...newExplosions);
106
129
  this.#sparks.push(...newSparks);
107
130
 
108
- this.#explosions = this.#explosions.filter(e => !e.isDead);
109
- this.#sparks = this.#sparks.filter(s => !s.isDead);
131
+ let aliveExplosions = 0;
132
+ for (let i = 0; i < this.#explosions.length; i++) {
133
+ if (!this.#explosions[i].isDead) {
134
+ this.#explosions[aliveExplosions++] = this.#explosions[i];
135
+ }
136
+ }
137
+ this.#explosions.length = aliveExplosions;
138
+
139
+ let aliveSparks = 0;
140
+ for (let i = 0; i < this.#sparks.length; i++) {
141
+ if (!this.#sparks[i].isDead) {
142
+ this.#sparks[aliveSparks++] = this.#sparks[i];
143
+ }
144
+ }
145
+ this.#sparks.length = aliveSparks;
110
146
  }
111
147
 
112
148
  draw(ctx: CanvasRenderingContext2D, width: number, height: number): void {
@@ -132,278 +168,10 @@ export class FireworkLayer extends SimulationLayer {
132
168
  ctx.globalCompositeOperation = 'source-over';
133
169
  }
134
170
 
135
- #createExplosion(position: Point, hue: number, variant?: FireworkVariant): void {
171
+ #spawnExplosion(position: Point, hue: number, variant?: FireworkVariant): void {
136
172
  const selected = variant ?? this.#pickVariant();
137
-
138
- if (selected === 'saturn') {
139
- this.#createSaturnExplosion(position, hue);
140
- return;
141
- }
142
-
143
- if (selected === 'dahlia') {
144
- this.#createDahliaExplosion(position, hue);
145
- return;
146
- }
147
-
148
- if (selected === 'heart') {
149
- this.#createHeartExplosion(position, hue);
150
- return;
151
- }
152
-
153
- if (selected === 'spiral') {
154
- this.#createSpiralExplosion(position, hue);
155
- return;
156
- }
157
-
158
- if (selected === 'flower') {
159
- this.#createFlowerExplosion(position, hue);
160
- return;
161
- }
162
-
163
- if (selected === 'concentric') {
164
- this.#createConcentricExplosion(position, hue);
165
- return;
166
- }
167
-
168
- const type: ExplosionType = selected;
169
- const config = EXPLOSION_CONFIGS[type];
170
- const particleCount = Math.floor(MULBERRY.nextBetween(config.particleCount[0], config.particleCount[1]));
171
-
172
- const effectiveHue = type === 'brocade'
173
- ? MULBERRY.nextBetween(35, 50)
174
- : hue;
175
-
176
- for (let i = 0; i < particleCount; i++) {
177
- let angle: number | undefined;
178
- let speed: number | undefined;
179
-
180
- if (type === 'ring') {
181
- angle = (i / particleCount) * Math.PI * 2;
182
- speed = MULBERRY.nextBetween(config.speed[0], config.speed[1]) * 0.5 + config.speed[0] * 0.5;
183
- } else if (type === 'palm' || type === 'horsetail') {
184
- const spread = type === 'horsetail' ? Math.PI / 8 : Math.PI / 5;
185
- angle = -Math.PI / 2 + MULBERRY.nextBetween(-spread, spread);
186
- }
187
-
188
- this.#explosions.push(new Explosion(position, effectiveHue, this.#baseSize, type, this.#scale, angle, speed));
189
- }
190
- }
191
-
192
- #createSaturnExplosion(position: Point, hue: number): void {
193
- const velocity = MULBERRY.nextBetween(4, 6);
194
- const shellCount = Math.floor(MULBERRY.nextBetween(25, 35));
195
-
196
- for (let i = 0; i < shellCount; i++) {
197
- const rad = (i / shellCount) * Math.PI * 2;
198
-
199
- this.#explosions.push(new Explosion(
200
- position,
201
- hue,
202
- this.#baseSize,
203
- 'peony',
204
- this.#scale,
205
- rad + MULBERRY.nextBetween(-0.05, 0.05),
206
- velocity + MULBERRY.nextBetween(-0.25, 0.25)
207
- ));
208
- }
209
-
210
- const fillCount = Math.floor(MULBERRY.nextBetween(40, 60));
211
-
212
- for (let i = 0; i < fillCount; i++) {
213
- const rad = MULBERRY.nextBetween(0, Math.PI * 2);
214
- const speed = velocity * MULBERRY.nextBetween(0, 1);
215
-
216
- this.#explosions.push(new Explosion(
217
- position,
218
- hue,
219
- this.#baseSize,
220
- 'peony',
221
- this.#scale,
222
- rad,
223
- speed
224
- ));
225
- }
226
-
227
- const ringRotation = MULBERRY.nextBetween(0, Math.PI * 2);
228
- const ringCount = Math.floor(MULBERRY.nextBetween(40, 55));
229
- const ringVx = velocity * MULBERRY.nextBetween(2, 3);
230
- const ringVy = velocity * 0.6;
231
-
232
- for (let i = 0; i < ringCount; i++) {
233
- const rad = (i / ringCount) * Math.PI * 2;
234
-
235
- const cx = Math.cos(rad) * ringVx + MULBERRY.nextBetween(-0.25, 0.25);
236
- const cy = Math.sin(rad) * ringVy + MULBERRY.nextBetween(-0.25, 0.25);
237
-
238
- const cosR = Math.cos(ringRotation);
239
- const sinR = Math.sin(ringRotation);
240
- const vx = cx * cosR - cy * sinR;
241
- const vy = cx * sinR + cy * cosR;
242
-
243
- const screenAngle = Math.atan2(vy, vx);
244
- const screenSpeed = Math.sqrt(vx * vx + vy * vy);
245
- const vz = Math.sin(rad) * velocity * 0.8;
246
-
247
- this.#explosions.push(new Explosion(
248
- position,
249
- hue + 60,
250
- this.#baseSize,
251
- 'ring',
252
- this.#scale,
253
- screenAngle,
254
- screenSpeed,
255
- vz
256
- ));
257
- }
258
- }
259
-
260
- #createDahliaExplosion(position: Point, hue: number): void {
261
- const petalCount = Math.floor(MULBERRY.nextBetween(6, 9));
262
- const particlesPerPetal = Math.floor(MULBERRY.nextBetween(8, 12));
263
-
264
- for (let petal = 0; petal < petalCount; petal++) {
265
- const baseAngle = (petal / petalCount) * Math.PI * 2;
266
- const petalHue = hue + (petal % 2 === 0 ? 25 : -25);
267
-
268
- for (let i = 0; i < particlesPerPetal; i++) {
269
- const angle = baseAngle + MULBERRY.nextBetween(-0.3, 0.3);
270
-
271
- this.#explosions.push(new Explosion(
272
- position,
273
- petalHue,
274
- this.#baseSize,
275
- 'dahlia',
276
- this.#scale,
277
- angle
278
- ));
279
- }
280
- }
281
- }
282
-
283
- #createHeartExplosion(position: Point, hue: number): void {
284
- const velocity = MULBERRY.nextBetween(3, 5);
285
- const count = Math.floor(MULBERRY.nextBetween(60, 80));
286
- const rotation = MULBERRY.nextBetween(-0.3, 0.3);
287
-
288
- for (let i = 0; i < count; i++) {
289
- const t = (i / count) * Math.PI * 2;
290
-
291
- const hx = 16 * Math.pow(Math.sin(t), 3);
292
- const hy = -(13 * Math.cos(t) - 5 * Math.cos(2 * t) - 2 * Math.cos(3 * t) - Math.cos(4 * t));
293
-
294
- const scale = velocity / 16;
295
- const vx = hx * scale;
296
- const vy = hy * scale;
297
-
298
- const cosR = Math.cos(rotation);
299
- const sinR = Math.sin(rotation);
300
- const rvx = vx * cosR - vy * sinR;
301
- const rvy = vx * sinR + vy * cosR;
302
-
303
- const angle = Math.atan2(rvy, rvx);
304
- const speed = Math.sqrt(rvx * rvx + rvy * rvy);
305
-
306
- this.#explosions.push(new Explosion(
307
- position,
308
- hue,
309
- this.#baseSize,
310
- 'heart',
311
- this.#scale,
312
- angle,
313
- Math.max(0.1, speed + MULBERRY.nextBetween(-0.15, 0.15))
314
- ));
315
- }
316
- }
317
-
318
- #createSpiralExplosion(position: Point, hue: number): void {
319
- const arms = Math.floor(MULBERRY.nextBetween(3, 5));
320
- const particlesPerArm = Math.floor(MULBERRY.nextBetween(15, 20));
321
- const twist = MULBERRY.nextBetween(2, 3.5);
322
- const baseRotation = MULBERRY.nextBetween(0, Math.PI * 2);
323
-
324
- for (let arm = 0; arm < arms; arm++) {
325
- const baseAngle = baseRotation + (arm / arms) * Math.PI * 2;
326
- const armHue = hue + arm * (360 / arms / 3);
327
-
328
- for (let i = 0; i < particlesPerArm; i++) {
329
- const progress = i / particlesPerArm;
330
- const angle = baseAngle + progress * twist;
331
- const speed = 2 + progress * 8;
332
-
333
- this.#explosions.push(new Explosion(
334
- position,
335
- armHue,
336
- this.#baseSize,
337
- 'spiral',
338
- this.#scale,
339
- angle,
340
- speed + MULBERRY.nextBetween(-0.3, 0.3)
341
- ));
342
- }
343
- }
344
- }
345
-
346
- #createFlowerExplosion(position: Point, hue: number): void {
347
- const velocity = MULBERRY.nextBetween(4, 7);
348
- const count = Math.floor(MULBERRY.nextBetween(70, 90));
349
- const petals = Math.floor(MULBERRY.nextBetween(2, 4));
350
- const rotation = MULBERRY.nextBetween(0, Math.PI * 2);
351
-
352
- for (let i = 0; i < count; i++) {
353
- const t = (i / count) * Math.PI * 2;
354
- const r = Math.abs(Math.cos(petals * t));
355
- const speed = velocity * r;
356
-
357
- if (speed < 0.3) {
358
- continue;
359
- }
360
-
361
- this.#explosions.push(new Explosion(
362
- position,
363
- hue + MULBERRY.nextBetween(-15, 15),
364
- this.#baseSize,
365
- 'flower',
366
- this.#scale,
367
- t + rotation,
368
- speed + MULBERRY.nextBetween(-0.2, 0.2)
369
- ));
370
- }
371
- }
372
-
373
- #createConcentricExplosion(position: Point, hue: number): void {
374
- const outerCount = Math.floor(MULBERRY.nextBetween(35, 50));
375
- const outerSpeed = MULBERRY.nextBetween(7, 10);
376
-
377
- for (let i = 0; i < outerCount; i++) {
378
- const angle = (i / outerCount) * Math.PI * 2;
379
-
380
- this.#explosions.push(new Explosion(
381
- position,
382
- hue,
383
- this.#baseSize,
384
- 'ring',
385
- this.#scale,
386
- angle + MULBERRY.nextBetween(-0.05, 0.05),
387
- outerSpeed + MULBERRY.nextBetween(-0.25, 0.25)
388
- ));
389
- }
390
-
391
- const innerCount = Math.floor(MULBERRY.nextBetween(25, 35));
392
- const innerSpeed = MULBERRY.nextBetween(3, 5);
393
-
394
- for (let i = 0; i < innerCount; i++) {
395
- const angle = (i / innerCount) * Math.PI * 2;
396
-
397
- this.#explosions.push(new Explosion(
398
- position,
399
- hue + 120,
400
- this.#baseSize,
401
- 'ring',
402
- this.#scale,
403
- angle + MULBERRY.nextBetween(-0.05, 0.05),
404
- innerSpeed + MULBERRY.nextBetween(-0.25, 0.25)
405
- ));
406
- }
173
+ const rng = () => MULBERRY.nextBetween(0, 1);
174
+ this.#explosions.push(...createExplosion(selected, position, hue, {lineWidth: this.#baseSize, scale: this.#scale}, rng));
407
175
  }
408
176
 
409
177
  #createFirework(position?: Point): void {
@@ -422,31 +190,14 @@ export class FireworkLayer extends SimulationLayer {
422
190
 
423
191
  firework.addEventListener('remove', () => {
424
192
  this.#fireworks.splice(this.#fireworks.indexOf(firework), 1);
425
- this.#createExplosion(firework.position, hue);
193
+ this.#spawnExplosion(firework.position, hue);
426
194
  }, {once: true});
427
195
 
428
196
  this.#fireworks.push(firework);
429
197
  }
430
198
 
431
199
  #pickVariant(): FireworkVariant {
432
- const roll = MULBERRY.nextBetween(0, 100);
433
-
434
- if (roll < 12) { return 'peony'; }
435
- if (roll < 22) { return 'chrysanthemum'; }
436
- if (roll < 29) { return 'willow'; }
437
- if (roll < 34) { return 'ring'; }
438
- if (roll < 39) { return 'palm'; }
439
- if (roll < 44) { return 'crackle'; }
440
- if (roll < 48) { return 'crossette'; }
441
- if (roll < 55) { return 'saturn'; }
442
- if (roll < 62) { return 'dahlia'; }
443
- if (roll < 67) { return 'brocade'; }
444
- if (roll < 71) { return 'horsetail'; }
445
- if (roll < 75) { return 'strobe'; }
446
- if (roll < 82) { return 'heart'; }
447
- if (roll < 89) { return 'spiral'; }
448
- if (roll < 94) { return 'flower'; }
449
-
450
- return 'concentric';
200
+ const index = Math.floor(MULBERRY.nextBetween(0, this.#variants.length));
201
+ return this.#variants[index];
451
202
  }
452
203
  }
@@ -8,7 +8,7 @@ export class Spark {
8
8
  readonly #size: number;
9
9
  readonly #decay: number;
10
10
  readonly #friction: number = 0.94;
11
- readonly #gravity: number = 0.6;
11
+ readonly #gravity: number = 0.3;
12
12
  #alpha: number = 1;
13
13
 
14
14
  get isDead(): boolean {
@@ -26,7 +26,7 @@ export class Spark {
26
26
  this.#decay = MULBERRY.nextBetween(0.03, 0.08);
27
27
  this.#velocity = {
28
28
  x: velocityX + MULBERRY.nextBetween(-1.5, 1.5),
29
- y: velocityY + MULBERRY.nextBetween(-2, 0.5)
29
+ y: velocityY + MULBERRY.nextBetween(-2, 2)
30
30
  };
31
31
  }
32
32
 
@@ -21,10 +21,10 @@ export interface ExplosionConfig {
21
21
  readonly glowSize: number;
22
22
  }
23
23
 
24
- export interface FireworkSimulationConfig {
24
+ export interface FireworksConfig {
25
25
  readonly scale?: number;
26
26
  readonly autoSpawn?: boolean;
27
- readonly canvasOptions?: CanvasRenderingContext2DSettings;
27
+ readonly variants?: FireworkVariant[];
28
28
  }
29
29
 
30
30
  export const FIREWORK_VARIANTS: FireworkVariant[] = [
@@ -1,4 +1,9 @@
1
- export { GlitterLayer } from './layer';
2
- export { GlitterSimulation } from './simulation';
3
- export type { GlitterSimulationConfig } from './simulation';
4
- export type { FallingGlitter, SettledGlitter } from './types';
1
+ import { Glitter } from './layer';
2
+ import type { GlitterConfig } from './types';
3
+ import type { Effect } from '../effect';
4
+
5
+ export function createGlitter(config?: GlitterConfig): Effect<GlitterConfig> {
6
+ return new Glitter(config);
7
+ }
8
+
9
+ export type { GlitterConfig, FallingGlitter, SettledGlitter } from './types';
@@ -1,14 +1,13 @@
1
1
  import { hexToRGB } from '@basmilius/utils';
2
- import { SimulationLayer } from '../layer';
2
+ import { Effect } from '../effect';
3
3
  import { GLITTER_COLORS, MULBERRY } from './consts';
4
- import type { GlitterSimulationConfig } from './simulation';
5
- import type { FallingGlitter, SettledGlitter } from './types';
4
+ import type { FallingGlitter, GlitterConfig, SettledGlitter } from './types';
6
5
 
7
- export class GlitterLayer extends SimulationLayer {
6
+ export class Glitter extends Effect<GlitterConfig> {
8
7
  readonly #scale: number;
9
8
  readonly #size: number;
10
- readonly #speed: number;
11
- readonly #groundLevel: number;
9
+ #speed: number;
10
+ #groundLevel: number;
12
11
  readonly #maxSettled: number;
13
12
  readonly #colorRGBs: [number, number, number][];
14
13
  #maxCount: number;
@@ -16,7 +15,7 @@ export class GlitterLayer extends SimulationLayer {
16
15
  #falling: FallingGlitter[] = [];
17
16
  #settled: SettledGlitter[] = [];
18
17
 
19
- constructor(config: GlitterSimulationConfig = {}) {
18
+ constructor(config: GlitterConfig = {}) {
20
19
  super();
21
20
 
22
21
  this.#scale = config.scale ?? 1;
@@ -38,6 +37,15 @@ export class GlitterLayer extends SimulationLayer {
38
37
  }
39
38
  }
40
39
 
40
+ configure(config: Partial<GlitterConfig>): void {
41
+ if (config.speed !== undefined) {
42
+ this.#speed = config.speed;
43
+ }
44
+ if (config.groundLevel !== undefined) {
45
+ this.#groundLevel = config.groundLevel;
46
+ }
47
+ }
48
+
41
49
  tick(dt: number, _width: number, _height: number): void {
42
50
  this.#time += 0.03 * dt;
43
51
 
@@ -1,3 +1,13 @@
1
+ export interface GlitterConfig {
2
+ readonly count?: number;
3
+ readonly colors?: string[];
4
+ readonly size?: number;
5
+ readonly speed?: number;
6
+ readonly groundLevel?: number;
7
+ readonly maxSettled?: number;
8
+ readonly scale?: number;
9
+ }
10
+
1
11
  export type FallingGlitter = {
2
12
  x: number;
3
13
  y: number;
package/src/index.ts CHANGED
@@ -1,18 +1,16 @@
1
1
  export * from './aurora';
2
- export * from './color';
3
2
  export * from './balloons';
4
3
  export * from './bubbles';
5
4
  export * from './canvas';
5
+ export * from './color';
6
6
  export * from './confetti';
7
7
  export * from './donuts';
8
+ export * from './effect';
8
9
  export * from './fireflies';
9
10
  export * from './firepit';
10
11
  export * from './fireworks';
11
12
  export * from './glitter';
12
13
  export * from './lanterns';
13
- export * from './layer';
14
- export * from './layered';
15
- export * from './simulation-canvas';
16
14
  export * from './leaves';
17
15
  export * from './lightning';
18
16
  export * from './matrix';
@@ -22,6 +20,7 @@ export * from './petals';
22
20
  export * from './plasma';
23
21
  export * from './rain';
24
22
  export * from './sandstorm';
23
+ export * from './scene';
25
24
  export * from './shooting-stars';
26
25
  export * from './snow';
27
26
  export * from './sparklers';
@@ -1,4 +1,9 @@
1
- export { LanternLayer } from './layer';
2
- export { LanternSimulation } from './simulation';
3
- export type { LanternSimulationConfig } from './simulation';
4
- export type { Lantern } from './types';
1
+ import { Lanterns } from './layer';
2
+ import type { LanternsConfig } from './types';
3
+ import type { Effect } from '../effect';
4
+
5
+ export function createLanterns(config?: LanternsConfig): Effect<LanternsConfig> {
6
+ return new Lanterns(config);
7
+ }
8
+
9
+ export type { LanternsConfig, Lantern } from './types';
@@ -1,19 +1,20 @@
1
1
  import { hexToRGB } from '@basmilius/utils';
2
- import { SimulationLayer } from '../layer';
2
+ import { Effect } from '../effect';
3
3
  import { LANTERN_COLORS, MULBERRY } from './consts';
4
- import type { LanternSimulationConfig } from './simulation';
5
- import type { Lantern } from './types';
4
+ import type { Lantern, LanternsConfig } from './types';
6
5
 
7
- export class LanternLayer extends SimulationLayer {
6
+ export class Lanterns extends Effect<LanternsConfig> {
8
7
  readonly #scale: number;
9
- readonly #speed: number;
8
+ #speed: number;
10
9
  readonly #size: number;
11
10
  readonly #colorRGBs: [number, number, number][];
12
11
  #maxCount: number;
13
12
  #time: number = 0;
14
13
  #lanterns: Lantern[] = [];
14
+ #sortedLanterns: Lantern[] = [];
15
+ #sortDirty: boolean = true;
15
16
 
16
- constructor(config: LanternSimulationConfig = {}) {
17
+ constructor(config: LanternsConfig = {}) {
17
18
  super();
18
19
 
19
20
  this.#scale = config.scale ?? 1;
@@ -33,6 +34,12 @@ export class LanternLayer extends SimulationLayer {
33
34
  }
34
35
  }
35
36
 
37
+ configure(config: Partial<LanternsConfig>): void {
38
+ if (config.speed !== undefined) {
39
+ this.#speed = config.speed;
40
+ }
41
+ }
42
+
36
43
  tick(dt: number, width: number, height: number): void {
37
44
  this.#time += 0.02 * dt * this.#speed;
38
45
 
@@ -46,13 +53,19 @@ export class LanternLayer extends SimulationLayer {
46
53
 
47
54
  if (lantern.y < -0.15) {
48
55
  this.#lanterns[i] = this.#createLantern(false);
56
+ this.#sortDirty = true;
49
57
  }
50
58
  }
51
59
  }
52
60
 
53
61
  draw(ctx: CanvasRenderingContext2D, width: number, height: number): void {
54
62
 
55
- const sorted = [...this.#lanterns].sort((a, b) => a.size - b.size);
63
+ if (this.#sortDirty) {
64
+ this.#sortedLanterns = [...this.#lanterns].sort((a, b) => a.size - b.size);
65
+ this.#sortDirty = false;
66
+ }
67
+
68
+ const sorted = this.#sortedLanterns;
56
69
 
57
70
  for (const lantern of sorted) {
58
71
  const px = lantern.x * width;
@@ -75,8 +88,7 @@ export class LanternLayer extends SimulationLayer {
75
88
  ctx.arc(px, py, glowRadius, 0, Math.PI * 2);
76
89
  ctx.fill();
77
90
 
78
- ctx.save();
79
- ctx.translate(px, py);
91
+ ctx.setTransform(1, 0, 0, 1, px, py);
80
92
 
81
93
  const bodyW = size * 0.8;
82
94
  const bodyH = size;
@@ -140,7 +152,7 @@ export class LanternLayer extends SimulationLayer {
140
152
  ctx.lineWidth = size * 0.04;
141
153
  ctx.stroke();
142
154
 
143
- ctx.restore();
155
+ ctx.resetTransform();
144
156
  }
145
157
  }
146
158
 
@@ -1,3 +1,11 @@
1
+ export interface LanternsConfig {
2
+ readonly count?: number;
3
+ readonly colors?: string[];
4
+ readonly size?: number;
5
+ readonly speed?: number;
6
+ readonly scale?: number;
7
+ }
8
+
1
9
  export type Lantern = {
2
10
  x: number;
3
11
  y: number;