@tsparticles/interaction-particles-links 4.0.0-alpha.5 → 4.0.0-beta.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 (61) hide show
  1. package/13.min.js +1 -0
  2. package/342.min.js +1 -0
  3. package/823.min.js +1 -0
  4. package/README.md +5 -0
  5. package/browser/CircleWarp.js +34 -18
  6. package/browser/LinkInstance.js +138 -131
  7. package/browser/Linker.js +49 -50
  8. package/browser/LinksPlugin.js +2 -1
  9. package/browser/Options/Classes/Links.js +12 -0
  10. package/browser/Options/Classes/LinksShadow.js +3 -0
  11. package/browser/Options/Classes/LinksTriangle.js +4 -0
  12. package/browser/Utils.js +2 -83
  13. package/browser/index.js +7 -4
  14. package/cjs/CircleWarp.js +34 -18
  15. package/cjs/LinkInstance.js +138 -131
  16. package/cjs/Linker.js +49 -50
  17. package/cjs/LinksPlugin.js +2 -1
  18. package/cjs/Options/Classes/Links.js +12 -0
  19. package/cjs/Options/Classes/LinksShadow.js +3 -0
  20. package/cjs/Options/Classes/LinksTriangle.js +4 -0
  21. package/cjs/Utils.js +2 -83
  22. package/cjs/index.js +7 -4
  23. package/dist_browser_LinkInstance_js.js +3 -3
  24. package/dist_browser_Linker_js.js +3 -3
  25. package/dist_browser_LinksPlugin_js.js +2 -2
  26. package/esm/CircleWarp.js +34 -18
  27. package/esm/LinkInstance.js +138 -131
  28. package/esm/Linker.js +49 -50
  29. package/esm/LinksPlugin.js +2 -1
  30. package/esm/Options/Classes/Links.js +12 -0
  31. package/esm/Options/Classes/LinksShadow.js +3 -0
  32. package/esm/Options/Classes/LinksTriangle.js +4 -0
  33. package/esm/Utils.js +2 -83
  34. package/esm/index.js +7 -4
  35. package/package.json +4 -3
  36. package/report.html +3 -3
  37. package/tsparticles.interaction.particles.links.js +45 -33
  38. package/tsparticles.interaction.particles.links.min.js +2 -2
  39. package/types/CircleWarp.d.ts +2 -2
  40. package/types/Interfaces.d.ts +4 -2
  41. package/types/LinkInstance.d.ts +6 -6
  42. package/types/Linker.d.ts +4 -1
  43. package/types/LinksPlugin.d.ts +1 -1
  44. package/types/Types.d.ts +11 -20
  45. package/types/Utils.d.ts +1 -5
  46. package/umd/CircleWarp.js +33 -17
  47. package/umd/LinkInstance.js +136 -129
  48. package/umd/Linker.js +48 -49
  49. package/umd/LinksPlugin.js +2 -1
  50. package/umd/Options/Classes/Links.js +12 -0
  51. package/umd/Options/Classes/LinksShadow.js +3 -0
  52. package/umd/Options/Classes/LinksTriangle.js +4 -0
  53. package/umd/Utils.js +1 -85
  54. package/umd/index.js +7 -4
  55. package/161.min.js +0 -2
  56. package/161.min.js.LICENSE.txt +0 -1
  57. package/29.min.js +0 -2
  58. package/29.min.js.LICENSE.txt +0 -1
  59. package/94.min.js +0 -2
  60. package/94.min.js.LICENSE.txt +0 -1
  61. package/tsparticles.interaction.particles.links.min.js.LICENSE.txt +0 -1
