@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
@@ -5,7 +5,7 @@ import { Point } from "./Utils/Point.js";
5
5
  import { QuadTree } from "./Utils/QuadTree.js";
6
6
  import { Rectangle } from "./Utils/Rectangle.js";
7
7
  import { errorPrefix } from "./Utils/Constants.js";
8
- const qTreeCapacity = 4;
8
+ const qTreeCapacity = 4, squareExp = 2, defaultRemoveQuantity = 1;
9
9
  const qTreeRectangle = (canvasSize) => {
10
10
  const { height, width } = canvasSize, posOffset = -0.25, sizeFactor = 1.5;
11
11
  return new Rectangle(posOffset * width, posOffset * height, sizeFactor * width, sizeFactor * height);
@@ -28,7 +28,7 @@ export class Particles {
28
28
  }
29
29
  return;
30
30
  }
31
- const densityFactor = this._initDensityFactor(numberOptions.density), optParticlesNumber = numberOptions.value, optParticlesLimit = numberOptions.limit.value > 0 ? numberOptions.limit.value : optParticlesNumber, particlesNumber = Math.min(optParticlesNumber, optParticlesLimit) * densityFactor + manualCount, particlesCount = Math.min(this.count, this.filter((t) => t.group === group).length);
31
+ const densityFactor = this._initDensityFactor(numberOptions.density), optParticlesNumber = numberOptions.value, minLimit = 0, optParticlesLimit = numberOptions.limit.value > minLimit ? numberOptions.limit.value : optParticlesNumber, particlesNumber = Math.min(optParticlesNumber, optParticlesLimit) * densityFactor + manualCount, particlesCount = Math.min(this.count, this.filter((t) => t.group === group).length);
32
32
  if (group === undefined) {
33
33
  this._limit = numberOptions.limit.value * densityFactor;
34
34
  }
@@ -43,12 +43,12 @@ export class Particles {
43
43
  }
44
44
  };
45
45
  this._initDensityFactor = (densityOptions) => {
46
- const container = this._container;
46
+ const container = this._container, defaultFactor = 1;
47
47
  if (!container.canvas.element || !densityOptions.enable) {
48
- return 1;
48
+ return defaultFactor;
49
49
  }
50
50
  const canvas = container.canvas.element, pxRatio = container.retina.pixelRatio;
51
- return (canvas.width * canvas.height) / (densityOptions.height * densityOptions.width * pxRatio ** 2);
51
+ return (canvas.width * canvas.height) / (densityOptions.height * densityOptions.width * pxRatio ** squareExp);
52
52
  };
