@tsparticles/engine 4.0.0-beta.15 → 4.0.0-beta.17

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 (42) hide show
  1. package/browser/Core/CanvasManager.js +73 -36
  2. package/browser/Core/Engine.js +11 -5
  3. package/browser/Core/Particle.js +1 -3
  4. package/browser/Core/ParticlesManager.js +157 -78
  5. package/browser/Core/Retina.js +2 -3
  6. package/browser/Core/Utils/EventListeners.js +1 -1
  7. package/cjs/Core/CanvasManager.js +73 -36
  8. package/cjs/Core/Engine.js +11 -5
  9. package/cjs/Core/Particle.js +1 -3
  10. package/cjs/Core/ParticlesManager.js +157 -78
  11. package/cjs/Core/Retina.js +2 -3
  12. package/cjs/Core/Utils/EventListeners.js +1 -1
  13. package/esm/Core/CanvasManager.js +73 -36
  14. package/esm/Core/Engine.js +11 -5
  15. package/esm/Core/Particle.js +1 -3
  16. package/esm/Core/ParticlesManager.js +157 -78
  17. package/esm/Core/Retina.js +2 -3
  18. package/esm/Core/Utils/EventListeners.js +1 -1
  19. package/package.json +1 -1
  20. package/report.html +1 -1
  21. package/scripts/install.js +321 -220
  22. package/tsparticles.engine.js +245 -126
  23. package/tsparticles.engine.min.js +1 -1
  24. package/types/Core/CanvasManager.d.ts +3 -2
  25. package/types/Core/Container.d.ts +1 -2
  26. package/types/Core/Interfaces/IContainerPlugin.d.ts +7 -8
  27. package/types/Core/Interfaces/IDrawParticleParams.d.ts +1 -2
  28. package/types/Core/Interfaces/ILoadParams.d.ts +1 -1
  29. package/types/Core/Interfaces/IParticleUpdater.d.ts +1 -2
  30. package/types/Core/Interfaces/IShapeDrawData.d.ts +1 -2
  31. package/types/Core/ParticlesManager.d.ts +11 -5
  32. package/types/Core/RenderManager.d.ts +2 -3
  33. package/types/Core/Utils/PluginManager.d.ts +5 -6
  34. package/types/Options/Classes/Options.d.ts +1 -2
  35. package/types/Utils/CanvasUtils.d.ts +4 -5
  36. package/types/Utils/LogUtils.d.ts +1 -2
  37. package/types/Utils/Utils.d.ts +1 -2
  38. package/types/export-types.d.ts +0 -1
  39. package/browser/Types/CanvasContextType.js +0 -1
  40. package/cjs/Types/CanvasContextType.js +0 -1
  41. package/esm/Types/CanvasContextType.js +0 -1
  42. package/types/Types/CanvasContextType.d.ts +0 -1
@@ -1,5 +1,5 @@
1
1
  (function(g){g.__tsParticlesInternals=g.__tsParticlesInternals||{};g.__tsParticlesInternals.bundles=g.__tsParticlesInternals.bundles||{};g.__tsParticlesInternals.effects=g.__tsParticlesInternals.effects||{};g.__tsParticlesInternals.engine=g.__tsParticlesInternals.engine||{};g.__tsParticlesInternals.interactions=g.__tsParticlesInternals.interactions||{};g.__tsParticlesInternals.palettes=g.__tsParticlesInternals.palettes||{};g.__tsParticlesInternals.paths=g.__tsParticlesInternals.paths||{};g.__tsParticlesInternals.plugins=g.__tsParticlesInternals.plugins||{};g.__tsParticlesInternals.plugins=g.__tsParticlesInternals.plugins||{};g.__tsParticlesInternals.plugins.emittersShapes=g.__tsParticlesInternals.plugins.emittersShapes||{};g.__tsParticlesInternals.presets=g.__tsParticlesInternals.presets||{};g.__tsParticlesInternals.shapes=g.__tsParticlesInternals.shapes||{};g.__tsParticlesInternals.updaters=g.__tsParticlesInternals.updaters||{};g.__tsParticlesInternals.utils=g.__tsParticlesInternals.utils||{};g.__tsParticlesInternals.canvas=g.__tsParticlesInternals.canvas||{};g.__tsParticlesInternals.canvas=g.__tsParticlesInternals.canvas||{};g.__tsParticlesInternals.canvas.utils=g.__tsParticlesInternals.canvas.utils||{};g.__tsParticlesInternals.path=g.__tsParticlesInternals.path||{};g.__tsParticlesInternals.path=g.__tsParticlesInternals.path||{};g.__tsParticlesInternals.path.utils=g.__tsParticlesInternals.path.utils||{};var __tsProxyFactory=typeof Proxy!=="undefined"?function(obj){return new Proxy(obj,{get:function(target,key){if(!(key in target)){target[key]={};}return target[key];}});}:function(obj){return obj;};g.__tsParticlesInternals.bundles=__tsProxyFactory(g.__tsParticlesInternals.bundles);g.__tsParticlesInternals.effects=__tsProxyFactory(g.__tsParticlesInternals.effects);g.__tsParticlesInternals.interactions=__tsProxyFactory(g.__tsParticlesInternals.interactions);g.__tsParticlesInternals.palettes=__tsProxyFactory(g.__tsParticlesInternals.palettes);g.__tsParticlesInternals.paths=__tsProxyFactory(g.__tsParticlesInternals.paths);g.__tsParticlesInternals.plugins=__tsProxyFactory(g.__tsParticlesInternals.plugins);g.__tsParticlesInternals.plugins.emittersShapes=__tsProxyFactory(g.__tsParticlesInternals.plugins.emittersShapes);g.__tsParticlesInternals.presets=__tsProxyFactory(g.__tsParticlesInternals.presets);g.__tsParticlesInternals.shapes=__tsProxyFactory(g.__tsParticlesInternals.shapes);g.__tsParticlesInternals.updaters=__tsProxyFactory(g.__tsParticlesInternals.updaters);g.__tsParticlesInternals.utils=__tsProxyFactory(g.__tsParticlesInternals.utils);g.__tsParticlesInternals.canvas=__tsProxyFactory(g.__tsParticlesInternals.canvas);g.__tsParticlesInternals.path=__tsProxyFactory(g.__tsParticlesInternals.path);g.tsparticlesInternalExports=g.tsparticlesInternalExports||{};})(typeof globalThis!=="undefined"?globalThis:typeof window!=="undefined"?window:this);
