@tsparticles/engine 3.0.3 → 3.1.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 (124) hide show
  1. package/README.md +265 -145
  2. package/browser/Core/Canvas.js +19 -19
  3. package/browser/Core/Container.js +45 -34
  4. package/browser/Core/Engine.js +36 -20
  5. package/browser/Core/Particle.js +35 -36
  6. package/browser/Core/Particles.js +30 -24
  7. package/browser/Core/Retina.js +5 -4
  8. package/browser/Core/Utils/Circle.js +4 -3
  9. package/browser/Core/Utils/Constants.js +3 -0
  10. package/browser/Core/Utils/EventListeners.js +18 -15
  11. package/browser/Core/Utils/ExternalInteractorBase.js +1 -1
  12. package/browser/Core/Utils/InteractionManager.js +14 -6
  13. package/browser/Core/Utils/ParticlesInteractorBase.js +1 -1
  14. package/browser/Core/Utils/QuadTree.js +5 -3
  15. package/browser/Core/Utils/Vector.js +7 -2
  16. package/browser/Core/Utils/Vector3d.js +14 -9
  17. package/browser/Options/Classes/ManualParticle.js +3 -2
  18. package/browser/Options/Classes/Options.js +3 -0
  19. package/browser/Utils/CanvasUtils.js +36 -26
  20. package/browser/Utils/ColorUtils.js +124 -45
  21. package/browser/Utils/EventDispatcher.js +6 -5
  22. package/browser/Utils/HslColorManager.js +5 -5
  23. package/browser/Utils/NumberUtils.js +35 -23
  24. package/browser/Utils/RgbColorManager.js +5 -5
  25. package/browser/Utils/Utils.js +102 -19
  26. package/cjs/Core/Canvas.js +19 -19
  27. package/cjs/Core/Container.js +45 -34
  28. package/cjs/Core/Engine.js +36 -20
  29. package/cjs/Core/Particle.js +34 -35
  30. package/cjs/Core/Particles.js +30 -24
  31. package/cjs/Core/Retina.js +5 -4
  32. package/cjs/Core/Utils/Circle.js +4 -3
  33. package/cjs/Core/Utils/Constants.js +4 -1
  34. package/cjs/Core/Utils/EventListeners.js +17 -14
  35. package/cjs/Core/Utils/ExternalInteractorBase.js +1 -1
  36. package/cjs/Core/Utils/InteractionManager.js +14 -6
  37. package/cjs/Core/Utils/ParticlesInteractorBase.js +1 -1
  38. package/cjs/Core/Utils/QuadTree.js +5 -3
  39. package/cjs/Core/Utils/Vector.js +7 -2
  40. package/cjs/Core/Utils/Vector3d.js +14 -9
  41. package/cjs/Options/Classes/ManualParticle.js +3 -2
  42. package/cjs/Options/Classes/Options.js +3 -0
  43. package/cjs/Utils/CanvasUtils.js +36 -26
  44. package/cjs/Utils/ColorUtils.js +126 -45
  45. package/cjs/Utils/EventDispatcher.js +6 -5
  46. package/cjs/Utils/HslColorManager.js +5 -5
  47. package/cjs/Utils/NumberUtils.js +37 -24
  48. package/cjs/Utils/RgbColorManager.js +5 -5
  49. package/cjs/Utils/Utils.js +103 -19
  50. package/esm/Core/Canvas.js +19 -19
  51. package/esm/Core/Container.js +45 -34
  52. package/esm/Core/Engine.js +36 -20
  53. package/esm/Core/Particle.js +35 -36
  54. package/esm/Core/Particles.js +30 -24
  55. package/esm/Core/Retina.js +5 -4
  56. package/esm/Core/Utils/Circle.js +4 -3
  57. package/esm/Core/Utils/Constants.js +3 -0
  58. package/esm/Core/Utils/EventListeners.js +18 -15
  59. package/esm/Core/Utils/ExternalInteractorBase.js +1 -1
  60. package/esm/Core/Utils/InteractionManager.js +14 -6
  61. package/esm/Core/Utils/ParticlesInteractorBase.js +1 -1
  62. package/esm/Core/Utils/QuadTree.js +5 -3
  63. package/esm/Core/Utils/Vector.js +7 -2
  64. package/esm/Core/Utils/Vector3d.js +14 -9
  65. package/esm/Options/Classes/ManualParticle.js +3 -2
  66. package/esm/Options/Classes/Options.js +3 -0
  67. package/esm/Utils/CanvasUtils.js +36 -26
  68. package/esm/Utils/ColorUtils.js +124 -45
  69. package/esm/Utils/EventDispatcher.js +6 -5
  70. package/esm/Utils/HslColorManager.js +5 -5
  71. package/esm/Utils/NumberUtils.js +35 -23
  72. package/esm/Utils/RgbColorManager.js +5 -5
  73. package/esm/Utils/Utils.js +102 -19
  74. package/package.json +1 -1
  75. package/report.html +2 -2
  76. package/tsparticles.engine.js +693 -334
  77. package/tsparticles.engine.min.js +1 -1
  78. package/tsparticles.engine.min.js.LICENSE.txt +1 -1
  79. package/types/Core/Interfaces/IParticleHslAnimation.d.ts +4 -4
  80. package/types/Core/Interfaces/IParticleValueAnimation.d.ts +4 -0
  81. package/types/Core/Interfaces/IShapeDrawData.d.ts +2 -2
  82. package/types/Core/Utils/Constants.d.ts +3 -0
  83. package/types/Core/Utils/ExternalInteractorBase.d.ts +1 -1
  84. package/types/Core/Utils/InteractionManager.d.ts +1 -1
  85. package/types/Core/Utils/ParticlesInteractorBase.d.ts +1 -1
  86. package/types/Core/Utils/Point.d.ts +1 -1
  87. package/types/Options/Classes/Options.d.ts +1 -0
  88. package/types/Options/Classes/Particles/Move/Move.d.ts +1 -2
  89. package/types/Options/Classes/Particles/Move/OutModes.d.ts +1 -2
  90. package/types/Options/Interfaces/IOptions.d.ts +1 -0
  91. package/types/Options/Interfaces/Interactivity/Modes/IModes.d.ts +1 -3
  92. package/types/Types/CustomEventArgs.d.ts +2 -2
  93. package/types/Types/ExportResult.d.ts +2 -2
  94. package/types/Types/ParticlesGroups.d.ts +1 -3
  95. package/types/Types/PathOptions.d.ts +1 -3
  96. package/types/Types/ShapeData.d.ts +1 -3
  97. package/types/Utils/CanvasUtils.d.ts +3 -2
  98. package/types/Utils/ColorUtils.d.ts +5 -0
  99. package/types/Utils/NumberUtils.d.ts +2 -2
  100. package/types/Utils/Utils.d.ts +9 -6
  101. package/umd/Core/Canvas.js +19 -19
  102. package/umd/Core/Container.js +46 -35
  103. package/umd/Core/Engine.js +36 -20
  104. package/umd/Core/Particle.js +35 -36
  105. package/umd/Core/Particles.js +30 -24
  106. package/umd/Core/Retina.js +5 -4
  107. package/umd/Core/Utils/Circle.js +4 -3
  108. package/umd/Core/Utils/Constants.js +4 -1
  109. package/umd/Core/Utils/EventListeners.js +17 -14
  110. package/umd/Core/Utils/ExternalInteractorBase.js +1 -1
  111. package/umd/Core/Utils/InteractionManager.js +14 -6
  112. package/umd/Core/Utils/ParticlesInteractorBase.js +1 -1
  113. package/umd/Core/Utils/QuadTree.js +5 -3
  114. package/umd/Core/Utils/Vector.js +7 -2
  115. package/umd/Core/Utils/Vector3d.js +14 -9
  116. package/umd/Options/Classes/ManualParticle.js +3 -2
  117. package/umd/Options/Classes/Options.js +3 -0
  118. package/umd/Utils/CanvasUtils.js +36 -26
  119. package/umd/Utils/ColorUtils.js +127 -46
  120. package/umd/Utils/EventDispatcher.js +6 -5
  121. package/umd/Utils/HslColorManager.js +5 -5
  122. package/umd/Utils/NumberUtils.js +38 -25
  123. package/umd/Utils/RgbColorManager.js +5 -5
  124. package/umd/Utils/Utils.js +104 -20