package/13.min.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";(this.webpackChunk_tsparticles_interaction_particles_links=this.webpackChunk_tsparticles_interaction_particles_links||[]).push([[13],{13(n,i,t){t.r(i),t.d(i,{LinksPlugin:()=>e});class e{id="links";_engine;constructor(n){this._engine=n}async getPlugin(n){let{LinkInstance:i}=await t.e(823).then(t.bind(t,823));return new i(n,this._engine)}loadOptions(){}needsPlugin(){return!0}}}}]);
package/342.min.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";(this.webpackChunk_tsparticles_interaction_particles_links=this.webpackChunk_tsparticles_interaction_particles_links||[]).push([[342],{342(i,t,n){n.d(t,{Linker:()=>a});var s=n(303);class e extends s.Circle{canvasSize;constructor(i,t,n,s){super(i,t,n),this.canvasSize=s}contains(i){if(super.contains(i))return!0;let{width:t,height:n}=this.canvasSize,{x:s,y:e}=i;return super.contains({x:s-t,y:e})||super.contains({x:s+t,y:e})||super.contains({x:s,y:e-n})||super.contains({x:s,y:e+n})||super.contains({x:s-t,y:e-n})||super.contains({x:s+t,y:e+n})||super.contains({x:s-t,y:e+n})||super.contains({x:s+t,y:e-n})}intersects(i){if(super.intersects(i))return!0;let{width:t,height:n}=this.canvasSize,e=i.position;for(let r of[{x:-t,y:0},{x:t,y:0},{x:0,y:-n},{x:0,y:n},{x:-t,y:-n},{x:t,y:n},{x:-t,y:n},{x:t,y:-n}]){let t,n={x:e.x+r.x,y:e.y+r.y};if(t=i instanceof s.Circle?new s.Circle(n.x,n.y,i.radius):new s.Rectangle(n.x,n.y,i.size.width,i.size.height),super.intersects(t))return!0}return!1}}var r=n(348),o=n(702);class a extends o.ParticlesInteractorBase{_engine;_maxDistance;constructor(i,t){super(i),this._engine=t,this._maxDistance=0}get maxDistance(){return this._maxDistance}clear(){}init(){this.container.particles.linksColor=void 0,this.container.particles.linksColors=new Map}interact(i){if(!i.options.links)return;i.links=[],i.linksDistance&&i.linksDistance>this._maxDistance&&(this._maxDistance=i.linksDistance);let t=i.getPosition(),n=this.container,r=n.canvas.size;if(t.x<s.originPoint.x||t.y<s.originPoint.y||t.x>r.width||t.y>r.height)return;let o=i.options.links,a=o.opacity,l=i.retina.linksDistance??0,c=o.warp,p=c?new e(t.x,t.y,l,r):new s.Circle(t.x,t.y,l);for(let e of n.particles.grid.query(p)){let n=e.options.links;if(i===e||!n?.enable||o.id!==n.id||e.spawning||e.destroyed||!e.links||i.links.some(i=>i.destination===e)||e.links.some(t=>t.destination===i))continue;let p=e.getPosition();if(p.x<s.originPoint.x||p.y<s.originPoint.y||p.x>r.width||p.y>r.height)continue;let h=(0,s.getDistances)(t,p).distance,x=c&&n.warp?function(i,t,n){let{dx:e,dy:r}=(0,s.getDistances)(i,t),o={x:Math.abs(e),y:Math.abs(r)},a={x:Math.min(o.x,n.width-o.x),y:Math.min(o.y,n.height-o.y)};return Math.hypot(a.x,a.y)}(t,p,r):h,k=Math.min(h,x);if(k>l)continue;let u=(1-k/l)*a;this._setColor(i),i.links.push({destination:e,opacity:u,color:this._getLinkColor(i,e),isWarped:x<h})}}isEnabled(i){return!!i.options.links?.enable}loadParticlesOptions(i,...t){for(let n of(i.links??=new r.q,t))i.links.load(n?.links)}reset(){}_getLinkColor(i,t){let n=this.container,e=i.options.links;if(!e)return;let r=void 0!==e.id?n.particles.linksColors.get(e.id):n.particles.linksColor;return(0,s.getLinkColor)(i,t,r)}_setColor(i){if(!i.options.links)return;let t=this.container,n=i.options.links,e=void 0===n.id?t.particles.linksColor:t.particles.linksColors.get(n.id);e||(e=(0,s.getLinkRandomColor)(this._engine,n.color,n.blink,n.consent),void 0===n.id?t.particles.linksColor=e:t.particles.linksColors.set(n.id,e))}}}}]);
package/823.min.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";(this.webpackChunk_tsparticles_interaction_particles_links=this.webpackChunk_tsparticles_interaction_particles_links||[]).push([[823],{823(e,i,t){t.d(i,{LinkInstance:()=>s});var n=t(303);function l(e,i){let t=[...e.map(e=>e.id)].sort((e,i)=>e-i).join("_"),l=i.get(t);return void 0===l&&(l=(0,n.getRandom)(),i.set(t,l)),l}class s{_colorCache=new Map;_container;_engine;_freqs;constructor(e,i){this._container=e,this._engine=i,this._freqs={links:new Map,triangles:new Map}}drawParticle(e,i){let{links:t,options:l}=i;if(!t?.length||!l.links)return;let s=l.links,o=i.retina.linksWidth??0,a=i.getPosition(),r=i.options.twinkle?.links,c=s.triangles.enable,h=c?new Set(t.map(e=>e.destination.id)):null,g=e.globalAlpha,d="",_=-1,k=-1,p=!1,y=()=>{p&&(e.stroke(),p=!1)};for(let g of t){if(s.frequency<1&&this._getLinkFrequency(i,g.destination)>s.frequency)continue;let t=g.destination.getPosition();if(c&&!g.isWarped&&h&&(y(),this._drawTriangles(l,i,g,h,a,t,e)),g.opacity<=0||o<=0||!s.enable)continue;let f=g.opacity,u=g.color,b=r?.enable&&(0,n.getRandom)()<r.frequency?(0,n.rangeColorToRgb)(this._engine,r.color):void 0;if(r&&b&&(u=b,f=(0,n.getRangeValue)(r.opacity)),!u){let e=void 0!==s.id?this._container.particles.linksColors.get(s.id):this._container.particles.linksColor;u=(0,n.getLinkColor)(i,g.destination,e)}if(!u)continue;let q=this._getCachedStyle(u);if((q!==d||o!==_||f!==k)&&(y(),e.strokeStyle=q,e.lineWidth=o,e.globalAlpha=f,d=q,_=o,k=f,e.beginPath(),p=!0),g.isWarped){let i=this._container.canvas.size,l=t.x-a.x,s=t.y-a.y,o=n.originPoint.x,r=n.originPoint.y;Math.abs(l)>i.width*n.half&&(o=l>0?-i.width:i.width),Math.abs(s)>i.height*n.half&&(r=s>0?-i.height:i.height),e.moveTo(a.x,a.y),e.lineTo(t.x+o,t.y+r),e.moveTo(a.x-o,a.y-r),e.lineTo(t.x,t.y)}else e.moveTo(a.x,a.y),e.lineTo(t.x,t.y)}y(),e.globalAlpha=g}init(){return this._freqs.links.clear(),this._freqs.triangles.clear(),this._colorCache.clear(),Promise.resolve()}particleCreated(e){if(e.links=[],!e.options.links)return;e.linksDistance=e.options.links.distance,e.linksWidth=e.options.links.width;let i=this._container.retina.pixelRatio;e.retina.linksDistance=e.linksDistance*i,e.retina.linksWidth=e.linksWidth*i}particleDestroyed(e){e.links=[]}_drawTriangles(e,i,t,l,s,o,a){let r=t.destination,c=e.links?.triangles;if(!c?.enable||!r.options.links?.triangles.enable)return;let h=r.links;if(h?.length)for(let g of h){if(g.isWarped||this._getLinkFrequency(r,g.destination)>r.options.links.frequency||!l.has(g.destination.id))continue;let h=g.destination;if(this._getTriangleFrequency(i,r,h)>(e.links?.triangles.frequency??0))continue;let d=c.opacity??(t.opacity+g.opacity)*n.half,_=(0,n.rangeColorToRgb)(this._engine,c.color)??t.color;if(!_||d<=0)continue;let k=h.getPosition();a.save(),a.fillStyle=this._getCachedStyle(_),a.globalAlpha=d,a.beginPath(),a.moveTo(s.x,s.y),a.lineTo(o.x,o.y),a.lineTo(k.x,k.y),a.closePath(),a.fill(),a.restore()}}_getCachedStyle(e){let i=`${e.r},${e.g},${e.b}`,t=this._colorCache.get(i);return t||(t=(0,n.getStyleFromRgb)(e,this._container.hdr),this._colorCache.set(i,t)),t}_getLinkFrequency(e,i){return l([e,i],this._freqs.links)}_getTriangleFrequency(e,i,t){return l([e,i,t],this._freqs.triangles)}}}}]);
package/README.md CHANGED
@@ -27,6 +27,7 @@ Once the scripts are loaded you can set up `tsParticles` and the interaction plu
27
27
 