53
53
  this._pushParticle = (position, overrideOptions, group, initializer) => {
54
54
  try {
@@ -79,7 +79,6 @@ export class Particles {
79
79
  }
80
80
  catch (e) {
81
81
  getLogger().warning(`${errorPrefix} adding particle: ${e}`);
82
- return;
83
82
  }
84
83
  };
85
84
  this._removeParticle = (index, group, override) => {
@@ -87,9 +86,9 @@ export class Particles {
87
86
  if (!particle || particle.group !== group) {
88
87
  return false;
89
88
  }
90
- const zIdx = this._zArray.indexOf(particle);
91
- this._array.splice(index, 1);
92
- this._zArray.splice(zIdx, 1);
89
+ const zIdx = this._zArray.indexOf(particle), deleteCount = 1;
90
+ this._array.splice(index, deleteCount);
91
+ this._zArray.splice(zIdx, deleteCount);
93
92
  particle.destroy(override);
94
93
  this._engine.dispatchEvent("particleRemoved", {
95
94
  container: this._container,
@@ -126,11 +125,11 @@ export class Particles {
126
125
  }
127
126
  }
128
127
  addParticle(position, overrideOptions, group, initializer) {
129
- const limitOptions = this._container.actualOptions.particles.number.limit, limit = group === undefined ? this._limit : this._groupLimits.get(group) ?? this._limit, currentCount = this.count;
130
- if (limit > 0) {
128
+ const limitOptions = this._container.actualOptions.particles.number.limit, limit = group === undefined ? this._limit : this._groupLimits.get(group) ?? this._limit, currentCount = this.count, minLimit = 0;
129
+ if (limit > minLimit) {
131
130
  if (limitOptions.mode === "delete") {
132
- const countToRemove = currentCount + 1 - limit;
133
- if (countToRemove > 0) {
131
+ const countOffset = 1, minCount = 0, countToRemove = currentCount + countOffset - limit;
132
+ if (countToRemove > minCount) {
134
133
  this.removeQuantity(countToRemove);
135
134
  }
136
135
  }
@@ -221,22 +220,26 @@ export class Particles {
221
220
  remove(particle, group, override) {
222
221
  this.removeAt(this._array.indexOf(particle), undefined, group, override);
223
222
  }
224
- removeAt(index, quantity = 1, group, override) {
225
- if (index < 0 || index > this.count) {
223
+ removeAt(index, quantity = defaultRemoveQuantity, group, override) {
224
+ const minIndex = 0;
225
+ if (index < minIndex || index > this.count) {
226
226
  return;
227
227
  }
228
228
  let deleted = 0;
229
229
  for (let i = index; deleted < quantity && i < this.count; i++) {
230
- this._removeParticle(i--, group, override) && deleted++;
230
+ if (this._removeParticle(i--, group, override)) {
231
+ deleted++;
232
+ }
231
233
  }
232
234
  }
233
235
  removeQuantity(quantity, group) {
234
- this.removeAt(0, quantity, group);
236
+ const defaultIndex = 0;
237
+ this.removeAt(defaultIndex, quantity, group);
235
238
  }
236
239
  setDensity() {
237
- const options = this._container.actualOptions, groups = options.particles.groups;
240
+ const options = this._container.actualOptions, groups = options.particles.groups, manualCount = 0;
238
241
  for (const group in groups) {
239
- this._applyDensity(groups[group], 0, group);
242
+ this._applyDensity(groups[group], manualCount, group);
240
243
  }
241
244
  this._applyDensity(options.particles, options.manualParticles.length);
242
245
  }
@@ -254,7 +257,7 @@ export class Particles {
254
257
  pathGenerator.update();
255
258
  }
256
259
  for (const [, plugin] of container.plugins) {
257
- plugin.update && (await plugin.update(delta));
260
+ await plugin.update?.(delta);
258
261
  }
259
262
  const resizeFactor = this._resizeFactor;
260
263
  for (const particle of this._array) {
@@ -265,15 +268,17 @@ export class Particles {
265
268
  particle.initialPosition.y *= resizeFactor.height;
266
269
  }
267
270
  particle.ignoresResizeRatio = false;
268
- await this._interactionManager.reset(particle);
271
+ this._interactionManager.reset(particle);
269
272
  for (const [, plugin] of this._container.plugins) {
270
273
  if (particle.destroyed) {
271
274
  break;
272
275
  }
273
- plugin.particleUpdate && plugin.particleUpdate(particle, delta);
276
+ plugin.particleUpdate?.(particle, delta);
274
277
  }
275
278
  for (const mover of this.movers) {
276
- mover.isEnabled(particle) && mover.move(particle, delta);
279
+ if (mover.isEnabled(particle)) {
280
+ mover.move(particle, delta);
281
+ }
277
282
  }
278
283
  if (particle.destroyed) {
279
284
  particlesToDelete.add(particle);
@@ -308,7 +313,8 @@ export class Particles {
308
313
  if (this._needsSort) {
309
314
  const zArray = this._zArray;
310
315
  zArray.sort((a, b) => b.position.z - a.position.z || a.id - b.id);
311
- this._lastZIndex = zArray[zArray.length - 1].position.z;
316
+ const lengthOffset = 1;
317
+ this._lastZIndex = zArray[zArray.length - lengthOffset].position.z;
312
318
  this._needsSort = false;
313
319
  }
314
320
  }
@@ -1,15 +1,16 @@
1
1
  import { getRangeValue } from "../Utils/NumberUtils.js";
2
2
  import { isSsr } from "../Utils/Utils.js";
3
+ const defaultRatio = 1, defaultReduceFactor = 1;
3
4
  export class Retina {
4
5
  constructor(container) {
5
6
  this.container = container;
6
- this.pixelRatio = 1;
7
- this.reduceFactor = 1;
7
+ this.pixelRatio = defaultRatio;
8
+ this.reduceFactor = defaultReduceFactor;
8
9
  }
9
10
  init() {
10
11
  const container = this.container, options = container.actualOptions;
11
- this.pixelRatio = !options.detectRetina || isSsr() ? 1 : window.devicePixelRatio;
12
- this.reduceFactor = 1;
12
+ this.pixelRatio = !options.detectRetina || isSsr() ? defaultRatio : window.devicePixelRatio;
13
+ this.reduceFactor = defaultReduceFactor;
13
14
  const ratio = this.pixelRatio, canvas = container.canvas;
14
15
  if (canvas.element) {
15
16
  const element = canvas.element;
@@ -1,6 +1,7 @@
1
1
  import { Range } from "./Range.js";
2
2
  import { Rectangle } from "./Rectangle.js";
3
3
  import { getDistance } from "../../Utils/NumberUtils.js";
4
+ const squareExp = 2;
4
5
  export class Circle extends Range {
5
6
  constructor(x, y, radius) {
6
7
  super(x, y);
@@ -12,12 +13,12 @@ export class Circle extends Range {
12
13
  intersects(range) {
13
14
  const pos1 = this.position, pos2 = range.position, distPos = { x: Math.abs(pos2.x - pos1.x), y: Math.abs(pos2.y - pos1.y) }, r = this.radius;
14
15
  if (range instanceof Circle) {
15
- const rSum = r + range.radius, dist = Math.sqrt(distPos.x ** 2 + distPos.y ** 2);
16
+ const rSum = r + range.radius, dist = Math.sqrt(distPos.x ** squareExp + distPos.y ** squareExp);
16
17
  return rSum > dist;
17
18
  }
18
19
  else if (range instanceof Rectangle) {
19
- const { width, height } = range.size, edges = Math.pow(distPos.x - width, 2) + Math.pow(distPos.y - height, 2);
20
- return (edges <= r ** 2 ||
20
+ const { width, height } = range.size, edges = Math.pow(distPos.x - width, squareExp) + Math.pow(distPos.y - height, squareExp);
21
+ return (edges <= r ** squareExp ||
21
22
  (distPos.x <= r + width && distPos.y <= r + height) ||
22
23
  distPos.x <= width ||
23
24
  distPos.y <= height);
@@ -11,3 +11,6 @@ export const touchCancelEvent = "touchcancel";
11
11
  export const resizeEvent = "resize";
12
12
  export const visibilityChangeEvent = "visibilitychange";
13
13
  export const errorPrefix = "tsParticles - Error";
14
+ export const percentDenominator = 100;
15
+ export const halfRandom = 0.5;
16
+ export const millisecondsToSeconds = 1000;
@@ -1,5 +1,6 @@
1
1
  import { executeOnSingleOrMultiple, isBoolean, safeMatchMedia } from "../../Utils/Utils.js";
2
- import { mouseDownEvent, mouseLeaveEvent, mouseMoveEvent, mouseOutEvent, mouseUpEvent, resizeEvent, touchCancelEvent, touchEndEvent, touchMoveEvent, touchStartEvent, visibilityChangeEvent, } from "./Constants.js";
2
+ import { millisecondsToSeconds, mouseDownEvent, mouseLeaveEvent, mouseMoveEvent, mouseOutEvent, mouseUpEvent, resizeEvent, touchCancelEvent, touchEndEvent, touchMoveEvent, touchStartEvent, visibilityChangeEvent, } from "./Constants.js";
3
+ const double = 2;
3
4
  function manageListener(element, event, handler, add, options) {
4
5
  if (add) {
5
6
  let addOptions = { passive: true };
@@ -32,13 +33,14 @@ export class EventListeners {
32
33
  executeOnSingleOrMultiple(onClick.mode, (mode) => this.container.handleClickMode(mode));
33
34
  }
34
35
  if (e.type === "touchend") {
35
- setTimeout(() => this._mouseTouchFinish(), 500);
36
+ const touchDelay = 500;
37
+ setTimeout(() => this._mouseTouchFinish(), touchDelay);
36
38
  }
37
39
  };
38
40
  this._handleThemeChange = (e) => {
39
41
  const mediaEvent = e, container = this.container, options = container.options, defaultThemes = options.defaultThemes, themeName = mediaEvent.matches ? defaultThemes.dark : defaultThemes.light, theme = options.themes.find((theme) => theme.name === themeName);
40
42
  if (theme && theme.default.auto) {
41
- container.loadTheme(themeName);
43
+ void container.loadTheme(themeName);
42
44
  }
43
45
  };
44
46
  this._handleVisibilityChange = () => {
@@ -61,15 +63,16 @@ export class EventListeners {
61
63
  }
62
64
  }
63
65
  };
64
- this._handleWindowResize = async () => {
66
+ this._handleWindowResize = () => {
65
67
  if (this._resizeTimeout) {
66
68
  clearTimeout(this._resizeTimeout);
67
69
  delete this._resizeTimeout;
68
70
  }
69
- this._resizeTimeout = setTimeout(async () => {
71
+ const handleResize = async () => {
70
72
  const canvas = this.container.canvas;
71
- canvas && (await canvas.windowResize());
72
- }, this.container.actualOptions.interactivity.events.resize.delay * 1000);
73
+ await canvas?.windowResize();
74
+ };
75
+ this._resizeTimeout = setTimeout(() => void handleResize(), this.container.actualOptions.interactivity.events.resize.delay * millisecondsToSeconds);
73
76
  };
74
77
  this._manageInteractivityListeners = (mouseLeaveTmpEvent, add) => {
75
78
  const handlers = this._handlers, container = this.container, options = container.actualOptions;
@@ -155,12 +158,12 @@ export class EventListeners {
155
158
  delete this._resizeObserver;
156
159
  }
157
160
  else if (!this._resizeObserver && add && canvasEl) {
158
- this._resizeObserver = new ResizeObserver(async (entries) => {
161
+ this._resizeObserver = new ResizeObserver((entries) => {
159
162
  const entry = entries.find((e) => e.target === canvasEl);
160
163
  if (!entry) {
161
164
  return;
162
165
  }
163
- await this._handleWindowResize();
166
+ this._handleWindowResize();
164
167
  });
165
168
  this._resizeObserver.observe(canvasEl);
166
169
  }
@@ -211,7 +214,7 @@ export class EventListeners {
211
214
  };
212
215
  this._mouseTouchMove = (e) => {
213
216
  const container = this.container, options = container.actualOptions, interactivity = container.interactivity, canvasEl = container.canvas.element;
214
- if (!interactivity || !interactivity.element) {
217
+ if (!interactivity?.element) {
215
218
  return;
216
219
  }
217
220
  interactivity.mouse.inside = true;
@@ -233,8 +236,8 @@ export class EventListeners {
233
236
  if (source && target && canvasEl) {
234
237
  const sourceRect = source.getBoundingClientRect(), targetRect = target.getBoundingClientRect(), canvasRect = canvasEl.getBoundingClientRect();
235
238
  pos = {
236
- x: mouseEvent.offsetX + 2 * sourceRect.left - (targetRect.left + canvasRect.left),
237
- y: mouseEvent.offsetY + 2 * sourceRect.top - (targetRect.top + canvasRect.top),
239
+ x: mouseEvent.offsetX + double * sourceRect.left - (targetRect.left + canvasRect.left),
240
+ y: mouseEvent.offsetY + double * sourceRect.top - (targetRect.top + canvasRect.top),
238
241
  };
239
242
  }
240
243
  else {
@@ -254,10 +257,10 @@ export class EventListeners {
254
257
  else {
255
258
  this._canPush = e.type !== "touchmove";
256
259
  if (canvasEl) {
257
- const touchEvent = e, lastTouch = touchEvent.touches[touchEvent.touches.length - 1], canvasRect = canvasEl.getBoundingClientRect();
260
+ const touchEvent = e, lengthOffset = 1, lastTouch = touchEvent.touches[touchEvent.touches.length - lengthOffset], canvasRect = canvasEl.getBoundingClientRect(), defaultCoordinate = 0;
258
261
  pos = {
259
- x: lastTouch.clientX - (canvasRect.left ?? 0),
260
- y: lastTouch.clientY - (canvasRect.top ?? 0),
262
+ x: lastTouch.clientX - (canvasRect.left ?? defaultCoordinate),
263
+ y: lastTouch.clientY - (canvasRect.top ?? defaultCoordinate),
261
264
  };
262
265
  }
263
266
  }
@@ -1,6 +1,6 @@
1
1
  export class ExternalInteractorBase {
2
2
  constructor(container) {
3
- this.container = container;
4
3
  this.type = "external";
4
+ this.container = container;
5
5
  }
6
6
  }
@@ -8,12 +8,14 @@ export class InteractionManager {
8
8
  }
9
9
  async externalInteract(delta) {
10
10
  for (const interactor of this._externalInteractors) {
11
- interactor.isEnabled() && (await interactor.interact(delta));
11
+ if (interactor.isEnabled()) {
12
+ await interactor.interact(delta);
13
+ }
12
14
  }
13
15
  }
14
16
  handleClickMode(mode) {
15
17
  for (const interactor of this._externalInteractors) {
16
- interactor.handleClickMode && interactor.handleClickMode(mode);
18
+ interactor.handleClickMode?.(mode);
17
19
  }
18
20
  }
19
21
  init() {
@@ -36,15 +38,21 @@ export class InteractionManager {
36
38
  interactor.clear(particle, delta);
37
39
  }
38
40
  for (const interactor of this._particleInteractors) {
39
- interactor.isEnabled(particle) && (await interactor.interact(particle, delta));
41
+ if (interactor.isEnabled(particle)) {
42
+ await interactor.interact(particle, delta);
43
+ }
40
44
  }
41
45
  }
42
- async reset(particle) {
46
+ reset(particle) {
43
47
  for (const interactor of this._externalInteractors) {
44
- interactor.isEnabled() && interactor.reset(particle);
48
+ if (interactor.isEnabled()) {
49
+ interactor.reset(particle);
50
+ }
45
51
  }
46
52
  for (const interactor of this._particleInteractors) {
47
- interactor.isEnabled(particle) && interactor.reset(particle);
53
+ if (interactor.isEnabled(particle)) {
54
+ interactor.reset(particle);
55
+ }
48
56
  }
49
57
  }
50
58
  }
@@ -1,6 +1,6 @@
1
1
  export class ParticlesInteractorBase {
2
2
  constructor(container) {
3
- this.container = container;
4
3
  this.type = "particles";
4
+ this.container = container;
5
5
  }
6
6
  }
@@ -1,14 +1,16 @@
1
1
  import { Circle } from "./Circle.js";
2
2
  import { Rectangle } from "./Rectangle.js";
3
3
  import { getDistance } from "../../Utils/NumberUtils.js";
4
+ const half = 0.5, double = 2, subdivideCount = 4;
4
5
  export class QuadTree {
5
6
  constructor(rectangle, capacity) {
6
7
  this.rectangle = rectangle;
7
8
  this.capacity = capacity;
8
9
  this._subdivide = () => {
9
10
  const { x, y } = this.rectangle.position, { width, height } = this.rectangle.size, { capacity } = this;
10
- for (let i = 0; i < 4; i++) {
11
- this._subs.push(new QuadTree(new Rectangle(x + width * 0.5 * (i % 2), y + height * 0.5 * (Math.round(i * 0.5) - (i % 2)), width * 0.5, height * 0.5), capacity));
11
+ for (let i = 0; i < subdivideCount; i++) {
12
+ const fixedIndex = i % double;
13
+ this._subs.push(new QuadTree(new Rectangle(x + width * half * fixedIndex, y + height * half * (Math.round(i * half) - fixedIndex), width * half, height * half), capacity));
12
14
  }
13
15
  this._divided = true;
14
16
  };
@@ -30,7 +32,7 @@ export class QuadTree {
30
32
  return this._subs.some((sub) => sub.insert(point));
31
33
  }
32
34
  query(range, check, found) {
33
- const res = found || [];
35
+ const res = found ?? [];
34
36
  if (!range.intersects(this.rectangle)) {
35
37
  return [];
36
38
  }
@@ -1,10 +1,15 @@
1
1
  import { Vector3d } from "./Vector3d.js";
2
+ const origin = {
3
+ x: 0,
4
+ y: 0,
5
+ z: 0,
6
+ };
2
7
  export class Vector extends Vector3d {
3
8
  constructor(xOrCoords, y) {
4
- super(xOrCoords, y, 0);
9
+ super(xOrCoords, y, origin.z);
5
10
  }
6
11
  static get origin() {
7
- return Vector.create(0, 0);
12
+ return Vector.create(origin.x, origin.y);
8
13
  }
9
14
  static clone(source) {
10
15
  return Vector.create(source.x, source.y);
@@ -1,5 +1,10 @@
1
1
  import { errorPrefix } from "./Constants.js";
2
2
  import { isNumber } from "../../Utils/Utils.js";
3
+ const origin = {
4
+ x: 0,
5
+ y: 0,
6
+ z: 0,
7
+ }, squareExp = 2, inverseFactorNumerator = 1.0;
3
8
  export class Vector3d {
4
9
  constructor(xOrCoords, y, z) {
5
10
  this._updateFromAngle = (angle, length) => {
@@ -10,19 +15,19 @@ export class Vector3d {
10
15
  this.x = xOrCoords.x;
11
16
  this.y = xOrCoords.y;
12
17
  const coords3d = xOrCoords;
13
- this.z = coords3d.z ? coords3d.z : 0;
18
+ this.z = coords3d.z ? coords3d.z : origin.z;
14
19
  }
15
20
  else if (xOrCoords !== undefined && y !== undefined) {
16
21
  this.x = xOrCoords;
17
22
  this.y = y;
18
- this.z = z ?? 0;
23
+ this.z = z ?? origin.z;
19
24
  }
20
25
  else {
21
26
  throw new Error(`${errorPrefix} Vector3d not initialized correctly`);
22
27
  }
23
28
  }
24
29
  static get origin() {
25
- return Vector3d.create(0, 0, 0);
30
+ return Vector3d.create(origin.x, origin.y, origin.z);
26
31
  }
27
32
  get angle() {
28
33
  return Math.atan2(this.y, this.x);
@@ -68,7 +73,7 @@ export class Vector3d {
68
73
  this.z /= n;
69
74
  }
70
75
  getLengthSq() {
71
- return this.x ** 2 + this.y ** 2;
76
+ return this.x ** squareExp + this.y ** squareExp;
72
77
  }
73
78
  mult(n) {
74
79
  return Vector3d.create(this.x * n, this.y * n, this.z * n);
@@ -79,19 +84,19 @@ export class Vector3d {
79
84
  this.z *= n;
80
85
  }
81
86
  normalize() {
82
- const length = this.length;
83
- if (length != 0) {
84
- this.multTo(1.0 / length);
87
+ const length = this.length, noLength = 0;
88
+ if (length != noLength) {
89
+ this.multTo(inverseFactorNumerator / length);
85
90
  }
86
91
  }
87
92
  rotate(angle) {
88
- return Vector3d.create(this.x * Math.cos(angle) - this.y * Math.sin(angle), this.x * Math.sin(angle) + this.y * Math.cos(angle), 0);
93
+ return Vector3d.create(this.x * Math.cos(angle) - this.y * Math.sin(angle), this.x * Math.sin(angle) + this.y * Math.cos(angle), origin.z);
89
94
  }
90
95
  setTo(c) {
91
96
  this.x = c.x;
92
97
  this.y = c.y;
93
98
  const v3d = c;
94
- this.z = v3d.z ? v3d.z : 0;
99
+ this.z = v3d.z ? v3d.z : origin.z;
95
100
  }
96
101
  sub(v) {
97
102
  return Vector3d.create(this.x - v.x, this.y - v.y, this.z - v.z);
@@ -1,4 +1,5 @@
1
1
  import { deepExtend } from "../../Utils/Utils.js";
2
+ const defaultPosition = 50;
2
3
  export class ManualParticle {
3
4
  load(data) {
4
5
  if (!data) {
@@ -6,8 +7,8 @@ export class ManualParticle {
6
7
  }
7
8
  if (data.position) {
8
9
  this.position = {
9
- x: data.position.x ?? 50,
10
- y: data.position.y ?? 50,
10
+ x: data.position.x ?? defaultPosition,
11
+ y: data.position.y ?? defaultPosition,
11
12
  mode: data.position.mode ?? "percent",
12
13
  };
13
14
  }
@@ -53,6 +53,9 @@ export class Options {
53
53
  if (data.clear !== undefined) {
54
54
  this.clear = data.clear;
55
55
  }
56
+ if (data.key !== undefined) {
57
+ this.key = data.key;
58
+ }
56
59
  if (data.name !== undefined) {
57
60
  this.name = data.name;
58
61
  }
@@ -1,4 +1,5 @@
1
1
  import { getStyleFromRgb } from "./ColorUtils.js";
2
+ const origin = { x: 0, y: 0 };
2
3
  export function drawLine(context, begin, end) {
3
4
  context.beginPath();
4
5
  context.moveTo(begin.x, begin.y);
@@ -7,29 +8,28 @@ export function drawLine(context, begin, end) {
7
8
  }
8
9
  export function paintBase(context, dimension, baseColor) {
9
10
  context.fillStyle = baseColor ?? "rgba(0,0,0,0)";
10
- context.fillRect(0, 0, dimension.width, dimension.height);
11
+ context.fillRect(origin.x, origin.y, dimension.width, dimension.height);
11
12
  }
12
13
  export function paintImage(context, dimension, image, opacity) {
13
14
  if (!image) {
14
15
  return;
15
16
  }
16
17
  context.globalAlpha = opacity;
17
- context.drawImage(image, 0, 0, dimension.width, dimension.height);
18
+ context.drawImage(image, origin.x, origin.y, dimension.width, dimension.height);
18
19
  context.globalAlpha = 1;
19
20
  }
20
21
  export function clear(context, dimension) {
21
- context.clearRect(0, 0, dimension.width, dimension.height);
22
+ context.clearRect(origin.x, origin.y, dimension.width, dimension.height);
22
23
  }
23
24
  export function drawParticle(data) {
24
- const { container, context, particle, delta, colorStyles, backgroundMask, composite, radius, opacity, shadow, transform, } = data;
25
- const pos = particle.getPosition(), angle = particle.rotation + (particle.pathRotation ? particle.velocity.angle : 0), rotateData = {
25
+ const { container, context, particle, delta, colorStyles, backgroundMask, composite, radius, opacity, shadow, transform, } = data, pos = particle.getPosition(), defaultAngle = 0, angle = particle.rotation + (particle.pathRotation ? particle.velocity.angle : defaultAngle), rotateData = {
26
26
  sin: Math.sin(angle),
27
27
  cos: Math.cos(angle),
28
- }, transformData = {
29
- a: rotateData.cos * (transform.a ?? 1),
30
- b: rotateData.sin * (transform.b ?? 1),
31
- c: -rotateData.sin * (transform.c ?? 1),
32
- d: rotateData.cos * (transform.d ?? 1),
28
+ }, defaultTransformFactor = 1, transformData = {
29
+ a: rotateData.cos * (transform.a ?? defaultTransformFactor),
30
+ b: rotateData.sin * (transform.b ?? defaultTransformFactor),
31
+ c: -rotateData.sin * (transform.c ?? defaultTransformFactor),
32
+ d: rotateData.cos * (transform.d ?? defaultTransformFactor),
33
33
  };
34
34
  context.setTransform(transformData.a, transformData.b, transformData.c, transformData.d, pos.x, pos.y);
35
35
  if (backgroundMask) {
@@ -45,27 +45,26 @@ export function drawParticle(data) {
45
45
  if (colorStyles.fill) {
46
46
  context.fillStyle = colorStyles.fill;
47
47
  }
48
- const strokeWidth = particle.strokeWidth ?? 0;
48
+ const minStrokeWidth = 0, strokeWidth = particle.strokeWidth ?? minStrokeWidth;
49
49
  context.lineWidth = strokeWidth;
50
50
  if (colorStyles.stroke) {
51
51
  context.strokeStyle = colorStyles.stroke;
52
52
  }
53
- const drawData = { container, context, particle, radius, opacity, delta, transformData };
54
- context.beginPath();
53
+ const drawData = {
54
+ container,
55
+ context,
56
+ particle,
57
+ radius,
58
+ opacity,
59
+ delta,
60
+ transformData,
61
+ strokeWidth,
62
+ };
55
63
  drawShape(drawData);
56
- if (particle.shapeClose) {
57
- context.closePath();
58
- }
59
- if (strokeWidth > 0) {
60
- context.stroke();
61
- }
62
- if (particle.shapeFill) {
63
- context.fill();
64
- }
65
64
  drawShapeAfterDraw(drawData);
66
65
  drawEffect(drawData);
67
66
  context.globalCompositeOperation = "source-over";
68
- context.setTransform(1, 0, 0, 1, 0, 0);
67
+ context.resetTransform();
69
68
  }
70
69
  export function drawEffect(data) {
71
70
  const { container, context, particle, radius, opacity, delta, transformData } = data;
@@ -87,7 +86,7 @@ export function drawEffect(data) {
87
86
  });
88
87
  }
89
88
  export function drawShape(data) {
90
- const { container, context, particle, radius, opacity, delta, transformData } = data;
89
+ const { container, context, particle, radius, opacity, delta, strokeWidth, transformData } = data, minStrokeWidth = 0;
91
90
  if (!particle.shape) {
92
91
  return;
93
92
  }
@@ -95,6 +94,7 @@ export function drawShape(data) {
95
94
  if (!drawer) {
96
95
  return;
97
96
  }
97
+ context.beginPath();
98
98
  drawer.draw({
99
99
  context,
100
100
  particle,
@@ -104,6 +104,15 @@ export function drawShape(data) {
104
104
  pixelRatio: container.retina.pixelRatio,
105
105
  transformData: { ...transformData },
106
106
  });
107
+ if (particle.shapeClose) {
108
+ context.closePath();
109
+ }
110
+ if (strokeWidth > minStrokeWidth) {
111
+ context.stroke();
112
+ }
113
+ if (particle.shapeFill) {
114
+ context.fill();
115
+ }
107
116
  }
108
117
  export function drawShapeAfterDraw(data) {
109
118
  const { container, context, particle, radius, opacity, delta, transformData } = data;
@@ -111,7 +120,7 @@ export function drawShapeAfterDraw(data) {
111
120
  return;
112
121
  }
113
122
  const drawer = container.shapeDrawers.get(particle.shape);
114
- if (!drawer || !drawer.afterDraw) {
123
+ if (!drawer?.afterDraw) {
115
124
  return;
116
125
  }
117
126
  drawer.afterDraw({
@@ -137,9 +146,10 @@ export function drawParticlePlugin(context, plugin, particle, delta) {
137
146
  plugin.drawParticle(context, particle, delta);
138
147
  }
139
148
  export function alterHsl(color, type, value) {
149
+ const lFactor = 1;
140
150
  return {
141
151
  h: color.h,
142
152
  s: color.s,
143
- l: color.l + (type === "darken" ? -1 : 1) * value,
153
+ l: color.l + (type === "darken" ? -lFactor : lFactor) * value,
144
154
  };
145
155
  }