@@ -1,5 +1,6 @@
1
- import { getRandom, getRangeValue, mix, randomInRange, setRangeValue } from "./NumberUtils.js";
1
+ import { clamp, getRandom, getRangeMax, getRangeMin, getRangeValue, mix, randomInRange, setRangeValue, } from "./NumberUtils.js";
2
2
  import { isArray, isString, itemFromArray } from "./Utils.js";
3
+ import { millisecondsToSeconds, percentDenominator } from "../Core/Utils/Constants.js";
3
4
  const randomColorValue = "random", midColorValue = "mid", colorManagers = new Map();
4
5
  export function addColorManager(manager) {
5
6
  colorManagers.set(manager.key, manager);
@@ -12,13 +13,15 @@ function stringToRgba(input) {
12
13
  }
13
14
  const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])([a-f\d])?$/i, hexFixed = input.replace(shorthandRegex, (_, r, g, b, a) => {
14
15
  return r + r + g + g + b + b + (a !== undefined ? a + a : "");
15
- }), regex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i, result = regex.exec(hexFixed);
16
+ }), regex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})?$/i, result = regex.exec(hexFixed), radix = 16, defaultAlpha = 1, alphaFactor = 0xff;
16
17
  return result
17
18
  ? {
18
- a: result[4] !== undefined ? parseInt(result[4], 16) / 0xff : 1,
19
- b: parseInt(result[3], 16),
20
- g: parseInt(result[2], 16),
21
- r: parseInt(result[1], 16),
19
+ a: result[4] !== undefined
20
+ ? parseInt(result[4], radix) / alphaFactor
21
+ : defaultAlpha,
22
+ b: parseInt(result[3], radix),
23
+ g: parseInt(result[2], radix),
24
+ r: parseInt(result[1], radix),
22
25
  }
23
26
  : undefined;
24
27
  }
@@ -71,26 +74,26 @@ export function rangeColorToHsl(color, index, useIndex = true) {
71
74
  return rgb ? rgbToHsl(rgb) : undefined;
72
75
  }