28
28
  ```javascript
29
29
  (async () => {
30
+ await loadInteractivityPlugin(tsParticles);
30
31
  await loadParticlesLinksInteraction(tsParticles);
31
32
 
32
33
  await tsParticles.load({
@@ -56,9 +57,11 @@ Then you need to import it in the app, like this:
56
57
 
57
58
  ```javascript
58
59
  const { tsParticles } = require("@tsparticles/engine");
60
+ const { loadInteractivityPlugin } = require("@tsparticles/plugin-interactivity");
59
61
  const { loadParticlesLinksInteraction } = require("@tsparticles/interaction-particles-links");
60
62
 
61
63
  (async () => {
64
+ await loadInteractivityPlugin(tsParticles);
62
65
  await loadParticlesLinksInteraction(tsParticles);
63
66
  })();
64
67
  ```
@@ -67,9 +70,11 @@ or
67
70
 
68
71
  ```javascript
69
72
  import { tsParticles } from "@tsparticles/engine";
73
+ import { loadInteractivityPlugin } from "@tsparticles/plugin-interactivity";
70
74
  import { loadParticlesLinksInteraction } from "@tsparticles/interaction-particles-links";
71
75
 
72
76
  (async () => {
77
+ await loadInteractivityPlugin(tsParticles);
73
78
  await loadParticlesLinksInteraction(tsParticles);
74
79
  })();