2
- /* tsParticles Engine v4.0.0-beta.15 */
2
+ /* tsParticles Engine v4.0.0-beta.17 */
3
3
  (function (global, factory) {
4
4
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
5
5
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
@@ -1081,7 +1081,7 @@
1081
1081
  }
1082
1082
  }
1083
1083
  else {
1084
- const existingCanvases = domContainer.getElementsByTagName(canvasTag), foundCanvas = existingCanvases[canvasFirstIndex];
1084
+ const existingCanvases = domContainer.getElementsByTagName(canvasTag), foundCanvas = existingCanvases.item(canvasFirstIndex);
1085
1085
  if (foundCanvas) {
1086
1086
  canvasEl = foundCanvas;
1087
1087
  canvasEl.dataset[generatedAttribute] = generatedFalse;
@@ -1118,7 +1118,7 @@
1118
1118
  return this._domArray;
1119
1119
  }
1120
1120
  get version() {
1121
- return "4.0.0-beta.15";
1121
+ return "4.0.0-beta.17";
1122
1122
  }
1123
1123
  addEventListener(type, listener) {
1124
1124
  this._eventDispatcher.addEventListener(type, listener);
@@ -1149,7 +1149,11 @@
1149
1149
  }
1150
1150
  async load(params) {
1151
1151
  await this.init();
1152
- const { Container } = await Promise.resolve().then(function () { return Container$1; }), id = params.id ?? params.element?.id ?? `tsparticles${Math.floor(getRandom() * loadRandomFactor).toString()}`, { index, url } = params, options = url ? await getDataFromUrl({ fallback: params.options, url, index }) : params.options, currentOptions = itemFromSingleOrMultiple(options, index), { items } = this, oldIndex = items.findIndex(v => v.id.description === id), newItem = new Container({
1152
+ let domSourceElement;
1153
+ if (typeof HTMLElement !== "undefined" && params.element instanceof HTMLElement) {
1154
+ domSourceElement = params.element;
1155
+ }
1156
+ const { Container } = await Promise.resolve().then(function () { return Container$1; }), id = params.id ?? domSourceElement?.id ?? `tsparticles${Math.floor(getRandom() * loadRandomFactor).toString()}`, { index, url } = params, options = url ? await getDataFromUrl({ fallback: params.options, url, index }) : params.options, currentOptions = itemFromSingleOrMultiple(options, index), { items } = this, oldIndex = items.findIndex(v => v.id.description === id), newItem = new Container({
1153
1157
  dispatchCallback: (eventType, args) => {
1154
1158
  this.dispatchEvent(eventType, args);
1155
1159
  },
@@ -1176,8 +1180,10 @@
1176
1180
  else {
1177
1181
  items.push(newItem);
1178
1182
  }
1179
- const domContainer = getDomContainer(id, params.element), canvasEl = getCanvasFromContainer(domContainer);
1180
- newItem.canvas.loadCanvas(canvasEl);
1183
+ const sourceCanvas = typeof OffscreenCanvas !== "undefined" && params.element instanceof OffscreenCanvas
1184
+ ? params.element
1185
+ : getCanvasFromContainer(getDomContainer(id, domSourceElement));
1186
+ newItem.canvas.loadCanvas(sourceCanvas);
1181
1187
  await newItem.start();
1182
1188
  return newItem;
1183
1189
  }
@@ -3308,6 +3314,25 @@
3308
3314
  };
3309
3315
  }
3310
3316
 
3317
+ const transferredCanvases = new WeakMap(), getTransferredCanvas = (canvas) => {
3318
+ const transferredCanvas = transferredCanvases.get(canvas);
3319
+ if (transferredCanvas) {
3320
+ return transferredCanvas;
3321
+ }
3322
+ if (typeof canvas.transferControlToOffscreen !== "function") {
3323
+ throw new TypeError("OffscreenCanvas is required but not supported by this browser");
3324
+ }
3325
+ try {
3326
+ const offscreenCanvas = canvas.transferControlToOffscreen();
3327
+ transferredCanvases.set(canvas, offscreenCanvas);
3328
+ return offscreenCanvas;
3329
+ }
3330
+ catch {
3331
+ throw new TypeError("OffscreenCanvas transfer failed");
3332
+ }
3333
+ }, isHtmlCanvasElement = (canvas) => {
3334
+ return typeof HTMLCanvasElement !== "undefined" && canvas instanceof HTMLCanvasElement;
3335
+ };
3311
3336
  function setStyle(canvas, style, important = false) {
3312
3337
  if (!style) {
3313
3338
  return;
@@ -3338,8 +3363,9 @@
3338
3363
  }
3339
3364
  }
3340
3365
  class CanvasManager {
3341
- element;
3366
+ domElement;
3342
3367
  render;
3368
+ renderCanvas;
3343
3369
  size;
3344
3370
  zoom = defaultZoom;
3345
3371
  _container;
@@ -3374,9 +3400,10 @@
3374
3400
  destroy() {
3375
3401
  this.stop();
3376
3402
  if (this._generated) {
3377
- const element = this.element;
3403
+ const element = this.domElement;
3378
3404
  element?.remove();
3379
- this.element = undefined;
3405
+ this.domElement = undefined;
3406
+ this.renderCanvas = undefined;
3380
3407
  }
3381
3408
  else {
3382
3409
  this._resetOriginalStyle();
@@ -3409,16 +3436,17 @@
3409
3436
  this._initStyle();
3410
3437
  this.initBackground();
3411
3438
  this._safeMutationObserver(obs => {
3412
- if (!this.element || !(this.element instanceof Node)) {
3439
+ const element = this.domElement;
3440
+ if (!element || !(element instanceof Node)) {
3413
3441
  return;
3414
3442
  }
3415
- obs.observe(this.element, { attributes: true });
3443
+ obs.observe(element, { attributes: true });
3416
3444
  });
3417
3445
  this.initPlugins();
3418
3446
  this.render.init();
3419
3447
  }
3420
3448
  initBackground() {
3421
- const { _container } = this, options = _container.actualOptions, background = options.background, element = this.element;
3449
+ const { _container } = this, options = _container.actualOptions, background = options.background, element = this.domElement;
3422
3450
  if (!element) {
3423
3451
  return;
3424
3452
  }
@@ -3443,21 +3471,30 @@
3443
3471
  }
3444
3472
  }
3445
3473
  loadCanvas(canvas) {
3446
- if (this._generated && this.element) {
3447
- this.element.remove();
3474
+ if (this._generated && this.domElement) {
3475
+ this.domElement.remove();
3476
+ }
3477
+ const container = this._container, domCanvas = isHtmlCanvasElement(canvas) ? canvas : undefined;
3478
+ this.domElement = domCanvas;
3479
+ this._generated = domCanvas ? domCanvas.dataset[generatedAttribute] === "true" : false;
3480
+ this.renderCanvas = domCanvas ? getTransferredCanvas(domCanvas) : canvas;
3481
+ const domElement = this.domElement;
3482
+ if (domElement) {
3483
+ domElement.ariaHidden = "true";
3484
+ this._originalStyle = cloneStyle(domElement.style);
3485
+ }
3486
+ const standardSize = this._standardSize, renderCanvas = this.renderCanvas;
3487
+ if (domElement) {
3488
+ standardSize.height = domElement.offsetHeight;
3489
+ standardSize.width = domElement.offsetWidth;
3490
+ }
3491
+ else {
3492
+ standardSize.height = renderCanvas.height;
3493
+ standardSize.width = renderCanvas.width;
3448
3494
  }
3449
- const container = this._container;
3450
- this._generated =
3451
- generatedAttribute in canvas.dataset ? canvas.dataset[generatedAttribute] === "true" : this._generated;
3452
- this.element = canvas;
3453
- this.element.ariaHidden = "true";
3454
- this._originalStyle = cloneStyle(this.element.style);
3455
- const standardSize = this._standardSize;
3456
- standardSize.height = canvas.offsetHeight;
3457
- standardSize.width = canvas.offsetWidth;
3458
3495
  const pxRatio = this._container.retina.pixelRatio, retinaSize = this.size;
3459
- canvas.height = retinaSize.height = standardSize.height * pxRatio;
3460
- canvas.width = retinaSize.width = standardSize.width * pxRatio;
3496
+ renderCanvas.height = retinaSize.height = standardSize.height * pxRatio;
3497
+ renderCanvas.width = retinaSize.width = standardSize.width * pxRatio;
3461
3498
  const canSupportHdrQuery = safeMatchMedia("(color-gamut: p3)");
3462
3499
  this.render.setContextSettings({
3463
3500
  alpha: true,
@@ -3465,42 +3502,48 @@
3465
3502
  desynchronized: true,
3466
3503
  willReadFrequently: false,
3467
3504
  });
3468
- this.render.setContext(this.element.getContext("2d", this.render.settings));
3505
+ this.render.setContext(renderCanvas.getContext("2d", this.render.settings));
3469
3506
  this._safeMutationObserver(obs => {
3470
3507
  obs.disconnect();
3471
3508
  });
3472
3509
  container.retina.init();
3473
3510
  this.initBackground();
3474
3511
  this._safeMutationObserver(obs => {
3475
- if (!this.element || !(this.element instanceof Node)) {
3512
+ const element = this.domElement;
3513
+ if (!element || !(element instanceof Node)) {
3476
3514
  return;
3477
3515
  }
3478
- obs.observe(this.element, { attributes: true });
3516
+ obs.observe(element, { attributes: true });
3479
3517
  });
3480
3518
  }
3481
3519
  resize() {
3482
- if (!this.element) {
3520
+ const element = this.domElement;
3521
+ if (!element) {
3522
+ return false;
3523
+ }
3524
+ const container = this._container, renderCanvas = this.renderCanvas;
3525
+ if (renderCanvas === undefined) {
3483
3526
  return false;
3484
3527
  }
3485
- const container = this._container, currentSize = container.canvas._standardSize, newSize = {
3486
- width: this.element.offsetWidth,
3487
- height: this.element.offsetHeight,
3528
+ const currentSize = container.canvas._standardSize, newSize = {
3529
+ width: element.offsetWidth,
3530
+ height: element.offsetHeight,
3488
3531
  }, pxRatio = container.retina.pixelRatio, retinaSize = {
3489
3532
  width: newSize.width * pxRatio,
3490
3533
  height: newSize.height * pxRatio,
3491
3534
  };
3492
3535
  if (newSize.height === currentSize.height &&
3493
3536
  newSize.width === currentSize.width &&
3494
- retinaSize.height === this.element.height &&
3495
- retinaSize.width === this.element.width) {
3537
+ retinaSize.height === renderCanvas.height &&
3538
+ retinaSize.width === renderCanvas.width) {
3496
3539
  return false;
3497
3540
  }
3498
3541
  const oldSize = { ...currentSize };
3499
3542
  currentSize.height = newSize.height;
3500
3543
  currentSize.width = newSize.width;
3501
3544
  const canvasSize = this.size;
3502
- this.element.width = canvasSize.width = retinaSize.width;
3503
- this.element.height = canvasSize.height = retinaSize.height;
3545
+ renderCanvas.width = canvasSize.width = retinaSize.width;
3546
+ renderCanvas.height = canvasSize.height = retinaSize.height;
3504
3547
  if (this._container.started) {
3505
3548
  container.particles.setResizeFactor({
3506
3549
  width: currentSize.width / oldSize.width,
@@ -3510,7 +3553,7 @@
3510
3553
  return true;
3511
3554
  }
3512
3555
  setPointerEvents(type) {
3513
- const element = this.element;
3556
+ const element = this.domElement;
3514
3557
  if (!element) {
3515
3558
  return;
3516
3559
  }
@@ -3529,7 +3572,7 @@
3529
3572
  this.render.stop();
3530
3573
  }
3531
3574
  async windowResize() {
3532
- if (!this.element || !this.resize()) {
3575
+ if (!this.domElement || !this.resize()) {
3533
3576
  return;
3534
3577
  }
3535
3578
  const container = this._container, needsRefresh = container.updateActualOptions();
@@ -3545,7 +3588,7 @@
3545
3588
  }
3546
3589
  };
3547
3590
  _initStyle = () => {
3548
- const element = this.element, options = this._container.actualOptions;
3591
+ const element = this.domElement, options = this._container.actualOptions;
3549
3592
  if (!element) {
3550
3593
  return;
3551
3594
  }
@@ -3567,7 +3610,7 @@
3567
3610
  }
3568
3611
  };
3569
3612
  _repairStyle = () => {
3570
- const element = this.element;
3613
+ const element = this.domElement;
3571
3614
  if (!element) {
3572
3615
  return;
3573
3616
  }
@@ -3587,7 +3630,7 @@
3587
3630
  });
3588
3631
  };
3589
3632
  _resetOriginalStyle = () => {
3590
- const element = this.element, originalStyle = this._originalStyle;
3633
+ const element = this.domElement, originalStyle = this._originalStyle;
3591
3634
  if (!element || !originalStyle) {
3592
3635
  return;
3593
3636
  }
@@ -3600,7 +3643,7 @@
3600
3643
  callback(this._mutationObserver);
3601
3644
  };
3602
3645
  _setFullScreenStyle = () => {
3603
- const element = this.element;
3646
+ const element = this.domElement;
3604
3647
  if (!element) {
3605
3648
  return;
3606
3649
  }
@@ -3674,7 +3717,7 @@
3674
3717
  manageListener(globalThis, resizeEvent, handlers.resize, add);
3675
3718
  return;
3676
3719
  }
3677
- const canvasEl = container.canvas.element;
3720
+ const canvasEl = container.canvas.domElement;
3678
3721
  if (this._resizeObserver && !add) {
3679
3722
  if (canvasEl) {
3680
3723
  this._resizeObserver.unobserve(canvasEl);
@@ -3929,8 +3972,6 @@
3929
3972
  this._initPosition(position);
3930
3973
  this.initialVelocity = this._calculateVelocity();
3931
3974
  this.velocity = this.initialVelocity.copy();
3932
- const particles = container.particles;
3933
- particles.setLastZIndex(this.position.z);
3934
3975
  this.zIndexFactor = this.position.z / container.zLayers;
3935
3976
  this.sides = 24;
3936
3977
  let effectDrawer, shapeDrawer;
@@ -4080,7 +4121,7 @@
4080
4121
  return color;
4081
4122
  };
4082
4123
  _initPosition = position => {
4083
- const container = this._container, zIndexValue = getRangeValue(this.options.zIndex.value), initialPosition = this._calcPosition(position, clamp(zIndexValue, minZ, container.zLayers));
4124
+ const container = this._container, zIndexValue = Math.floor(getRangeValue(this.options.zIndex.value)), initialPosition = this._calcPosition(position, clamp(zIndexValue, minZ, container.zLayers));
4084
4125
  if (!initialPosition) {
4085
4126
  throw new Error("a valid position cannot be found for particle");
4086
4127
  }
@@ -4214,10 +4255,8 @@
4214
4255
  _container;
4215
4256
  _groupLimits;
4216
4257
  _limit;
4217
- _maxZIndex;
4218
- _minZIndex;
4219
- _needsSort;
4220
4258
  _nextId;
4259
+ _particleBuckets;
4221
4260
  _particleResetPlugins;
4222
4261
  _particleUpdatePlugins;
4223
4262
  _pluginManager;
@@ -4226,19 +4265,17 @@
4226
4265
  _postUpdatePlugins;
4227
4266
  _resizeFactor;
4228
4267
  _updatePlugins;
4229
- _zArray;
4268
+ _zBuckets;
4230
4269
  constructor(pluginManager, container) {
4231
4270
  this._pluginManager = pluginManager;
4232
4271
  this._container = container;
4233
4272
  this._nextId = 0;
4234
4273
  this._array = [];
4235
- this._zArray = [];
4236
4274
  this._pool = [];
4237
4275
  this._limit = 0;
4238
4276
  this._groupLimits = new Map();
4239
- this._needsSort = false;
4240
- this._minZIndex = 0;
4241
- this._maxZIndex = 0;
4277
+ this._particleBuckets = new Map();
4278
+ this._zBuckets = this._createBuckets(this._container.zLayers);
4242
4279
  this.grid = new SpatialHashGrid(spatialHashGridCellSize);
4243
4280
  this.checkParticlePositionPlugins = [];
4244
4281
  this._particleResetPlugins = [];
@@ -4280,7 +4317,7 @@
4280
4317
  return;
4281
4318
  }
4282
4319
  this._array.push(particle);
4283
- this._zArray.push(particle);
4320
+ this._insertParticleIntoBucket(particle);
4284
4321
  this._nextId++;
4285
4322
  this._container.dispatchEvent(exports.EventType.particleAdded, {
4286
4323
  particle,
@@ -4294,12 +4331,14 @@
4294
4331
  }
4295
4332
  clear() {
4296
4333
  this._array = [];
4297
- this._zArray = [];
4334
+ this._particleBuckets.clear();
4335
+ this._resetBuckets(this._container.zLayers);
4298
4336
  }
4299
4337
  destroy() {
4300
4338
  this._array = [];
4301
4339
  this._pool.length = 0;
4302
- this._zArray = [];
4340
+ this._particleBuckets.clear();
4341
+ this._zBuckets = [];
4303
4342
  this.checkParticlePositionPlugins = [];
4304
4343
  this._particleResetPlugins = [];
4305
4344
  this._particleUpdatePlugins = [];
@@ -4308,8 +4347,14 @@
4308
4347
  this._updatePlugins = [];
4309
4348
  }
4310
4349
  drawParticles(delta) {
4311
- for (const particle of this._zArray) {
4312
- particle.draw(delta);
4350
+ for (let i = this._zBuckets.length - one; i >= minIndex; i--) {
4351
+ const bucket = this._zBuckets[i];
4352
+ if (!bucket) {
4353
+ continue;
4354
+ }
4355
+ for (const particle of bucket) {
4356
+ particle.draw(delta);
4357
+ }
4313
4358
  }
4314
4359
  }
4315
4360
  filter(condition) {
@@ -4323,15 +4368,14 @@
4323
4368
  }
4324
4369
  async init() {
4325
4370
  const container = this._container, options = container.actualOptions;
4326
- this._minZIndex = 0;
4327
- this._maxZIndex = 0;
4328
- this._needsSort = false;
4329
4371
  this.checkParticlePositionPlugins = [];
4330
4372
  this._updatePlugins = [];
4331
4373
  this._particleUpdatePlugins = [];
4332
4374
  this._postUpdatePlugins = [];
4333
4375
  this._particleResetPlugins = [];
4334
4376
  this._postParticleUpdatePlugins = [];
4377
+ this._particleBuckets.clear();
4378
+ this._resetBuckets(container.zLayers);
4335
4379
  this.grid = new SpatialHashGrid(spatialHashGridCellSize * container.retina.pixelRatio);
4336
4380
  for (const plugin of container.plugins) {
4337
4381
  if (plugin.redrawInit) {
@@ -4432,79 +4476,25 @@
4432
4476
  }
4433
4477
  this._applyDensity(options.particles, pluginsCount);
4434
4478
  }
4435
- setLastZIndex(zIndex) {
4436
- this._needsSort ||= zIndex >= this._maxZIndex || (zIndex > this._minZIndex && zIndex < this._maxZIndex);
4437
- }
4438
4479
  setResizeFactor(factor) {
4439
4480
  this._resizeFactor = factor;
4440
4481
  }
4441
4482
  update(delta) {
4442
- const particlesToDelete = new Set();
4443
4483
  this.grid.clear();
4444
4484
  for (const plugin of this._updatePlugins) {
4445
4485
  plugin.update?.(delta);
4446
4486
  }
4447
- const resizeFactor = this._resizeFactor;
4448
- for (const particle of this._array) {
4449
- if (resizeFactor && !particle.ignoresResizeRatio) {
4450
- particle.position.x *= resizeFactor.width;
4451
- particle.position.y *= resizeFactor.height;
4452
- particle.initialPosition.x *= resizeFactor.width;
4453
- particle.initialPosition.y *= resizeFactor.height;
4454
- }
4455
- particle.ignoresResizeRatio = false;
4456
- for (const plugin of this._particleResetPlugins) {
4457
- plugin.particleReset?.(particle);
4458
- }
4459
- for (const plugin of this._particleUpdatePlugins) {
4460
- if (particle.destroyed) {
4461
- break;
4462
- }
4463
- plugin.particleUpdate?.(particle, delta);
4464
- }
4465
- if (particle.destroyed) {
4466
- particlesToDelete.add(particle);
4467
- continue;
4468
- }
4469
- this.grid.insert(particle);
4470
- }
4487
+ const particlesToDelete = this._updateParticlesPhase1(delta);
4471
4488
  for (const plugin of this._postUpdatePlugins) {
4472
4489
  plugin.postUpdate?.(delta);
4473
4490
  }
4474
- for (const particle of this._array) {
4475
- if (particle.destroyed) {
4476
- particlesToDelete.add(particle);
4477
- continue;
4478
- }
4479
- for (const updater of this._container.particleUpdaters) {
4480
- updater.update(particle, delta);
4481
- }
4482
- if (!particle.destroyed && !particle.spawning) {
4483
- for (const plugin of this._postParticleUpdatePlugins) {
4484
- plugin.postParticleUpdate?.(particle, delta);
4485
- }
4486
- }
4487
- else if (particle.destroyed) {
4488
- particlesToDelete.add(particle);
4489
- }
4490
- }
4491
+ this._updateParticlesPhase2(delta, particlesToDelete);
4491
4492
  if (particlesToDelete.size) {
4492
4493
  for (const particle of particlesToDelete) {
4493
4494
  this.remove(particle);
4494
4495
  }
4495
4496
  }
4496
4497
  delete this._resizeFactor;
4497
- if (this._needsSort) {
4498
- const zArray = this._zArray;
4499
- zArray.sort((a, b) => b.position.z - a.position.z || a.id - b.id);
4500
- const firstItem = zArray[minIndex], lastItem = zArray[zArray.length - lengthOffset];
4501
- if (!firstItem || !lastItem) {
4502
- return;
4503
- }
4504
- this._maxZIndex = firstItem.position.z;
4505
- this._minZIndex = lastItem.position.z;
4506
- this._needsSort = false;
4507
- }
4508
4498
  }
4509
4499
  _addToPool = (...particles) => {
4510
4500
  this._pool.push(...particles);
@@ -4534,13 +4524,52 @@
4534
4524
  this.removeQuantity(particlesCount - particlesNumber, group);
4535
4525
  }
4536
4526
  };
4527
+ _createBuckets = (zLayers) => {
4528
+ const bucketCount = Math.max(Math.floor(zLayers), one);
4529
+ return Array.from({ length: bucketCount }, () => []);
4530
+ };
4531
+ _getBucketIndex = (zIndex) => {
4532
+ const maxBucketIndex = this._zBuckets.length - one;
4533
+ if (maxBucketIndex <= minIndex) {
4534
+ return minIndex;
4535
+ }
4536
+ return Math.min(Math.max(Math.floor(zIndex), minIndex), maxBucketIndex);
4537
+ };
4538
+ _getParticleInsertIndex = (bucket, particleId) => {
4539
+ let start = minIndex, end = bucket.length;
4540
+ while (start < end) {
4541
+ const middle = Math.floor((start + end) / double), middleParticle = bucket[middle];
4542
+ if (!middleParticle) {
4543
+ end = middle;
4544
+ continue;
4545
+ }
4546
+ if (middleParticle.id < particleId) {
4547
+ start = middle + one;
4548
+ }
4549
+ else {
4550
+ end = middle;
4551
+ }
4552
+ }
4553
+ return start;
4554
+ };
4537
4555
  _initDensityFactor = densityOptions => {
4538
4556
  const container = this._container;
4539
- if (!container.canvas.element || !densityOptions.enable) {
4557
+ if (!densityOptions.enable) {
4558
+ return defaultDensityFactor;
4559
+ }
4560
+ const canvasSize = container.canvas.size, pxRatio = container.retina.pixelRatio;
4561
+ if (!canvasSize.width || !canvasSize.height) {
4540
4562
  return defaultDensityFactor;
4541
4563
  }
4542
- const canvas = container.canvas.element, pxRatio = container.retina.pixelRatio;
4543
- return (canvas.width * canvas.height) / (densityOptions.height * densityOptions.width * pxRatio ** squareExp);
4564
+ return ((canvasSize.width * canvasSize.height) / (densityOptions.height * densityOptions.width * pxRatio ** squareExp));
4565
+ };
4566
+ _insertParticleIntoBucket = (particle) => {
4567
+ const bucketIndex = this._getBucketIndex(particle.position.z), bucket = this._zBuckets[bucketIndex];
4568
+ if (!bucket) {
4569
+ return;
4570
+ }
4571
+ bucket.splice(this._getParticleInsertIndex(bucket, particle.id), empty, particle);
4572
+ this._particleBuckets.set(particle.id, bucketIndex);
4544
4573
  };
4545
4574
  _removeParticle = (index, group, override) => {
4546
4575
  const particle = this._array[index];
@@ -4550,9 +4579,8 @@
4550
4579
  if (particle.group !== group) {
4551
4580
  return false;
4552
4581
  }
4553
- const zIdx = this._zArray.indexOf(particle);
4554
4582
  this._array.splice(index, deleteCount);
4555
- this._zArray.splice(zIdx, deleteCount);
4583
+ this._removeParticleFromBucket(particle);
4556
4584
  particle.destroy(override);
4557
4585
  this._container.dispatchEvent(exports.EventType.particleRemoved, {
4558
4586
  particle,
@@ -4560,6 +4588,98 @@
4560
4588
  this._addToPool(particle);
4561
4589
  return true;
4562
4590
  };
4591
+ _removeParticleFromBucket = (particle) => {
4592
+ const bucketIndex = this._particleBuckets.get(particle.id) ?? this._getBucketIndex(particle.position.z), bucket = this._zBuckets[bucketIndex];
4593
+ if (!bucket) {
4594
+ this._particleBuckets.delete(particle.id);
4595
+ return;
4596
+ }
4597
+ const particleIndex = this._getParticleInsertIndex(bucket, particle.id);
4598
+ if (bucket[particleIndex]?.id !== particle.id) {
4599
+ this._particleBuckets.delete(particle.id);
4600
+ return;
4601
+ }
4602
+ bucket.splice(particleIndex, deleteCount);
4603
+ this._particleBuckets.delete(particle.id);
4604
+ };
4605
+ _resetBuckets = (zLayers) => {
4606
+ const bucketCount = Math.max(Math.floor(zLayers), one);
4607
+ if (this._zBuckets.length !== bucketCount) {
4608
+ this._zBuckets = this._createBuckets(bucketCount);
4609
+ return;
4610
+ }
4611
+ for (const bucket of this._zBuckets) {
4612
+ bucket.length = minIndex;
4613
+ }
4614
+ };
4615
+ _updateParticleBucket = (particle) => {
4616
+ const newBucketIndex = this._getBucketIndex(particle.position.z), currentBucketIndex = this._particleBuckets.get(particle.id);
4617
+ if (currentBucketIndex === undefined) {
4618
+ this._insertParticleIntoBucket(particle);
4619
+ return;
4620
+ }
4621
+ if (currentBucketIndex === newBucketIndex) {
4622
+ return;
4623
+ }
4624
+ const currentBucket = this._zBuckets[currentBucketIndex];
4625
+ if (currentBucket) {
4626
+ const particleIndex = this._getParticleInsertIndex(currentBucket, particle.id);
4627
+ if (currentBucket[particleIndex]?.id === particle.id) {
4628
+ currentBucket.splice(particleIndex, deleteCount);
4629
+ }
4630
+ }
4631
+ const newBucket = this._zBuckets[newBucketIndex];
4632
+ if (!newBucket) {
4633
+ this._particleBuckets.set(particle.id, newBucketIndex);
4634
+ return;
4635
+ }
4636
+ newBucket.splice(this._getParticleInsertIndex(newBucket, particle.id), empty, particle);
4637
+ this._particleBuckets.set(particle.id, newBucketIndex);
4638
+ };
4639
+ _updateParticlesPhase1 = (delta) => {
4640
+ const particlesToDelete = new Set(), resizeFactor = this._resizeFactor;
4641
+ for (const particle of this._array) {
4642
+ if (resizeFactor && !particle.ignoresResizeRatio) {
4643
+ particle.position.x *= resizeFactor.width;
4644
+ particle.position.y *= resizeFactor.height;
4645
+ particle.initialPosition.x *= resizeFactor.width;
4646
+ particle.initialPosition.y *= resizeFactor.height;
4647
+ }
4648
+ particle.ignoresResizeRatio = false;
4649
+ for (const plugin of this._particleResetPlugins) {
4650
+ plugin.particleReset?.(particle);
4651
+ }
4652
+ for (const plugin of this._particleUpdatePlugins) {
4653
+ if (particle.destroyed) {
4654
+ break;
4655
+ }
4656
+ plugin.particleUpdate?.(particle, delta);
4657
+ }
4658
+ if (particle.destroyed) {
4659
+ particlesToDelete.add(particle);
4660
+ continue;
4661
+ }
4662
+ this.grid.insert(particle);
4663
+ }
4664
+ return particlesToDelete;
4665
+ };
4666
+ _updateParticlesPhase2 = (delta, particlesToDelete) => {
4667
+ for (const particle of this._array) {
4668
+ if (particle.destroyed) {
4669
+ particlesToDelete.add(particle);
4670
+ continue;
4671
+ }
4672
+ for (const updater of this._container.particleUpdaters) {
4673
+ updater.update(particle, delta);
4674
+ }
4675
+ if (!particle.spawning) {
4676
+ for (const plugin of this._postParticleUpdatePlugins) {
4677
+ plugin.postParticleUpdate?.(particle, delta);
4678
+ }
4679
+ }
4680
+ this._updateParticleBucket(particle);
4681
+ }
4682
+ };
4563
4683
  }
4564
4684
 
4565
4685
  class Retina {
@@ -4575,9 +4695,8 @@
4575
4695
  const container = this.container, options = container.actualOptions;
4576
4696
  this.pixelRatio = options.detectRetina ? devicePixelRatio : defaultRatio;
4577
4697
  this.reduceFactor = defaultReduceFactor;
4578
- const ratio = this.pixelRatio, canvas = container.canvas;
4579
- if (canvas.element) {
4580
- const element = canvas.element;
4698
+ const ratio = this.pixelRatio, canvas = container.canvas, element = canvas.domElement;
4699
+ if (element) {
4581
4700
  canvas.size.width = element.offsetWidth * ratio;
4582
4701
  canvas.size.height = element.offsetHeight * ratio;
4583
4702
  }