73
76
  export function rgbToHsl(color) {
74
- const r1 = color.r / 255, g1 = color.g / 255, b1 = color.b / 255, max = Math.max(r1, g1, b1), min = Math.min(r1, g1, b1), res = {
75
- h: 0,
76
- l: (max + min) * 0.5,
77
- s: 0,
77
+ const rgbMax = 255, hMax = 360, sMax = 100, lMax = 100, hMin = 0, sMin = 0, hPhase = 60, half = 0.5, double = 2, r1 = color.r / rgbMax, g1 = color.g / rgbMax, b1 = color.b / rgbMax, max = Math.max(r1, g1, b1), min = Math.min(r1, g1, b1), res = {
78
+ h: hMin,
79
+ l: (max + min) * half,
80
+ s: sMin,
78
81
  };
79
82
  if (max !== min) {
80
- res.s = res.l < 0.5 ? (max - min) / (max + min) : (max - min) / (2.0 - max - min);
83
+ res.s = res.l < half ? (max - min) / (max + min) : (max - min) / (double - max - min);
81
84
  res.h =
82
85
  r1 === max
83
86
  ? (g1 - b1) / (max - min)
84
- : (res.h = g1 === max ? 2.0 + (b1 - r1) / (max - min) : 4.0 + (r1 - g1) / (max - min));
87
+ : (res.h = g1 === max ? double + (b1 - r1) / (max - min) : double * double + (r1 - g1) / (max - min));
85
88
  }
86
- res.l *= 100;
87
- res.s *= 100;
88
- res.h *= 60;
89
- if (res.h < 0) {
90
- res.h += 360;
89
+ res.l *= lMax;
90
+ res.s *= sMax;
91
+ res.h *= hPhase;
92
+ if (res.h < hMin) {
93
+ res.h += hMax;
91
94
  }
92
- if (res.h >= 360) {
93
- res.h -= 360;
95
+ if (res.h >= hMax) {
96
+ res.h -= hMax;
94
97
  }
95
98
  return res;
96
99
  }
@@ -101,29 +104,33 @@ export function stringToRgb(input) {
101
104
  return stringToRgba(input);
102
105
  }
103
106
  export function hslToRgb(hsl) {
104
- const h = ((hsl.h % 360) + 360) % 360, s = Math.max(0, Math.min(100, hsl.s)), l = Math.max(0, Math.min(100, hsl.l)), hNormalized = h / 360, sNormalized = s / 100, lNormalized = l / 100;
105
- if (s === 0) {
106
- const grayscaleValue = Math.round(lNormalized * 255);
107
+ const hMax = 360, sMax = 100, lMax = 100, sMin = 0, lMin = 0, h = ((hsl.h % hMax) + hMax) % hMax, s = Math.max(sMin, Math.min(sMax, hsl.s)), l = Math.max(lMin, Math.min(lMax, hsl.l)), hNormalized = h / hMax, sNormalized = s / sMax, lNormalized = l / lMax, rgbFactor = 255, triple = 3;
108
+ if (s === sMin) {
109
+ const grayscaleValue = Math.round(lNormalized * rgbFactor);
107
110
  return { r: grayscaleValue, g: grayscaleValue, b: grayscaleValue };
108
111
  }
109
- const channel = (temp1, temp2, temp3) => {
110
- if (temp3 < 0) {
111
- temp3 += 1;
112
+ const half = 0.5, double = 2, channel = (temp1, temp2, temp3) => {
113
+ const temp3Min = 0, temp3Max = 1, sextuple = 6;
114
+ if (temp3 < temp3Min) {
115
+ temp3++;
112
116
  }
113
- if (temp3 > 1) {
114
- temp3 -= 1;
117
+ if (temp3 > temp3Max) {
118
+ temp3--;
115
119
  }
116
- if (temp3 * 6 < 1) {
117
- return temp1 + (temp2 - temp1) * 6 * temp3;
120
+ if (temp3 * sextuple < temp3Max) {
121
+ return temp1 + (temp2 - temp1) * sextuple * temp3;
118
122
  }
119
- if (temp3 * 2 < 1) {
123
+ if (temp3 * double < temp3Max) {
120
124
  return temp2;
121
125
  }
122
- if (temp3 * 3 < 2) {
123
- return temp1 + (temp2 - temp1) * (2 / 3 - temp3) * 6;
126
+ if (temp3 * triple < temp3Max * double) {
127
+ const temp3Offset = double / triple;
128
+ return temp1 + (temp2 - temp1) * (temp3Offset - temp3) * sextuple;
124
129
  }
125
130
  return temp1;
126
- }, temp1 = lNormalized < 0.5 ? lNormalized * (1 + sNormalized) : lNormalized + sNormalized - lNormalized * sNormalized, temp2 = 2 * lNormalized - temp1, red = Math.min(255, 255 * channel(temp2, temp1, hNormalized + 1 / 3)), green = Math.min(255, 255 * channel(temp2, temp1, hNormalized)), blue = Math.min(255, 255 * channel(temp2, temp1, hNormalized - 1 / 3));
131
+ }, sNormalizedOffset = 1, temp1 = lNormalized < half
132
+ ? lNormalized * (sNormalizedOffset + sNormalized)
133
+ : lNormalized + sNormalized - lNormalized * sNormalized, temp2 = double * lNormalized - temp1, phaseNumerator = 1, phaseThird = phaseNumerator / triple, red = Math.min(rgbFactor, rgbFactor * channel(temp2, temp1, hNormalized + phaseThird)), green = Math.min(rgbFactor, rgbFactor * channel(temp2, temp1, hNormalized)), blue = Math.min(rgbFactor, rgbFactor * channel(temp2, temp1, hNormalized - phaseThird));
127
134
  return { r: Math.round(red), g: Math.round(green), b: Math.round(blue) };
128
135
  }
129
136
  export function hslaToRgba(hsla) {
@@ -136,18 +143,20 @@ export function hslaToRgba(hsla) {
136
143
  };
137
144
  }
138
145
  export function getRandomRgbColor(min) {
139
- const fixedMin = min ?? 0;
146
+ const defaultMin = 0, fixedMin = min ?? defaultMin, rgbMax = 256;
140
147
  return {
141
- b: Math.floor(randomInRange(setRangeValue(fixedMin, 256))),
142
- g: Math.floor(randomInRange(setRangeValue(fixedMin, 256))),
143
- r: Math.floor(randomInRange(setRangeValue(fixedMin, 256))),
148
+ b: Math.floor(randomInRange(setRangeValue(fixedMin, rgbMax))),
149
+ g: Math.floor(randomInRange(setRangeValue(fixedMin, rgbMax))),
150
+ r: Math.floor(randomInRange(setRangeValue(fixedMin, rgbMax))),
144
151
  };
145
152
  }
146
153
  export function getStyleFromRgb(color, opacity) {
147
- return `rgba(${color.r}, ${color.g}, ${color.b}, ${opacity ?? 1})`;
154
+ const defaultOpacity = 1;
155
+ return `rgba(${color.r}, ${color.g}, ${color.b}, ${opacity ?? defaultOpacity})`;
148
156
  }
149
157
  export function getStyleFromHsl(color, opacity) {
150
- return `hsla(${color.h}, ${color.s}%, ${color.l}%, ${opacity ?? 1})`;
158
+ const defaultOpacity = 1;
159
+ return `hsla(${color.h}, ${color.s}%, ${color.l}%, ${opacity ?? defaultOpacity})`;
151
160
  }
152
161
  export function colorMix(color1, color2, size1, size2) {
153
162
  let rgb1 = color1, rgb2 = color2;
@@ -238,21 +247,91 @@ export function getHslAnimationFromHsl(hsl, animationOptions, reduceFactor) {
238
247
  }
239
248
  function setColorAnimation(colorValue, colorAnimation, reduceFactor) {
240
249
  colorValue.enable = colorAnimation.enable;
250
+ const defaultVelocity = 0, decayOffset = 1, defaultLoops = 0, defaultTime = 0;
241
251
  if (colorValue.enable) {
242
- colorValue.velocity = (getRangeValue(colorAnimation.speed) / 100) * reduceFactor;
243
- colorValue.decay = 1 - getRangeValue(colorAnimation.decay);
252
+ colorValue.velocity = (getRangeValue(colorAnimation.speed) / percentDenominator) * reduceFactor;
253
+ colorValue.decay = decayOffset - getRangeValue(colorAnimation.decay);
244
254
  colorValue.status = "increasing";
245
- colorValue.loops = 0;
255
+ colorValue.loops = defaultLoops;
246
256
  colorValue.maxLoops = getRangeValue(colorAnimation.count);
247
- colorValue.time = 0;
248
- colorValue.delayTime = getRangeValue(colorAnimation.delay) * 1000;
257
+ colorValue.time = defaultTime;
258
+ colorValue.delayTime = getRangeValue(colorAnimation.delay) * millisecondsToSeconds;
249
259
  if (!colorAnimation.sync) {
250
260
  colorValue.velocity *= getRandom();
251
261
  colorValue.value *= getRandom();
252
262
  }
253
263
  colorValue.initialValue = colorValue.value;
264
+ colorValue.offset = setRangeValue(colorAnimation.offset);
254
265
  }
255
266
  else {
256
- colorValue.velocity = 0;
267
+ colorValue.velocity = defaultVelocity;
268
+ }
269
+ }
270
+ export function updateColorValue(data, range, decrease, delta) {
271
+ const minLoops = 0, minDelay = 0, identity = 1, minVelocity = 0, minOffset = 0, velocityFactor = 3.6;
272
+ if (!data ||
273
+ !data.enable ||
274
+ ((data.maxLoops ?? minLoops) > minLoops && (data.loops ?? minLoops) > (data.maxLoops ?? minLoops))) {
275
+ return;
276
+ }
277
+ if (!data.time) {
278
+ data.time = 0;
279
+ }
280
+ if ((data.delayTime ?? minDelay) > minDelay && data.time < (data.delayTime ?? minDelay)) {
281
+ data.time += delta.value;
282
+ }
283
+ if ((data.delayTime ?? minDelay) > minDelay && data.time < (data.delayTime ?? minDelay)) {
284
+ return;
285
+ }
286
+ const offset = data.offset ? randomInRange(data.offset) : minOffset, velocity = (data.velocity ?? minVelocity) * delta.factor + offset * velocityFactor, decay = data.decay ?? identity, max = getRangeMax(range), min = getRangeMin(range);
287
+ if (!decrease || data.status === "increasing") {
288
+ data.value += velocity;
289
+ if (data.value > max) {
290
+ if (!data.loops) {
291
+ data.loops = 0;
292
+ }
293
+ data.loops++;
294
+ if (decrease) {
295
+ data.status = "decreasing";
296
+ }
297
+ else {
298
+ data.value -= max;
299
+ }
300
+ }
301
+ }
302
+ else {
303
+ data.value -= velocity;
304
+ const minValue = 0;
305
+ if (data.value < minValue) {
306
+ if (!data.loops) {
307
+ data.loops = 0;
308
+ }
309
+ data.loops++;
310
+ data.status = "increasing";
311
+ }
312
+ }
313
+ if (data.velocity && decay !== identity) {
314
+ data.velocity *= decay;
315
+ }
316
+ data.value = clamp(data.value, min, max);
317
+ }
318
+ export function updateColor(color, delta) {
319
+ if (!color) {
320
+ return;
321
+ }
322
+ const { h, s, l } = color;
323
+ const ranges = {
324
+ h: { min: 0, max: 360 },
325
+ s: { min: 0, max: 100 },
326
+ l: { min: 0, max: 100 },
327
+ };
328
+ if (h) {
329
+ updateColorValue(h, ranges.h, false, delta);
330
+ }
331
+ if (s) {
332
+ updateColorValue(s, ranges.s, true, delta);
333
+ }
334
+ if (l) {
335
+ updateColorValue(l, ranges.l, true, delta);
257
336
  }
258
337
  }
@@ -13,7 +13,7 @@ export class EventDispatcher {
13
13
  }
14
14
  dispatchEvent(type, args) {
15
15
  const listeners = this._listeners.get(type);
16
- listeners && listeners.forEach((handler) => handler(args));
16
+ listeners?.forEach((handler) => handler(args));
17
17
  }
18
18
  hasEventListener(type) {
19
19
  return !!this._listeners.get(type);
@@ -31,15 +31,16 @@ export class EventDispatcher {
31
31
  if (!arr) {
32
32
  return;
33
33
  }
34
- const length = arr.length, idx = arr.indexOf(listener);
35
- if (idx < 0) {
34
+ const length = arr.length, idx = arr.indexOf(listener), minIndex = 0;
35
+ if (idx < minIndex) {
36
36
  return;
37
37
  }
38
- if (length === 1) {
38
+ const deleteCount = 1;
39
+ if (length === deleteCount) {
39
40
  this._listeners.delete(type);
40
41
  }
41
42
  else {
42
- arr.splice(idx, 1);
43
+ arr.splice(idx, deleteCount);
43
44
  }
44
45
  }
45
46
  }
@@ -25,13 +25,13 @@ export class HslColorManager {
25
25
  if (!input.startsWith("hsl")) {
26
26
  return;
27
27
  }
28
- const regex = /hsla?\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(,\s*([\d.%]+)\s*)?\)/i, result = regex.exec(input);
28
+ const regex = /hsla?\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(,\s*([\d.%]+)\s*)?\)/i, result = regex.exec(input), minLength = 4, defaultAlpha = 1, radix = 10;
29
29
  return result
30
30
  ? hslaToRgba({
31
- a: result.length > 4 ? parseAlpha(result[5]) : 1,
32
- h: parseInt(result[1], 10),
33
- l: parseInt(result[3], 10),
34
- s: parseInt(result[2], 10),
31
+ a: result.length > minLength ? parseAlpha(result[5]) : defaultAlpha,
32
+ h: parseInt(result[1], radix),
33
+ l: parseInt(result[3], radix),
34
+ s: parseInt(result[2], radix),
35
35
  })
36
36
  : undefined;
37
37
  }
@@ -1,7 +1,8 @@
1
1
  import { Vector } from "../Core/Utils/Vector.js";
2
2
  import { isNumber } from "./Utils.js";
3
+ import { percentDenominator } from "../Core/Utils/Constants.js";
3
4
  let _random = Math.random;
4
- const easings = new Map();
5
+ const easings = new Map(), double = 2, doublePI = Math.PI * double;
5
6
  export function addEasing(name, easing) {
6
7
  if (easings.get(name)) {
7
8
  return;
@@ -9,13 +10,14 @@ export function addEasing(name, easing) {
9
10
  easings.set(name, easing);
10
11
  }
11
12
  export function getEasing(name) {
12
- return easings.get(name) || ((value) => value);
13
+ return easings.get(name) ?? ((value) => value);
13
14
  }
14
15
  export function setRandom(rnd = Math.random) {
15
16
  _random = rnd;
16
17
  }
17
18
  export function getRandom() {
18
- return clamp(_random(), 0, 1 - 1e-16);
19
+ const min = 0, max = 1;
20
+ return clamp(_random(), min, max - Number.EPSILON);
19
21
  }
20
22
  export function clamp(num, min, max) {
21
23
  return Math.min(Math.max(num, min), max);
@@ -24,10 +26,10 @@ export function mix(comp1, comp2, weight1, weight2) {
24
26
  return Math.floor((comp1 * weight1 + comp2 * weight2) / (weight1 + weight2));
25
27
  }
26
28
  export function randomInRange(r) {
27
- const max = getRangeMax(r);
29
+ const max = getRangeMax(r), minOffset = 0;
28
30
  let min = getRangeMin(r);
29
31
  if (max === min) {
30
- min = 0;
32
+ min = minOffset;
31
33
  }
32
34
  return getRandom() * (max - min) + min;
33
35
  }
@@ -53,39 +55,44 @@ export function setRangeValue(source, value) {
53
55
  : setRangeValue(min, max);
54
56
  }
55
57
  export function getDistances(pointA, pointB) {
56
- const dx = pointA.x - pointB.x, dy = pointA.y - pointB.y;
57
- return { dx: dx, dy: dy, distance: Math.sqrt(dx ** 2 + dy ** 2) };
58
+ const dx = pointA.x - pointB.x, dy = pointA.y - pointB.y, squareExp = 2;
59
+ return { dx: dx, dy: dy, distance: Math.sqrt(dx ** squareExp + dy ** squareExp) };
58
60
  }
59
61
  export function getDistance(pointA, pointB) {
60
62
  return getDistances(pointA, pointB).distance;
61
63
  }
64
+ export function degToRad(degrees) {
65
+ const PIDeg = 180;
66
+ return (degrees * Math.PI) / PIDeg;
67
+ }
62
68
  export function getParticleDirectionAngle(direction, position, center) {
63
69
  if (isNumber(direction)) {
64
- return (direction * Math.PI) / 180;
70
+ return degToRad(direction);
65
71
  }
72
+ const empty = 0, half = 0.5, quarter = 0.25, threeQuarter = half + quarter;
66
73
  switch (direction) {
67
74
  case "top":
68
- return -Math.PI * 0.5;
75
+ return -Math.PI * half;
69
76
  case "top-right":
70
- return -Math.PI * 0.25;
77
+ return -Math.PI * quarter;
71
78
  case "right":
72
- return 0;
79
+ return empty;
73
80
  case "bottom-right":
74
- return Math.PI * 0.25;
81
+ return Math.PI * quarter;
75
82
  case "bottom":
76
- return Math.PI * 0.5;
83
+ return Math.PI * half;
77
84
  case "bottom-left":
78
- return Math.PI * 0.75;
85
+ return Math.PI * threeQuarter;
79
86
  case "left":
80
87
  return Math.PI;
81
88
  case "top-left":
82
- return -Math.PI * 0.75;
89
+ return -Math.PI * threeQuarter;
83
90
  case "inside":
84
91
  return Math.atan2(center.y - position.y, center.x - position.x);
85
92
  case "outside":
86
93
  return Math.atan2(position.y - center.y, position.x - center.x);
87
94
  default:
88
- return getRandom() * Math.PI * 2;
95
+ return getRandom() * doublePI;
89
96
  }
90
97
  }
91
98
  export function getParticleBaseVelocity(direction) {
@@ -95,20 +102,21 @@ export function getParticleBaseVelocity(direction) {
95
102
  return baseVelocity;
96
103
  }
97
104
  export function collisionVelocity(v1, v2, m1, m2) {
98
- return Vector.create((v1.x * (m1 - m2)) / (m1 + m2) + (v2.x * 2 * m2) / (m1 + m2), v1.y);
105
+ const double = 2;
106
+ return Vector.create((v1.x * (m1 - m2)) / (m1 + m2) + (v2.x * double * m2) / (m1 + m2), v1.y);
99
107
  }
100
108
  export function calcPositionFromSize(data) {
101
- return data.position && data.position.x !== undefined && data.position.y !== undefined
109
+ return data.position?.x !== undefined && data.position.y !== undefined
102
110
  ? {
103
- x: (data.position.x * data.size.width) / 100,
104
- y: (data.position.y * data.size.height) / 100,
111
+ x: (data.position.x * data.size.width) / percentDenominator,
112
+ y: (data.position.y * data.size.height) / percentDenominator,
105
113
  }
106
114
  : undefined;
107
115
  }
108
116
  export function calcPositionOrRandomFromSize(data) {
109
117
  return {
110
- x: ((data.position?.x ?? getRandom() * 100) * data.size.width) / 100,
111
- y: ((data.position?.y ?? getRandom() * 100) * data.size.height) / 100,
118
+ x: ((data.position?.x ?? getRandom() * percentDenominator) * data.size.width) / percentDenominator,
119
+ y: ((data.position?.y ?? getRandom() * percentDenominator) * data.size.height) / percentDenominator,
112
120
  };
113
121
  }
114
122
  export function calcPositionOrRandomFromSizeRanged(data) {
@@ -132,5 +140,9 @@ export function calcExactPositionOrRandomFromSizeRanged(data) {
132
140
  return calcExactPositionOrRandomFromSize({ size: data.size, position });
133
141
  }
134
142
  export function parseAlpha(input) {
135
- return input ? (input.endsWith("%") ? parseFloat(input) / 100 : parseFloat(input)) : 1;
143
+ const defaultAlpha = 1;
144
+ if (!input) {
145
+ return defaultAlpha;
146
+ }
147
+ return input.endsWith("%") ? parseFloat(input) / percentDenominator : parseFloat(input);
136
148
  }
@@ -24,13 +24,13 @@ export class RgbColorManager {
24
24
  if (!input.startsWith(this.stringPrefix)) {
25
25
  return;
26
26
  }
27
- const regex = /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(,\s*([\d.%]+)\s*)?\)/i, result = regex.exec(input);
27
+ const regex = /rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(,\s*([\d.%]+)\s*)?\)/i, result = regex.exec(input), radix = 10, minLength = 4, defaultAlpha = 1;
28
28
  return result
29
29
  ? {
30
- a: result.length > 4 ? parseAlpha(result[5]) : 1,
31
- b: parseInt(result[3], 10),
32
- g: parseInt(result[2], 10),
33
- r: parseInt(result[1], 10),
30
+ a: result.length > minLength ? parseAlpha(result[5]) : defaultAlpha,
31
+ b: parseInt(result[3], radix),
32
+ g: parseInt(result[2], radix),
33
+ r: parseInt(result[1], radix),
34
34
  }
35
35
  : undefined;
36
36
  }
@@ -1,4 +1,5 @@
1
- import { collisionVelocity, getDistances, getRandom, getRangeMax, getRangeMin, getRangeValue, randomInRange, } from "./NumberUtils.js";
1
+ import { clamp, collisionVelocity, getDistances, getRandom, getRangeMax, getRangeMin, getRangeValue, randomInRange, } from "./NumberUtils.js";
2
+ import { halfRandom, millisecondsToSeconds, percentDenominator } from "../Core/Utils/Constants.js";
2
3
  import { Vector } from "../Core/Utils/Vector.js";
3
4
  const _logger = {
4
5
  debug: console.debug,
@@ -20,15 +21,15 @@ export function getLogger() {
20
21
  return _logger;
21
22
  }
22
23
  function rectSideBounce(data) {
23
- const res = { bounced: false }, { pSide, pOtherSide, rectSide, rectOtherSide, velocity, factor } = data;
24
+ const res = { bounced: false }, { pSide, pOtherSide, rectSide, rectOtherSide, velocity, factor } = data, half = 0.5, minVelocity = 0;
24
25
  if (pOtherSide.min < rectOtherSide.min ||
25
26
  pOtherSide.min > rectOtherSide.max ||
26
27
  pOtherSide.max < rectOtherSide.min ||
27
28
  pOtherSide.max > rectOtherSide.max) {
28
29
  return res;
29
30
  }
30
- if ((pSide.max >= rectSide.min && pSide.max <= (rectSide.max + rectSide.min) * 0.5 && velocity > 0) ||
31
- (pSide.min <= rectSide.max && pSide.min > (rectSide.max + rectSide.min) * 0.5 && velocity < 0)) {
31
+ if ((pSide.max >= rectSide.min && pSide.max <= (rectSide.max + rectSide.min) * half && velocity > minVelocity) ||
32
+ (pSide.min <= rectSide.max && pSide.min > (rectSide.max + rectSide.min) * half && velocity < minVelocity)) {
32
33
  res.velocity = velocity * -factor;
33
34
  res.bounced = true;
34
35
  }
@@ -65,7 +66,8 @@ export function safeMutationObserver(callback) {
65
66
  return new MutationObserver(callback);
66
67
  }
67
68
  export function isInArray(value, array) {
68
- return value === array || (isArray(array) && array.indexOf(value) > -1);
69
+ const invalidIndex = -1;
70
+ return value === array || (isArray(array) && array.indexOf(value) > invalidIndex);
69
71
  }
70
72
  export async function loadFont(font, weight) {
71
73
  try {
@@ -81,7 +83,8 @@ export function itemFromArray(array, index, useIndex = true) {
81
83
  return array[index !== undefined && useIndex ? index % array.length : arrayRandomIndex(array)];
82
84
  }
83
85
  export function isPointInside(point, size, offset, radius, direction) {
84
- return areBoundsInside(calculateBounds(point, radius ?? 0), size, offset, direction);
86
+ const minRadius = 0;
87
+ return areBoundsInside(calculateBounds(point, radius ?? minRadius), size, offset, direction);
85
88
  }
86
89
  export function areBoundsInside(bounds, size, offset, direction) {
87
90
  let inside = true;
@@ -171,8 +174,8 @@ export function circleBounceDataFromParticle(p) {
171
174
  };
172
175
  }
173
176
  export function circleBounce(p1, p2) {
174
- const { x: xVelocityDiff, y: yVelocityDiff } = p1.velocity.sub(p2.velocity), [pos1, pos2] = [p1.position, p2.position], { dx: xDist, dy: yDist } = getDistances(pos2, pos1);
175
- if (xVelocityDiff * xDist + yVelocityDiff * yDist < 0) {
177
+ const { x: xVelocityDiff, y: yVelocityDiff } = p1.velocity.sub(p2.velocity), [pos1, pos2] = [p1.position, p2.position], { dx: xDist, dy: yDist } = getDistances(pos2, pos1), minimumDistance = 0;
178
+ if (xVelocityDiff * xDist + yVelocityDiff * yDist < minimumDistance) {
176
179
  return;
177
180
  }
178
181
  const angle = -Math.atan2(yDist, xDist), m1 = p1.mass, m2 = p2.mass, u1 = p1.velocity.rotate(angle), u2 = p2.velocity.rotate(angle), v1 = collisionVelocity(u1, u2, m1, m2), v2 = collisionVelocity(u2, u1, m1, m2), vFinal1 = v1.rotate(-angle), vFinal2 = v2.rotate(-angle);
@@ -240,17 +243,22 @@ export function rectBounce(particle, divBounds) {
240
243
  }
241
244
  }
242
245
  export function executeOnSingleOrMultiple(obj, callback) {
243
- return isArray(obj) ? obj.map((item, index) => callback(item, index)) : callback(obj, 0);
246
+ const defaultIndex = 0;
247
+ return isArray(obj) ? obj.map((item, index) => callback(item, index)) : callback(obj, defaultIndex);
244
248
  }
245
249
  export function itemFromSingleOrMultiple(obj, index, useIndex) {
246
250
  return isArray(obj) ? itemFromArray(obj, index, useIndex) : obj;
247
251
  }
248
252
  export function findItemFromSingleOrMultiple(obj, callback) {
249
- return isArray(obj) ? obj.find((t, index) => callback(t, index)) : callback(obj, 0) ? obj : undefined;
253
+ if (isArray(obj)) {
254
+ return obj.find((t, index) => callback(t, index));
255
+ }
256
+ const defaultIndex = 0;
257
+ return callback(obj, defaultIndex) ? obj : undefined;
250
258
  }
251
259
  export function initParticleNumericAnimationValue(options, pxRatio) {
252
260
  const valueRange = options.value, animationOptions = options.animation, res = {
253
- delayTime: getRangeValue(animationOptions.delay) * 1000,
261
+ delayTime: getRangeValue(animationOptions.delay) * millisecondsToSeconds,
254
262
  enable: animationOptions.enable,
255
263
  value: getRangeValue(options.value) * pxRatio,
256
264
  max: getRangeMax(valueRange) * pxRatio,
@@ -258,9 +266,9 @@ export function initParticleNumericAnimationValue(options, pxRatio) {
258
266
  loops: 0,
259
267
  maxLoops: getRangeValue(animationOptions.count),
260
268
  time: 0,
261
- };
269
+ }, decayOffset = 1;
262
270
  if (animationOptions.enable) {
263
- res.decay = 1 - getRangeValue(animationOptions.decay);
271
+ res.decay = decayOffset - getRangeValue(animationOptions.decay);
264
272
  switch (animationOptions.mode) {
265
273
  case "increase":
266
274
  res.status = "increasing";
@@ -269,7 +277,7 @@ export function initParticleNumericAnimationValue(options, pxRatio) {
269
277
  res.status = "decreasing";
270
278
  break;
271
279
  case "random":
272
- res.status = getRandom() >= 0.5 ? "increasing" : "decreasing";
280
+ res.status = getRandom() >= halfRandom ? "increasing" : "decreasing";
273
281
  break;
274
282
  }
275
283
  const autoStatus = animationOptions.mode === "auto";
@@ -290,7 +298,7 @@ export function initParticleNumericAnimationValue(options, pxRatio) {
290
298
  default:
291
299
  res.value = randomInRange(res);
292
300
  if (autoStatus) {
293
- res.status = getRandom() >= 0.5 ? "increasing" : "decreasing";
301
+ res.status = getRandom() >= halfRandom ? "increasing" : "decreasing";
294
302
  }
295
303
  break;
296
304
  }
@@ -307,14 +315,14 @@ function getPositionOrSize(positionOrSize, canvasSize) {
307
315
  const isPosition = "x" in positionOrSize;
308
316
  if (isPosition) {
309
317
  return {
310
- x: (positionOrSize.x / 100) * canvasSize.width,
311
- y: (positionOrSize.y / 100) * canvasSize.height,
318
+ x: (positionOrSize.x / percentDenominator) * canvasSize.width,
319
+ y: (positionOrSize.y / percentDenominator) * canvasSize.height,
312
320
  };
313
321
  }
314
322
  else {
315
323
  return {
316
- width: (positionOrSize.width / 100) * canvasSize.width,
317
- height: (positionOrSize.height / 100) * canvasSize.height,
324
+ width: (positionOrSize.width / percentDenominator) * canvasSize.width,
325
+ height: (positionOrSize.height / percentDenominator) * canvasSize.height,
318
326
  };
319
327
  }
320
328
  }
@@ -342,3 +350,78 @@ export function isObject(arg) {
342
350
  export function isArray(arg) {
343
351
  return Array.isArray(arg);
344
352
  }
353
+ function checkDestroy(particle, destroyType, value, minValue, maxValue) {
354
+ switch (destroyType) {
355
+ case "max":
356
+ if (value >= maxValue) {
357
+ particle.destroy();
358
+ }
359
+ break;
360
+ case "min":
361
+ if (value <= minValue) {
362
+ particle.destroy();
363
+ }
364
+ break;
365
+ }
366
+ }
367
+ export function updateAnimation(particle, data, changeDirection, destroyType, delta) {
368
+ const minLoops = 0, minDelay = 0, identity = 1, minVelocity = 0, minDecay = 1;
369
+ if (particle.destroyed ||
370
+ !data ||
371
+ !data.enable ||
372
+ ((data.maxLoops ?? minLoops) > minLoops && (data.loops ?? minLoops) > (data.maxLoops ?? minLoops))) {
373
+ return;
374
+ }
375
+ const velocity = (data.velocity ?? minVelocity) * delta.factor, minValue = data.min, maxValue = data.max, decay = data.decay ?? minDecay;
376
+ if (!data.time) {
377
+ data.time = 0;
378
+ }
379
+ if ((data.delayTime ?? minDelay) > minDelay && data.time < (data.delayTime ?? minDelay)) {
380
+ data.time += delta.value;
381
+ }
382
+ if ((data.delayTime ?? minDelay) > minDelay && data.time < (data.delayTime ?? minDelay)) {
383
+ return;
384
+ }
385
+ switch (data.status) {
386
+ case "increasing":
387
+ if (data.value >= maxValue) {
388
+ if (changeDirection) {
389
+ data.status = "decreasing";
390
+ }
391
+ else {
392
+ data.value -= maxValue;
393
+ }
394
+ if (!data.loops) {
395
+ data.loops = minLoops;
396
+ }
397
+ data.loops++;
398
+ }
399
+ else {
400
+ data.value += velocity;
401
+ }
402
+ break;
403
+ case "decreasing":
404
+ if (data.value <= minValue) {
405
+ if (changeDirection) {
406
+ data.status = "increasing";
407
+ }
408
+ else {
409
+ data.value += maxValue;
410
+ }
411
+ if (!data.loops) {
412
+ data.loops = minLoops;
413
+ }
414
+ data.loops++;
415
+ }
416
+ else {
417
+ data.value -= velocity;
418
+ }
419
+ }
420
+ if (data.velocity && decay !== identity) {
421
+ data.velocity *= decay;
422
+ }
423
+ checkDestroy(particle, destroyType, data.value, minValue, maxValue);
424
+ if (!particle.destroyed) {
425
+ data.value = clamp(data.value, minValue, maxValue);
426
+ }
427
+ }