75
80
  ```
@@ -1,32 +1,48 @@
1
- import { Circle, Rectangle, double } from "@tsparticles/engine";
1
+ import { Circle, Rectangle } from "@tsparticles/engine";
2
2
  export class CircleWarp extends Circle {
3
+ canvasSize;
3
4
  constructor(x, y, radius, canvasSize) {
4
5
  super(x, y, radius);
5
6
  this.canvasSize = canvasSize;
6
- this.canvasSize = { ...canvasSize };
7
7
  }
8
8
  contains(point) {
9
+ if (super.contains(point))
10
+ return true;
9
11
  const { width, height } = this.canvasSize, { x, y } = point;
10
- return (super.contains(point) ||
11
- super.contains({ x: x - width, y }) ||
12
+ return (super.contains({ x: x - width, y }) ||
13
+ super.contains({ x: x + width, y }) ||
14
+ super.contains({ x, y: y - height }) ||
15
+ super.contains({ x, y: y + height }) ||
12
16
  super.contains({ x: x - width, y: y - height }) ||
13
- super.contains({ x, y: y - height }));
17
+ super.contains({ x: x + width, y: y + height }) ||
18
+ super.contains({ x: x - width, y: y + height }) ||
19
+ super.contains({ x: x + width, y: y - height }));
14
20
  }
15
21
  intersects(range) {
16
- if (super.intersects(range)) {
22
+ if (super.intersects(range))
17
23
  return true;
18
- }
19
- const rect = range, circle = range, newPos = {
20
- x: range.position.x - this.canvasSize.width,
21
- y: range.position.y - this.canvasSize.height,
22
- };
23
- if (Object.hasOwn(circle, "radius")) {
24
- const biggerCircle = new Circle(newPos.x, newPos.y, circle.radius * double);
25
- return super.intersects(biggerCircle);
26
- }
27
- else if (Object.hasOwn(rect, "size")) {
28
- const rectSW = new Rectangle(newPos.x, newPos.y, rect.size.width * double, rect.size.height * double);
29
- return super.intersects(rectSW);
24
+ const { width, height } = this.canvasSize, pos = range.position, shifts = [
25
+ { x: -width, y: 0 },
26
+ { x: width, y: 0 },
27
+ { x: 0, y: -height },
28
+ { x: 0, y: height },
29
+ { x: -width, y: -height },
30
+ { x: width, y: height },
31
+ { x: -width, y: height },
32
+ { x: width, y: -height },
33
+ ];
34
+ for (const shift of shifts) {
35
+ const shiftedPos = { x: pos.x + shift.x, y: pos.y + shift.y };
36
+ let shiftedRange;
37
+ if (range instanceof Circle) {
38
+ shiftedRange = new Circle(shiftedPos.x, shiftedPos.y, range.radius);
39
+ }
40
+ else {
41
+ const rect = range;
42
+ shiftedRange = new Rectangle(shiftedPos.x, shiftedPos.y, rect.size.width, rect.size.height);
43
+ }
44
+ if (super.intersects(shiftedRange))
45
+ return true;
30
46
  }
31
47
  return false;
32
48
  }
@@ -1,156 +1,163 @@
1
- import { getDistance, getLinkColor, getRandom, getRangeValue, half, rangeColorToRgb, } from "@tsparticles/engine";
2
- import { drawLinkLine, drawLinkTriangle, setLinkFrequency } from "./Utils.js";
3
- const minOpacity = 0, minWidth = 0, minDistance = 0, maxFrequency = 1;
1
+ import { getLinkColor as engineGetLinkColor, getRandom, getRangeValue, getStyleFromRgb, half, originPoint, rangeColorToRgb, } from "@tsparticles/engine";
2
+ import { setLinkFrequency } from "./Utils.js";
3
+ const minOpacity = 0, minWidth = 0, minDistance = 0, maxFrequency = 1, defaultFrequency = 0;
4
4
  export class LinkInstance {
5
+ _colorCache = new Map();
6
+ _container;
7
+ _engine;
8
+ _freqs;
5
9
  constructor(container, engine) {
6
- this._drawLinkLine = (p1, link) => {
7
- const p1LinksOptions = p1.options.links;
8
- if (!p1LinksOptions?.enable) {
9
- return;
10
+ this._container = container;
11
+ this._engine = engine;
12
+ this._freqs = { links: new Map(), triangles: new Map() };
13
+ }
14
+ drawParticle(context, particle) {
15
+ const { links, options } = particle;
16
+ if (!links?.length || !options.links) {
17
+ return;
18
+ }
19
+ const linkOpts = options.links, width = particle.retina.linksWidth ?? minWidth, pos1 = particle.getPosition(), twinkle = particle.options["twinkle"]?.links, trianglesEnabled = linkOpts.triangles.enable, p1Destinations = trianglesEnabled ? new Set(links.map(l => l.destination.id)) : null, originalAlpha = context.globalAlpha;
20
+ let currentColorStyle = "", currentWidth = -1, currentAlpha = -1, pathOpen = false;
21
+ const flushLines = () => {
22
+ if (pathOpen) {
23
+ context.stroke();
24
+ pathOpen = false;
10
25
  }
11
- const container = this._container, p2 = link.destination, pos1 = p1.getPosition(), pos2 = p2.getPosition();
12
- let opacity = link.opacity;
13
- container.canvas.draw(ctx => {
14
- let colorLine;
15
- const twinkle = p1.options["twinkle"]?.lines;
16
- if (twinkle?.enable) {
17
- const twinkleFreq = twinkle.frequency, twinkleRgb = rangeColorToRgb(this._engine, twinkle.color), twinkling = getRandom() < twinkleFreq;
18
- if (twinkling && twinkleRgb) {
19
- colorLine = twinkleRgb;
20
- opacity = getRangeValue(twinkle.opacity);
21
- }
22
- }
23
- if (!colorLine) {
24
- const linkColor = p1LinksOptions.id !== undefined
25
- ? container.particles.linksColors.get(p1LinksOptions.id)
26
- : container.particles.linksColor;
27
- colorLine = getLinkColor(p1, p2, linkColor);
28
- }
29
- if (!colorLine) {
30
- return;
31
- }
32
- const width = p1.retina.linksWidth ?? minWidth, maxDistance = p1.retina.linksDistance ?? minDistance;
33
- drawLinkLine({
34
- context: ctx,
35
- width,
36
- begin: pos1,
37
- end: pos2,
38
- engine: this._engine,
39
- maxDistance,
40
- canvasSize: container.canvas.size,
41
- links: p1LinksOptions,
42
- colorLine,
43
- opacity,
44
- hdr: container.hdr,
45
- });
46
- });
47
26
  };
48
- this._drawLinkTriangle = (p1, link1, link2) => {
49
- const linksOptions = p1.options.links;
50
- if (!linksOptions?.enable) {
51
- return;
27
+ for (const link of links) {
28
+ if (linkOpts.frequency < maxFrequency &&
29
+ this._getLinkFrequency(particle, link.destination) > linkOpts.frequency) {
30
+ continue;
52
31
  }
53
- const triangleOptions = linksOptions.triangles;
54
- if (!triangleOptions.enable) {
55
- return;
32
+ const pos2 = link.destination.getPosition();
33
+ if (trianglesEnabled && !link.isWarped && p1Destinations) {
34
+ flushLines();
35
+ this._drawTriangles(options, particle, link, p1Destinations, pos1, pos2, context);
56
36
  }
57
- const container = this._container, p2 = link1.destination, p3 = link2.destination, opacityTriangle = triangleOptions.opacity ?? (link1.opacity + link2.opacity) * half;
58
- if (opacityTriangle <= minOpacity) {
59
- return;
37
+ if (link.opacity <= minOpacity || width <= minWidth) {
38
+ continue;
60
39
  }
61
- container.canvas.draw(ctx => {
62
- const pos1 = p1.getPosition(), pos2 = p2.getPosition(), pos3 = p3.getPosition(), linksDistance = p1.retina.linksDistance ?? minDistance;
63
- if (getDistance(pos1, pos2) > linksDistance ||
64
- getDistance(pos3, pos2) > linksDistance ||
65
- getDistance(pos3, pos1) > linksDistance) {
66
- return;
67
- }
68
- let colorTriangle = rangeColorToRgb(this._engine, triangleOptions.color);
69
- if (!colorTriangle) {
70
- const linkColor = linksOptions.id !== undefined
71
- ? container.particles.linksColors.get(linksOptions.id)
72
- : container.particles.linksColor;
73
- colorTriangle = getLinkColor(p1, p2, linkColor);
74
- }
75
- if (!colorTriangle) {
76
- return;
77
- }
78
- drawLinkTriangle({
79
- context: ctx,
80
- pos1,
81
- pos2,
82
- pos3,
83
- colorTriangle,
84
- opacityTriangle,
85
- hdr: container.hdr,
86
- });
87
- });
88
- };
89
- this._drawTriangles = (options, p1, link, p1Links) => {
90
- const p2 = link.destination;
91
- if (!(options.links?.triangles.enable && p2.options.links?.triangles.enable)) {
92
- return;
40
+ if (!linkOpts.enable) {
41
+ continue;
42
+ }
43
+ let opacity = link.opacity, colorLine = link.color;
44
+ const twinkleRgb = twinkle?.enable && getRandom() < twinkle.frequency ? rangeColorToRgb(this._engine, twinkle.color) : undefined;
45
+ if (twinkle && twinkleRgb) {
46
+ colorLine = twinkleRgb;
47
+ opacity = getRangeValue(twinkle.opacity);
48
+ }
49
+ if (!colorLine) {
50
+ const linkColor = linkOpts.id !== undefined
51
+ ? this._container.particles.linksColors.get(linkOpts.id)
52
+ : this._container.particles.linksColor;
53
+ colorLine = engineGetLinkColor(particle, link.destination, linkColor);
93
54
  }
94
- const vertices = p2.links?.filter(t => {
95
- const linkFreq = this._getLinkFrequency(p2, t.destination), minCount = 0;
96
- return (p2.options.links &&
97
- linkFreq <= p2.options.links.frequency &&
98
- p1Links.findIndex(l => l.destination === t.destination) >= minCount);
99
- });
100
- if (!vertices?.length) {
101
- return;
55
+ if (!colorLine) {
56
+ continue;
102
57
  }
103
- for (const vertex of vertices) {
104
- const p3 = vertex.destination, triangleFreq = this._getTriangleFrequency(p1, p2, p3);
105
- if (triangleFreq > options.links.triangles.frequency) {
106
- continue;
58
+ const colorStyle = this._getCachedStyle(colorLine);
59
+ if (colorStyle !== currentColorStyle || width !== currentWidth || opacity !== currentAlpha) {
60
+ flushLines();
61
+ context.strokeStyle = colorStyle;
62
+ context.lineWidth = width;
63
+ context.globalAlpha = opacity;
64
+ currentColorStyle = colorStyle;
65
+ currentWidth = width;
66
+ currentAlpha = opacity;
67
+ context.beginPath();
68
+ pathOpen = true;
69
+ }
70
+ if (link.isWarped) {
71
+ const canvasSize = this._container.canvas.size, dx = pos2.x - pos1.x, dy = pos2.y - pos1.y;
72
+ let sx = originPoint.x, sy = originPoint.y;
73
+ if (Math.abs(dx) > canvasSize.width * half) {
74
+ sx = dx > minDistance ? -canvasSize.width : canvasSize.width;
75
+ }
76
+ if (Math.abs(dy) > canvasSize.height * half) {
77
+ sy = dy > minDistance ? -canvasSize.height : canvasSize.height;
107
78
  }
108
- this._drawLinkTriangle(p1, link, vertex);
79
+ context.moveTo(pos1.x, pos1.y);
80
+ context.lineTo(pos2.x + sx, pos2.y + sy);
81
+ context.moveTo(pos1.x - sx, pos1.y - sy);
82
+ context.lineTo(pos2.x, pos2.y);
109
83
  }
110
- };
111
- this._getLinkFrequency = (p1, p2) => {
112
- return setLinkFrequency([p1, p2], this._freqs.links);
113
- };
114
- this._getTriangleFrequency = (p1, p2, p3) => {
115
- return setLinkFrequency([p1, p2, p3], this._freqs.triangles);
116
- };
117
- this._container = container;
118
- this._engine = engine;
119
- this._freqs = {
120
- links: new Map(),
121
- triangles: new Map(),
122
- };
123
- }
124
- drawParticle(_context, particle) {
125
- const { links, options } = particle;
126
- if (!links?.length) {
127
- return;
128
- }
129
- const p1Links = links.filter(l => options.links &&
130
- (options.links.frequency >= maxFrequency ||
131
- this._getLinkFrequency(particle, l.destination) <= options.links.frequency));
132
- for (const link of p1Links) {
133
- this._drawTriangles(options, particle, link, p1Links);
134
- if (link.opacity > minOpacity && (particle.retina.linksWidth ?? minWidth) > minWidth) {
135
- this._drawLinkLine(particle, link);
84
+ else {
85
+ context.moveTo(pos1.x, pos1.y);
86
+ context.lineTo(pos2.x, pos2.y);
136
87
  }
137
88
  }
89
+ flushLines();
90
+ context.globalAlpha = originalAlpha;
138
91
  }
139
- async init() {
140
- this._freqs.links = new Map();
141
- this._freqs.triangles = new Map();
142
- await Promise.resolve();
92
+ init() {
93
+ this._freqs.links.clear();
94
+ this._freqs.triangles.clear();
95
+ this._colorCache.clear();
96
+ return Promise.resolve();
143
97
  }
144
98
  particleCreated(particle) {
145
99
  particle.links = [];
146
100
  if (!particle.options.links) {
147
101
  return;
148
102
  }
149
- const ratio = this._container.retina.pixelRatio, { retina } = particle, { distance, width } = particle.options.links;
150
- retina.linksDistance = distance * ratio;
151
- retina.linksWidth = width * ratio;
103
+ particle.linksDistance = particle.options.links.distance;
104
+ particle.linksWidth = particle.options.links.width;
105
+ const ratio = this._container.retina.pixelRatio;
106
+ particle.retina.linksDistance = particle.linksDistance * ratio;
107
+ particle.retina.linksWidth = particle.linksWidth * ratio;
152
108
  }
153
109
  particleDestroyed(particle) {
154
110
  particle.links = [];
155
111
  }
112
+ _drawTriangles(options, p1, link, p1Destinations, pos1, pos2, context) {
113
+ const p2 = link.destination, triangleOptions = options.links?.triangles;
114
+ if (!triangleOptions?.enable || !p2.options.links?.triangles.enable) {
115
+ return;
116
+ }
117
+ const p2Links = p2.links;
118
+ if (!p2Links?.length) {
119
+ return;
120
+ }
121
+ for (const vertex of p2Links) {
122
+ if (vertex.isWarped ||
123
+ this._getLinkFrequency(p2, vertex.destination) > p2.options.links.frequency ||
124
+ !p1Destinations.has(vertex.destination.id)) {
125
+ continue;
126
+ }
127
+ const p3 = vertex.destination;
128
+ if (this._getTriangleFrequency(p1, p2, p3) > (options.links?.triangles.frequency ?? defaultFrequency)) {
129
+ continue;
130
+ }
131
+ const opacityTriangle = triangleOptions.opacity ?? (link.opacity + vertex.opacity) * half, colorTriangle = rangeColorToRgb(this._engine, triangleOptions.color) ?? link.color;
132
+ if (!colorTriangle || opacityTriangle <= minOpacity) {
133
+ continue;
134
+ }
135
+ const pos3 = p3.getPosition();
136
+ context.save();
137
+ context.fillStyle = this._getCachedStyle(colorTriangle);
138
+ context.globalAlpha = opacityTriangle;
139
+ context.beginPath();
140
+ context.moveTo(pos1.x, pos1.y);
141
+ context.lineTo(pos2.x, pos2.y);
142
+ context.lineTo(pos3.x, pos3.y);
143
+ context.closePath();
144
+ context.fill();
145
+ context.restore();
146
+ }
147
+ }
148
+ _getCachedStyle(rgb) {
149
+ const key = `${rgb.r},${rgb.g},${rgb.b}`;
150
+ let style = this._colorCache.get(key);
151
+ if (!style) {
152
+ style = getStyleFromRgb(rgb, this._container.hdr);
153
+ this._colorCache.set(key, style);
154
+ }
155
+ return style;
156
+ }
157
+ _getLinkFrequency(p1, p2) {
158
+ return setLinkFrequency([p1, p2], this._freqs.links);
159
+ }
160
+ _getTriangleFrequency(p1, p2, p3) {
161
+ return setLinkFrequency([p1, p2, p3], this._freqs.triangles);
162
+ }
156
163
  }
package/browser/Linker.js CHANGED
@@ -1,46 +1,25 @@
1
- import { Circle, getDistances, getLinkRandomColor, originPoint, } from "@tsparticles/engine";
1
+ import { Circle, getDistances, getLinkColor, getLinkRandomColor, originPoint, } from "@tsparticles/engine";
2
2
  import { CircleWarp } from "./CircleWarp.js";
3
3
  import { Links } from "./Options/Classes/Links.js";
4
4
  import { ParticlesInteractorBase } from "@tsparticles/plugin-interactivity";
5
- const squarePower = 2, opacityOffset = 1, minDistance = 0;
6
- function getLinkDistance(pos1, pos2, optDistance, canvasSize, warp) {
7
- const { dx, dy, distance } = getDistances(pos1, pos2);
8
- if (!warp || distance <= optDistance) {
9
- return distance;
10
- }
11
- const absDiffs = {
12
- x: Math.abs(dx),
13
- y: Math.abs(dy),
14
- }, warpDistances = {
5
+ const opacityOffset = 1, minDistance = 0;
6
+ function getWarpDistance(pos1, pos2, canvasSize) {
7
+ const { dx, dy } = getDistances(pos1, pos2), absDiffs = { x: Math.abs(dx), y: Math.abs(dy) }, warpDistances = {
15
8
  x: Math.min(absDiffs.x, canvasSize.width - absDiffs.x),
16
9
  y: Math.min(absDiffs.y, canvasSize.height - absDiffs.y),
17
10
  };
18
- return Math.sqrt(warpDistances.x ** squarePower + warpDistances.y ** squarePower);
11
+ return Math.hypot(warpDistances.x, warpDistances.y);
19
12
  }
20
13
  export class Linker extends ParticlesInteractorBase {
14
+ _engine;
15
+ _maxDistance;
21
16
  constructor(container, engine) {
22
17
  super(container);
23
- this._setColor = p1 => {
24
- if (!p1.options.links) {
25
- return;
26
- }
27
- const container = this.container, linksOptions = p1.options.links;
28
- let linkColor = linksOptions.id === undefined
29
- ? container.particles.linksColor
30
- : container.particles.linksColors.get(linksOptions.id);
31
- if (linkColor) {
32
- return;
33
- }
34
- const optColor = linksOptions.color;
35
- linkColor = getLinkRandomColor(this._engine, optColor, linksOptions.blink, linksOptions.consent);
36
- if (linksOptions.id === undefined) {
37
- container.particles.linksColor = linkColor;
38
- }
39
- else {
40
- container.particles.linksColors.set(linksOptions.id, linkColor);
41
- }
42
- };
43
18
  this._engine = engine;
19
+ this._maxDistance = 0;
20
+ }
21
+ get maxDistance() {
22
+ return this._maxDistance;
44
23
  }
45
24
  clear() {
46
25
  }
@@ -53,22 +32,14 @@ export class Linker extends ParticlesInteractorBase {
53
32
  return;
54
33
  }
55
34
  p1.links = [];
35
+ if (p1.linksDistance && p1.linksDistance > this._maxDistance) {
36
+ this._maxDistance = p1.linksDistance;
37
+ }
56
38
  const pos1 = p1.getPosition(), container = this.container, canvasSize = container.canvas.size;
57
- if (pos1.x < originPoint.x ||
58
- pos1.y < originPoint.y ||
59
- pos1.x > canvasSize.width ||
60
- pos1.y > canvasSize.height) {
39
+ if (pos1.x < originPoint.x || pos1.y < originPoint.y || pos1.x > canvasSize.width || pos1.y > canvasSize.height) {
61
40
  return;
62
41
  }
63
- const linkOpt1 = p1.options.links, optOpacity = linkOpt1.opacity, optDistance = p1.retina.linksDistance ?? minDistance, warp = linkOpt1.warp;
64
- let range;
65
- if (warp) {
66
- range = new CircleWarp(pos1.x, pos1.y, optDistance, canvasSize);
67
- }
68
- else {
69
- range = new Circle(pos1.x, pos1.y, optDistance);
70
- }
71
- const query = container.particles.quadTree.query(range);
42
+ const linkOpt1 = p1.options.links, optOpacity = linkOpt1.opacity, optDistance = p1.retina.linksDistance ?? minDistance, warp = linkOpt1.warp, range = warp ? new CircleWarp(pos1.x, pos1.y, optDistance, canvasSize) : new Circle(pos1.x, pos1.y, optDistance), query = container.particles.grid.query(range);
72
43
  for (const p2 of query) {
73
44
  const linkOpt2 = p2.options.links;
74
45
  if (p1 === p2 ||
@@ -82,13 +53,10 @@ export class Linker extends ParticlesInteractorBase {
82
53
  continue;
83
54
  }
84
55
  const pos2 = p2.getPosition();
85
- if (pos2.x < originPoint.x ||
86
- pos2.y < originPoint.y ||
87
- pos2.x > canvasSize.width ||
88
- pos2.y > canvasSize.height) {
56
+ if (pos2.x < originPoint.x || pos2.y < originPoint.y || pos2.x > canvasSize.width || pos2.y > canvasSize.height) {
89
57
  continue;
90
58
  }
91
- const distance = getLinkDistance(pos1, pos2, optDistance, canvasSize, warp && linkOpt2.warp);
59
+ const distDirect = getDistances(pos1, pos2).distance, distWarp = warp && linkOpt2.warp ? getWarpDistance(pos1, pos2, canvasSize) : distDirect, distance = Math.min(distDirect, distWarp);
92
60
  if (distance > optDistance) {
93
61
  continue;
94
62
  }
@@ -97,6 +65,8 @@ export class Linker extends ParticlesInteractorBase {
97
65
  p1.links.push({
98
66
  destination: p2,
99
67
  opacity: opacityLine,
68
+ color: this._getLinkColor(p1, p2),
69
+ isWarped: distWarp < distDirect,
100
70
  });
101
71
  }
102
72
  }
@@ -111,4 +81,33 @@ export class Linker extends ParticlesInteractorBase {
111
81
  }
112
82
  reset() {
113
83
  }
84
+ _getLinkColor(p1, p2) {
85
+ const container = this.container, linksOptions = p1.options.links;
86
+ if (!linksOptions) {
87
+ return;
88
+ }
89
+ const linkColor = linksOptions.id !== undefined
90
+ ? container.particles.linksColors.get(linksOptions.id)
91
+ : container.particles.linksColor;
92
+ return getLinkColor(p1, p2, linkColor);
93
+ }
94
+ _setColor(p1) {
95
+ if (!p1.options.links) {
96
+ return;
97
+ }
98
+ const container = this.container, linksOptions = p1.options.links;
99
+ let linkColor = linksOptions.id === undefined
100
+ ? container.particles.linksColor
101
+ : container.particles.linksColors.get(linksOptions.id);
102
+ if (linkColor) {
103
+ return;
104
+ }
105
+ linkColor = getLinkRandomColor(this._engine, linksOptions.color, linksOptions.blink, linksOptions.consent);
106
+ if (linksOptions.id === undefined) {
107
+ container.particles.linksColor = linkColor;
108
+ }
109
+ else {
110
+ container.particles.linksColors.set(linksOptions.id, linkColor);
111
+ }
112
+ }
114
113
  }
@@ -1,6 +1,7 @@
1
1
  export class LinksPlugin {
2
+ id = "links";
3
+ _engine;
2
4
  constructor(engine) {
3
- this.id = "links";
4
5
  this._engine = engine;
5
6
  }
6
7
  async getPlugin(container) {
@@ -2,6 +2,18 @@ import { OptionsColor, isNull } from "@tsparticles/engine";
2
2
  import { LinksShadow } from "./LinksShadow.js";
3
3
  import { LinksTriangle } from "./LinksTriangle.js";
4
4
  export class Links {
5
+ blink;
6
+ color;
7
+ consent;
8
+ distance;
9
+ enable;
10
+ frequency;
11
+ id;
12
+ opacity;
13
+ shadow;
14
+ triangles;
15
+ warp;
16
+ width;
5
17
  constructor() {
6
18
  this.blink = false;
7
19
  this.color = new OptionsColor();
@@ -1,5 +1,8 @@
1
1
  import { OptionsColor, isNull } from "@tsparticles/engine";
2
2
  export class LinksShadow {
3
+ blur;
4
+ color;
5
+ enable;
3
6
  constructor() {
4
7
  this.blur = 5;
5
8
  this.color = new OptionsColor();
@@ -1,5 +1,9 @@
1
1
  import { OptionsColor, isNull } from "@tsparticles/engine";
2
2
  export class LinksTriangle {
3
+ color;
4
+ enable;
5
+ frequency;
6
+ opacity;
3
7
  constructor() {
4
8
  this.enable = false;
5
9
  this.frequency = 1;