@tsparticles/plugin-polygon-mask 3.0.3 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/719.min.js +2 -0
- package/719.min.js.LICENSE.txt +1 -0
- package/787.min.js +2 -0
- package/787.min.js.LICENSE.txt +1 -0
- package/856.min.js +2 -0
- package/856.min.js.LICENSE.txt +1 -0
- package/browser/PolygonMaskInstance.js +60 -49
- package/browser/PolygonMaskPlugin.js +25 -0
- package/browser/index.js +1 -25
- package/browser/utils.js +22 -11
- package/cjs/PolygonMaskInstance.js +86 -52
- package/cjs/PolygonMaskPlugin.js +52 -0
- package/cjs/index.js +13 -25
- package/cjs/utils.js +24 -13
- package/dist_browser_PolygonMaskInstance_js.js +30 -0
- package/dist_browser_PolygonMaskPlugin_js.js +90 -0
- package/dist_browser_utils_js.js +30 -0
- package/esm/PolygonMaskInstance.js +60 -49
- package/esm/PolygonMaskPlugin.js +25 -0
- package/esm/index.js +1 -25
- package/esm/utils.js +22 -11
- package/package.json +2 -2
- package/report.html +3 -3
- package/tsparticles.plugin.polygon-mask.js +244 -2158
- package/tsparticles.plugin.polygon-mask.min.js +1 -1
- package/tsparticles.plugin.polygon-mask.min.js.LICENSE.txt +1 -1
- package/types/Options/Classes/PolygonMaskInline.d.ts +1 -2
- package/types/PolygonMaskInstance.d.ts +2 -2
- package/types/PolygonMaskPlugin.d.ts +11 -0
- package/types/utils.d.ts +1 -1
- package/umd/PolygonMaskInstance.js +88 -53
- package/umd/PolygonMaskPlugin.js +63 -0
- package/umd/index.js +15 -26
- package/umd/utils.js +24 -13
package/719.min.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! For license information please see 719.min.js.LICENSE.txt */
|
|
2
|
+
(this.webpackChunk_tsparticles_plugin_polygon_mask=this.webpackChunk_tsparticles_plugin_polygon_mask||[]).push([[719],{719:(t,i,e)=>{e.d(i,{PolygonMaskInstance:()=>c});var n=e(533);const s=`${n.errorPrefix} No polygon data loaded.`,a=`${n.errorPrefix} No polygon found, you need to specify SVG url in config.`,o=0,h=0,r=.5;class c{constructor(t,i){this._checkInsidePolygon=t=>{const i=this._container,e=i.actualOptions.polygon;if(!e?.enable||"none"===e.type||"inline"===e.type)return!0;if(!this.raw)throw new Error(a);const s=i.canvas.size,o=t?.x??(0,n.getRandom)()*s.width,h=t?.y??(0,n.getRandom)()*s.height;let r=!1;for(let t=0,i=this.raw.length-1;t<this.raw.length;i=t++){const e=this.raw[t],n=this.raw[i];e.y>h!=n.y>h&&o<(n.x-e.x)*(h-e.y)/(n.y-e.y)+e.x&&(r=!r)}return"inside"===e.type?r:"outside"===e.type&&!r},this._createPath2D=()=>{if(this._container.actualOptions.polygon&&this.paths?.length)for(const t of this.paths){const i=t.element?.getAttribute("d");if(i){const e=new Path2D(i),n=document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGMatrix(),s=new Path2D,a=n.scale(this._scale);s.addPath?(s.addPath(e,a),t.path2d=s):delete t.path2d}else delete t.path2d;if(t.path2d??!this.raw)continue;t.path2d=new Path2D;const e=0,n=this.raw[e];t.path2d.moveTo(n.x,n.y),this.raw.forEach(((i,n)=>{n>e&&t.path2d?.lineTo(i.x,i.y)})),t.path2d.closePath()}},this._downloadSvgPath=async(t,i)=>{const e=this._container.actualOptions.polygon;if(!e)return;const s=t??e.url,a=i??!1;if(!s||void 0!==this.paths&&!a)return this.raw;const o=await fetch(s);if(!o.ok)throw new Error(`${n.errorPrefix} occurred during polygon mask download`);return await this._parseSvgPath(await o.text(),i)},this._drawPoints=()=>{if(this.raw)for(const t of this.raw)this._container.particles.addParticle({x:t.x,y:t.y})},this._getEquidistantPointByIndex=t=>{const i=this._container.actualOptions;if(!i.polygon)return;if(!this.raw?.length||!this.paths?.length)throw new Error(s);let e,n=0;const a=this.paths.reduce(((t,i)=>t+i.length),0)/i.particles.number.value;for(const i of this.paths){const s=a*t-n;if(s<=i.length){e=i.element.getPointAtLength(s);break}n+=i.length}const r=this._scale;return{x:(e?.x??o)*r+(this.offset?.x??o),y:(e?.y??h)*r+(this.offset?.y??h)}},this._getPointByIndex=t=>{if(!this.raw?.length)throw new Error(s);const i=this.raw[t%this.raw.length];return{x:i.x,y:i.y}},this._getRandomPoint=()=>{if(!this.raw?.length)throw new Error(s);const t=(0,n.itemFromArray)(this.raw);return{x:t.x,y:t.y}},this._getRandomPointByLength=()=>{if(!this._container.actualOptions.polygon)return;if(!this.raw?.length||!this.paths?.length)throw new Error(s);const t=(0,n.itemFromArray)(this.paths),i=Math.floor((0,n.getRandom)()*t.length)+1,e=t.element.getPointAtLength(i),a=this._scale;return{x:e.x*a+(this.offset?.x??o),y:e.y*a+(this.offset?.y??h)}},this._initRawData=async t=>{const i=this._container.actualOptions.polygon;if(i){if(i.url)this.raw=await this._downloadSvgPath(i.url,t);else if(i.data){const e=i.data;let s;if((0,n.isString)(e))s=e;else{const t=t=>`<path d="${t}" />`,i=(0,n.isArray)(e.path)?e.path.map(t).join(""):t(e.path);s=`<svg ${'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"'} width="${e.size.width}" height="${e.size.height}">${i}</svg>`}this.raw=await this._parseSvgPath(s,t)}this._createPath2D(),this._engine.dispatchEvent("polygonMaskLoaded",{container:this._container})}},this._parseSvgPath=async(t,i)=>{const s=i??!1;if(void 0!==this.paths&&!s)return this.raw;const a=this._container,o=a.actualOptions.polygon;if(!o)return;const h=(new DOMParser).parseFromString(t,"image/svg+xml"),c=h.getElementsByTagName("svg")[0];let l=c.getElementsByTagName("path");l.length||(l=h.getElementsByTagName("path")),this.paths=[];for(let t=0;t<l.length;t++){const i=l.item(t);i&&this.paths.push({element:i,length:i.getTotalLength()})}const g=this._scale;this.dimension.width=parseFloat(c.getAttribute("width")??"0")*g,this.dimension.height=parseFloat(c.getAttribute("height")??"0")*g;const p=o.position??{x:50,y:50},d=a.canvas.size;this.offset={x:d.width*p.x/n.percentDenominator-this.dimension.width*r,y:d.height*p.y/n.percentDenominator-this.dimension.height*r};const{parsePaths:y}=await e.e(856).then(e.bind(e,856));return y(this.paths,g,this.offset)},this._polygonBounce=async(t,i,s)=>{const a=this._container.actualOptions.polygon;if(!this.raw||!a?.enable||"top"!==s)return!1;if("inside"===a.type||"outside"===a.type){let i,s,a;const o=t.getPosition(),h=t.getRadius(),r=1;for(let c=0,l=this.raw.length-r;c<this.raw.length;l=c++){const r=this.raw[c],g=this.raw[l],{calcClosestPointOnSegment:p}=await e.e(856).then(e.bind(e,856));i=p(r,g,o);const d=(0,n.getDistances)(o,i);if([s,a]=[d.dx,d.dy],d.distance<h){const{segmentBounce:i}=await e.e(856).then(e.bind(e,856));return i(r,g,t.velocity),!0}}if(i&&void 0!==s&&void 0!==a&&!this._checkInsidePolygon(o)){const e={x:1,y:1},n=2*h,s=-1;return o.x>=i.x&&(e.x=-1),o.y>=i.y&&(e.y=-1),t.position.x=i.x+n*e.x,t.position.y=i.y+n*e.y,t.velocity.mult(s),!0}}else if("inline"===a.type&&t.initialPosition){const i=(0,n.getDistance)(t.initialPosition,t.getPosition()),{velocity:e}=t;if(i>this._moveRadius)return e.x=e.y*r-e.x,e.y=e.x*r-e.y,!0}return!1},this._randomPoint=()=>{const t=this._container,i=t.actualOptions.polygon;if(!i)return;let e;if("inline"===i.type)switch(i.inline.arrangement){case"random-point":e=this._getRandomPoint();break;case"random-length":e=this._getRandomPointByLength();break;case"equidistant":e=this._getEquidistantPointByIndex(t.particles.count);break;default:e=this._getPointByIndex(t.particles.count)}else{const i=t.canvas.size;e={x:(0,n.getRandom)()*i.width,y:(0,n.getRandom)()*i.height}}return this._checkInsidePolygon(e)?e:this._randomPoint()},this._container=t,this._engine=i,this.dimension={height:0,width:0},this._moveRadius=0,this._scale=1}clickPositionValid(t){const i=this._container.actualOptions.polygon;return!!i?.enable&&"none"!==i.type&&"inline"!==i.type&&this._checkInsidePolygon(t)}async draw(t){if(!this.paths?.length)return;const i=this._container.actualOptions.polygon;if(!i?.enable)return;const n=i.draw;if(!n.enable)return;const s=this.raw;for(const i of this.paths){const a=i.path2d;if(t)if(a&&this.offset){const{drawPolygonMaskPath:i}=await e.e(856).then(e.bind(e,856));i(t,a,n.stroke,this.offset)}else if(s){const{drawPolygonMask:i}=await e.e(856).then(e.bind(e,856));i(t,s,n.stroke)}}}async init(){const t=this._container,i=t.actualOptions.polygon,e=t.retina.pixelRatio;i&&(this._moveRadius=i.move.radius*e,this._scale=i.scale*e,i.enable&&await this._initRawData())}async particleBounce(t,i,e){return await this._polygonBounce(t,i,e)}particlePosition(t){const i=this._container.actualOptions.polygon;if(i?.enable&&(this.raw?.length??0)>0)return(0,n.deepExtend)({},t||this._randomPoint())}particlesInitialization(){const t=this._container.actualOptions.polygon;return!(!t?.enable||"inline"!==t.type||"one-per-point"!==t.inline.arrangement&&"per-point"!==t.inline.arrangement)&&(this._drawPoints(),!0)}resize(){const t=this._container,i=t.actualOptions.polygon;if(!i?.enable||"none"===i.type)return;this.redrawTimeout&&clearTimeout(this.redrawTimeout);this.redrawTimeout=window.setTimeout((()=>{(async()=>{await this._initRawData(!0),await t.particles.redraw()})()}),250)}stop(){delete this.raw,delete this.paths}}}}]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tsParticles Polygon Mask Plugin v3.2.0 by Matteo Bruni */
|
package/787.min.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! For license information please see 787.min.js.LICENSE.txt */
|
|
2
|
+
(this.webpackChunk_tsparticles_plugin_polygon_mask=this.webpackChunk_tsparticles_plugin_polygon_mask||[]).push([[787],{787:(t,i,s)=>{s.d(i,{PolygonMaskPlugin:()=>r});var o=s(533);class e{constructor(){this.color=new o.OptionsColor,this.width=.5,this.opacity=1}load(t){t&&(this.color=o.OptionsColor.create(this.color,t.color),(0,o.isString)(this.color.value)&&(this.opacity=(0,o.stringToAlpha)(this.color.value)??this.opacity),void 0!==t.opacity&&(this.opacity=t.opacity),void 0!==t.width&&(this.width=t.width))}}class n{constructor(){this.enable=!1,this.stroke=new e}load(t){if(!t)return;void 0!==t.enable&&(this.enable=t.enable);const i=t.stroke;this.stroke.load(i)}}class a{constructor(){this.arrangement="one-per-point"}load(t){t&&void 0!==t.arrangement&&(this.arrangement=t.arrangement)}}class h{constructor(){this.path=[],this.size={height:0,width:0}}load(t){t&&(void 0!==t.path&&(this.path=t.path),void 0!==t.size&&(void 0!==t.size.width&&(this.size.width=t.size.width),void 0!==t.size.height&&(this.size.height=t.size.height)))}}class l{constructor(){this.radius=10,this.type="path"}load(t){t&&(void 0!==t.radius&&(this.radius=t.radius),void 0!==t.type&&(this.type=t.type))}}class d{constructor(){this.draw=new n,this.enable=!1,this.inline=new a,this.move=new l,this.scale=1,this.type="none"}load(t){t&&(this.draw.load(t.draw),this.inline.load(t.inline),this.move.load(t.move),void 0!==t.scale&&(this.scale=t.scale),void 0!==t.type&&(this.type=t.type),void 0!==t.enable?this.enable=t.enable:this.enable="none"!==this.type,void 0!==t.url&&(this.url=t.url),void 0!==t.data&&((0,o.isString)(t.data)?this.data=t.data:(this.data=new h,this.data.load(t.data))),void 0!==t.position&&(this.position=(0,o.deepExtend)({},t.position)))}}class r{constructor(t){this.id="polygonMask",this._engine=t}async getPlugin(t){const{PolygonMaskInstance:i}=await s.e(719).then(s.bind(s,719));return new i(t,this._engine)}loadOptions(t,i){if(!this.needsPlugin(t)&&!this.needsPlugin(i))return;let s=t.polygon;void 0===s?.load&&(t.polygon=s=new d),s.load(i?.polygon)}needsPlugin(t){return t?.polygon?.enable??(void 0!==t?.polygon?.type&&"none"!==t.polygon.type)}}}}]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tsParticles Polygon Mask Plugin v3.2.0 by Matteo Bruni */
|
package/856.min.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! For license information please see 856.min.js.LICENSE.txt */
|
|
2
|
+
(this.webpackChunk_tsparticles_plugin_polygon_mask=this.webpackChunk_tsparticles_plugin_polygon_mask||[]).push([[856],{856:(e,t,s)=>{s.d(t,{calcClosestPointOnSegment:()=>A,drawPolygonMask:()=>c,drawPolygonMaskPath:()=>T,parsePaths:()=>E,segmentBounce:()=>S});var o=s(533);const a=2,n={min:0,max:1},_=2;function c(e,t,s){const a=(0,o.rangeColorToRgb)(s.color);if(!a)return;const n=t[0];e.beginPath(),e.moveTo(n.x,n.y);for(const s of t)e.lineTo(s.x,s.y);e.closePath(),e.strokeStyle=(0,o.getStyleFromRgb)(a),e.lineWidth=s.width,e.stroke()}function T(e,t,s,a){const n=1,_=0,c=0,T=1;e.setTransform(n,_,c,T,a.x,a.y);const E=(0,o.rangeColorToRgb)(s.color);E&&(e.strokeStyle=(0,o.getStyleFromRgb)(E,s.opacity),e.lineWidth=s.width,e.stroke(t),e.resetTransform())}function E(e,t,s){const o=[];for(const a of e){const e=a.element.pathSegList,n=e?.numberOfItems??0,_={x:0,y:0};for(let a=0;a<n;a++){const n=e?.getItem(a),c=window.SVGPathSeg;switch(n?.pathSegType){case c.PATHSEG_MOVETO_ABS:case c.PATHSEG_LINETO_ABS:case c.PATHSEG_CURVETO_CUBIC_ABS:case c.PATHSEG_CURVETO_QUADRATIC_ABS:case c.PATHSEG_ARC_ABS:case c.PATHSEG_CURVETO_CUBIC_SMOOTH_ABS:case c.PATHSEG_CURVETO_QUADRATIC_SMOOTH_ABS:{const e=n;_.x=e.x,_.y=e.y;break}case c.PATHSEG_LINETO_HORIZONTAL_ABS:_.x=n.x;break;case c.PATHSEG_LINETO_VERTICAL_ABS:_.y=n.y;break;case c.PATHSEG_LINETO_REL:case c.PATHSEG_MOVETO_REL:case c.PATHSEG_CURVETO_CUBIC_REL:case c.PATHSEG_CURVETO_QUADRATIC_REL:case c.PATHSEG_ARC_REL:case c.PATHSEG_CURVETO_CUBIC_SMOOTH_REL:case c.PATHSEG_CURVETO_QUADRATIC_SMOOTH_REL:{const e=n;_.x+=e.x,_.y+=e.y;break}case c.PATHSEG_LINETO_HORIZONTAL_REL:_.x+=n.x;break;case c.PATHSEG_LINETO_VERTICAL_REL:_.y+=n.y;break;case c.PATHSEG_UNKNOWN:case c.PATHSEG_CLOSEPATH:continue}o.push({x:_.x*t+s.x,y:_.y*t+s.y})}}return o}function A(e,t,s){const{dx:_,dy:c}=(0,o.getDistances)(s,e),{dx:T,dy:E}=(0,o.getDistances)(t,e),A=(_*T+c*E)/(T**a+E**a),S={x:e.x+T*A,y:e.y+E*A,isOnSegment:A>=n.min&&A<=n.max};return A<n.min?(S.x=e.x,S.y=e.y):A>n.max&&(S.x=t.x,S.y=t.y),S}function S(e,t,s){const{dx:a,dy:n}=(0,o.getDistances)(e,t),c=Math.atan2(n,a),T=o.Vector.create(Math.sin(c),-Math.cos(c)),E=_*(s.x*T.x+s.y*T.y);T.multTo(E),s.subFrom(T)}}}]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tsParticles Polygon Mask Plugin v3.2.0 by Matteo Bruni */
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { deepExtend, errorPrefix, getDistance, getDistances, getRandom, isArray, isString, itemFromArray, } from "@tsparticles/engine";
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { deepExtend, errorPrefix, getDistance, getDistances, getRandom, isArray, isString, itemFromArray, percentDenominator, } from "@tsparticles/engine";
|
|
2
|
+
const noPolygonDataLoaded = `${errorPrefix} No polygon data loaded.`, noPolygonFound = `${errorPrefix} No polygon found, you need to specify SVG url in config.`, origin = {
|
|
3
|
+
x: 0,
|
|
4
|
+
y: 0,
|
|
5
|
+
}, half = 0.5, double = 2;
|
|
4
6
|
export class PolygonMaskInstance {
|
|
5
7
|
constructor(container, engine) {
|
|
6
8
|
this._checkInsidePolygon = (position) => {
|
|
@@ -11,19 +13,20 @@ export class PolygonMaskInstance {
|
|
|
11
13
|
if (!this.raw) {
|
|
12
14
|
throw new Error(noPolygonFound);
|
|
13
15
|
}
|
|
14
|
-
const canvasSize = container.canvas.size, x = position?.x ?? getRandom() * canvasSize.width, y = position?.y ?? getRandom() * canvasSize.height;
|
|
16
|
+
const canvasSize = container.canvas.size, x = position?.x ?? getRandom() * canvasSize.width, y = position?.y ?? getRandom() * canvasSize.height, indexOffset = 1;
|
|
15
17
|
let inside = false;
|
|
16
|
-
for (let i = 0, j = this.raw.length -
|
|
18
|
+
for (let i = 0, j = this.raw.length - indexOffset; i < this.raw.length; j = i++) {
|
|
17
19
|
const pi = this.raw[i], pj = this.raw[j], intersect = pi.y > y !== pj.y > y && x < ((pj.x - pi.x) * (y - pi.y)) / (pj.y - pi.y) + pi.x;
|
|
18
20
|
if (intersect) {
|
|
19
21
|
inside = !inside;
|
|
20
22
|
}
|
|
21
23
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
if (options.type === "inside") {
|
|
25
|
+
return inside;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
return options.type === "outside" ? !inside : false;
|
|
29
|
+
}
|
|
27
30
|
};
|
|
28
31
|
this._createPath2D = () => {
|
|
29
32
|
const container = this._container, options = container.actualOptions.polygon;
|
|
@@ -45,13 +48,14 @@ export class PolygonMaskInstance {
|
|
|
45
48
|
else {
|
|
46
49
|
delete path.path2d;
|
|
47
50
|
}
|
|
48
|
-
if (path.path2d
|
|
51
|
+
if (path.path2d ?? !this.raw) {
|
|
49
52
|
continue;
|
|
50
53
|
}
|
|
51
54
|
path.path2d = new Path2D();
|
|
52
|
-
|
|
55
|
+
const firstIndex = 0, firstPoint = this.raw[firstIndex];
|
|
56
|
+
path.path2d.moveTo(firstPoint.x, firstPoint.y);
|
|
53
57
|
this.raw.forEach((pos, i) => {
|
|
54
|
-
if (i >
|
|
58
|
+
if (i > firstIndex) {
|
|
55
59
|
path.path2d?.lineTo(pos.x, pos.y);
|
|
56
60
|
}
|
|
57
61
|
});
|
|
@@ -63,7 +67,7 @@ export class PolygonMaskInstance {
|
|
|
63
67
|
if (!options) {
|
|
64
68
|
return;
|
|
65
69
|
}
|
|
66
|
-
const url = svgUrl
|
|
70
|
+
const url = svgUrl ?? options.url, forceDownload = force ?? false;
|
|
67
71
|
if (!url || (this.paths !== undefined && !forceDownload)) {
|
|
68
72
|
return this.raw;
|
|
69
73
|
}
|
|
@@ -71,14 +75,14 @@ export class PolygonMaskInstance {
|
|
|
71
75
|
if (!req.ok) {
|
|
72
76
|
throw new Error(`${errorPrefix} occurred during polygon mask download`);
|
|
73
77
|
}
|
|
74
|
-
return this._parseSvgPath(await req.text(), force);
|
|
78
|
+
return await this._parseSvgPath(await req.text(), force);
|
|
75
79
|
};
|
|
76
80
|
this._drawPoints = () => {
|
|
77
81
|
if (!this.raw) {
|
|
78
82
|
return;
|
|
79
83
|
}
|
|
80
84
|
for (const item of this.raw) {
|
|
81
|
-
this._container.particles.addParticle({
|
|
85
|
+
void this._container.particles.addParticle({
|
|
82
86
|
x: item.x,
|
|
83
87
|
y: item.y,
|
|
84
88
|
});
|
|
@@ -89,11 +93,11 @@ export class PolygonMaskInstance {
|
|
|
89
93
|
if (!polygonMaskOptions) {
|
|
90
94
|
return;
|
|
91
95
|
}
|
|
92
|
-
if (!this.raw
|
|
96
|
+
if (!this.raw?.length || !this.paths?.length) {
|
|
93
97
|
throw new Error(noPolygonDataLoaded);
|
|
94
98
|
}
|
|
95
99
|
let offset = 0, point;
|
|
96
|
-
const totalLength = this.paths.reduce((tot, path) => tot + path.length,
|
|
100
|
+
const baseAccumulator = 0, totalLength = this.paths.reduce((tot, path) => tot + path.length, baseAccumulator), distance = totalLength / options.particles.number.value;
|
|
97
101
|
for (const path of this.paths) {
|
|
98
102
|
const pathDistance = distance * index - offset;
|
|
99
103
|
if (pathDistance <= path.length) {
|
|
@@ -106,12 +110,12 @@ export class PolygonMaskInstance {
|
|
|
106
110
|
}
|
|
107
111
|
const scale = this._scale;
|
|
108
112
|
return {
|
|
109
|
-
x: (point?.x ??
|
|
110
|
-
y: (point?.y ??
|
|
113
|
+
x: (point?.x ?? origin.x) * scale + (this.offset?.x ?? origin.x),
|
|
114
|
+
y: (point?.y ?? origin.y) * scale + (this.offset?.y ?? origin.y),
|
|
111
115
|
};
|
|
112
116
|
};
|
|
113
117
|
this._getPointByIndex = (index) => {
|
|
114
|
-
if (!this.raw
|
|
118
|
+
if (!this.raw?.length) {
|
|
115
119
|
throw new Error(noPolygonDataLoaded);
|
|
116
120
|
}
|
|
117
121
|
const coords = this.raw[index % this.raw.length];
|
|
@@ -121,7 +125,7 @@ export class PolygonMaskInstance {
|
|
|
121
125
|
};
|
|
122
126
|
};
|
|
123
127
|
this._getRandomPoint = () => {
|
|
124
|
-
if (!this.raw
|
|
128
|
+
if (!this.raw?.length) {
|
|
125
129
|
throw new Error(noPolygonDataLoaded);
|
|
126
130
|
}
|
|
127
131
|
const coords = itemFromArray(this.raw);
|
|
@@ -135,13 +139,13 @@ export class PolygonMaskInstance {
|
|
|
135
139
|
if (!options) {
|
|
136
140
|
return;
|
|
137
141
|
}
|
|
138
|
-
if (!this.raw
|
|
142
|
+
if (!this.raw?.length || !this.paths?.length) {
|
|
139
143
|
throw new Error(noPolygonDataLoaded);
|
|
140
144
|
}
|
|
141
|
-
const path = itemFromArray(this.paths), distance = Math.floor(getRandom() * path.length) +
|
|
145
|
+
const path = itemFromArray(this.paths), offset = 1, distance = Math.floor(getRandom() * path.length) + offset, point = path.element.getPointAtLength(distance), scale = this._scale;
|
|
142
146
|
return {
|
|
143
|
-
x: point.x * scale + (this.offset?.x
|
|
144
|
-
y: point.y * scale + (this.offset?.y
|
|
147
|
+
x: point.x * scale + (this.offset?.x ?? origin.x),
|
|
148
|
+
y: point.y * scale + (this.offset?.y ?? origin.y),
|
|
145
149
|
};
|
|
146
150
|
};
|
|
147
151
|
this._initRawData = async (force) => {
|
|
@@ -163,14 +167,14 @@ export class PolygonMaskInstance {
|
|
|
163
167
|
const namespaces = 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"';
|
|
164
168
|
svg = `<svg ${namespaces} width="${data.size.width}" height="${data.size.height}">${path}</svg>`;
|
|
165
169
|
}
|
|
166
|
-
this.raw = this._parseSvgPath(svg, force);
|
|
170
|
+
this.raw = await this._parseSvgPath(svg, force);
|
|
167
171
|
}
|
|
168
172
|
this._createPath2D();
|
|
169
173
|
this._engine.dispatchEvent("polygonMaskLoaded", {
|
|
170
174
|
container: this._container,
|
|
171
175
|
});
|
|
172
176
|
};
|
|
173
|
-
this._parseSvgPath = (xml, force) => {
|
|
177
|
+
this._parseSvgPath = async (xml, force) => {
|
|
174
178
|
const forceDownload = force ?? false;
|
|
175
179
|
if (this.paths !== undefined && !forceDownload) {
|
|
176
180
|
return this.raw;
|
|
@@ -179,7 +183,7 @@ export class PolygonMaskInstance {
|
|
|
179
183
|
if (!options) {
|
|
180
184
|
return;
|
|
181
185
|
}
|
|
182
|
-
const parser = new DOMParser(), doc = parser.parseFromString(xml, "image/svg+xml"), svg = doc.getElementsByTagName("svg")[
|
|
186
|
+
const parser = new DOMParser(), doc = parser.parseFromString(xml, "image/svg+xml"), firstIndex = 0, svg = doc.getElementsByTagName("svg")[firstIndex];
|
|
183
187
|
let svgPaths = svg.getElementsByTagName("path");
|
|
184
188
|
if (!svgPaths.length) {
|
|
185
189
|
svgPaths = doc.getElementsByTagName("path");
|
|
@@ -202,31 +206,33 @@ export class PolygonMaskInstance {
|
|
|
202
206
|
y: 50,
|
|
203
207
|
}, canvasSize = container.canvas.size;
|
|
204
208
|
this.offset = {
|
|
205
|
-
x: (canvasSize.width * position.x) /
|
|
206
|
-
y: (canvasSize.height * position.y) /
|
|
209
|
+
x: (canvasSize.width * position.x) / percentDenominator - this.dimension.width * half,
|
|
210
|
+
y: (canvasSize.height * position.y) / percentDenominator - this.dimension.height * half,
|
|
207
211
|
};
|
|
212
|
+
const { parsePaths } = await import("./utils.js");
|
|
208
213
|
return parsePaths(this.paths, scale, this.offset);
|
|
209
214
|
};
|
|
210
|
-
this._polygonBounce = (particle,
|
|
215
|
+
this._polygonBounce = async (particle, delta, direction) => {
|
|
211
216
|
const options = this._container.actualOptions.polygon;
|
|
212
217
|
if (!this.raw || !options?.enable || direction !== "top") {
|
|
213
218
|
return false;
|
|
214
219
|
}
|
|
215
220
|
if (options.type === "inside" || options.type === "outside") {
|
|
216
221
|
let closest, dx, dy;
|
|
217
|
-
const pos = particle.getPosition(), radius = particle.getRadius();
|
|
218
|
-
for (let i = 0, j = this.raw.length -
|
|
219
|
-
const pi = this.raw[i], pj = this.raw[j];
|
|
220
|
-
closest =
|
|
222
|
+
const pos = particle.getPosition(), radius = particle.getRadius(), offset = 1;
|
|
223
|
+
for (let i = 0, j = this.raw.length - offset; i < this.raw.length; j = i++) {
|
|
224
|
+
const pi = this.raw[i], pj = this.raw[j], { calcClosestPointOnSegment } = await import("./utils.js");
|
|
225
|
+
closest = calcClosestPointOnSegment(pi, pj, pos);
|
|
221
226
|
const dist = getDistances(pos, closest);
|
|
222
227
|
[dx, dy] = [dist.dx, dist.dy];
|
|
223
228
|
if (dist.distance < radius) {
|
|
229
|
+
const { segmentBounce } = await import("./utils.js");
|
|
224
230
|
segmentBounce(pi, pj, particle.velocity);
|
|
225
231
|
return true;
|
|
226
232
|
}
|
|
227
233
|
}
|
|
228
234
|
if (closest && dx !== undefined && dy !== undefined && !this._checkInsidePolygon(pos)) {
|
|
229
|
-
const factor = { x: 1, y: 1 }, diameter = radius *
|
|
235
|
+
const factor = { x: 1, y: 1 }, diameter = radius * double, inverse = -1;
|
|
230
236
|
if (pos.x >= closest.x) {
|
|
231
237
|
factor.x = -1;
|
|
232
238
|
}
|
|
@@ -235,15 +241,15 @@ export class PolygonMaskInstance {
|
|
|
235
241
|
}
|
|
236
242
|
particle.position.x = closest.x + diameter * factor.x;
|
|
237
243
|
particle.position.y = closest.y + diameter * factor.y;
|
|
238
|
-
particle.velocity.mult(
|
|
244
|
+
particle.velocity.mult(inverse);
|
|
239
245
|
return true;
|
|
240
246
|
}
|
|
241
247
|
}
|
|
242
248
|
else if (options.type === "inline" && particle.initialPosition) {
|
|
243
249
|
const dist = getDistance(particle.initialPosition, particle.getPosition()), { velocity } = particle;
|
|
244
250
|
if (dist > this._moveRadius) {
|
|
245
|
-
velocity.x = velocity.y
|
|
246
|
-
velocity.y = velocity.x
|
|
251
|
+
velocity.x = velocity.y * half - velocity.x;
|
|
252
|
+
velocity.y = velocity.x * half - velocity.y;
|
|
247
253
|
return true;
|
|
248
254
|
}
|
|
249
255
|
}
|
|
@@ -302,7 +308,7 @@ export class PolygonMaskInstance {
|
|
|
302
308
|
options.type !== "inline" &&
|
|
303
309
|
this._checkInsidePolygon(position));
|
|
304
310
|
}
|
|
305
|
-
draw(context) {
|
|
311
|
+
async draw(context) {
|
|
306
312
|
if (!this.paths?.length) {
|
|
307
313
|
return;
|
|
308
314
|
}
|
|
@@ -321,9 +327,11 @@ export class PolygonMaskInstance {
|
|
|
321
327
|
continue;
|
|
322
328
|
}
|
|
323
329
|
if (path2d && this.offset) {
|
|
330
|
+
const { drawPolygonMaskPath } = await import("./utils.js");
|
|
324
331
|
drawPolygonMaskPath(context, path2d, polygonDraw.stroke, this.offset);
|
|
325
332
|
}
|
|
326
333
|
else if (rawData) {
|
|
334
|
+
const { drawPolygonMask } = await import("./utils.js");
|
|
327
335
|
drawPolygonMask(context, rawData, polygonDraw.stroke);
|
|
328
336
|
}
|
|
329
337
|
}
|
|
@@ -339,12 +347,12 @@ export class PolygonMaskInstance {
|
|
|
339
347
|
await this._initRawData();
|
|
340
348
|
}
|
|
341
349
|
}
|
|
342
|
-
particleBounce(particle, delta, direction) {
|
|
343
|
-
return this._polygonBounce(particle, delta, direction);
|
|
350
|
+
async particleBounce(particle, delta, direction) {
|
|
351
|
+
return await this._polygonBounce(particle, delta, direction);
|
|
344
352
|
}
|
|
345
353
|
particlePosition(position) {
|
|
346
|
-
const options = this._container.actualOptions.polygon;
|
|
347
|
-
if (!(options?.enable && (this.raw?.length ??
|
|
354
|
+
const options = this._container.actualOptions.polygon, defaultLength = 0;
|
|
355
|
+
if (!(options?.enable && (this.raw?.length ?? defaultLength) > defaultLength)) {
|
|
348
356
|
return;
|
|
349
357
|
}
|
|
350
358
|
return deepExtend({}, position ? position : this._randomPoint());
|
|
@@ -368,10 +376,13 @@ export class PolygonMaskInstance {
|
|
|
368
376
|
if (this.redrawTimeout) {
|
|
369
377
|
clearTimeout(this.redrawTimeout);
|
|
370
378
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
379
|
+
const timeout = 250;
|
|
380
|
+
this.redrawTimeout = window.setTimeout(() => {
|
|
381
|
+
void (async () => {
|
|
382
|
+
await this._initRawData(true);
|
|
383
|
+
await container.particles.redraw();
|
|
384
|
+
})();
|
|
385
|
+
}, timeout);
|
|
375
386
|
}
|
|
376
387
|
stop() {
|
|
377
388
|
delete this.raw;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { PolygonMask } from "./Options/Classes/PolygonMask.js";
|
|
2
|
+
export class PolygonMaskPlugin {
|
|
3
|
+
constructor(engine) {
|
|
4
|
+
this.id = "polygonMask";
|
|
5
|
+
this._engine = engine;
|
|
6
|
+
}
|
|
7
|
+
async getPlugin(container) {
|
|
8
|
+
const { PolygonMaskInstance } = await import("./PolygonMaskInstance.js");
|
|
9
|
+
return new PolygonMaskInstance(container, this._engine);
|
|
10
|
+
}
|
|
11
|
+
loadOptions(options, source) {
|
|
12
|
+
if (!this.needsPlugin(options) && !this.needsPlugin(source)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let polygonOptions = options.polygon;
|
|
16
|
+
if (polygonOptions?.load === undefined) {
|
|
17
|
+
options.polygon = polygonOptions = new PolygonMask();
|
|
18
|
+
}
|
|
19
|
+
polygonOptions.load(source?.polygon);
|
|
20
|
+
}
|
|
21
|
+
needsPlugin(options) {
|
|
22
|
+
return (options?.polygon?.enable ??
|
|
23
|
+
(options?.polygon?.type !== undefined && options.polygon.type !== "none"));
|
|
24
|
+
}
|
|
25
|
+
}
|
package/browser/index.js
CHANGED
|
@@ -1,30 +1,6 @@
|
|
|
1
1
|
import "./pathseg.js";
|
|
2
|
-
import { PolygonMask } from "./Options/Classes/PolygonMask.js";
|
|
3
|
-
import { PolygonMaskInstance } from "./PolygonMaskInstance.js";
|
|
4
|
-
class PolygonMaskPlugin {
|
|
5
|
-
constructor(engine) {
|
|
6
|
-
this.id = "polygonMask";
|
|
7
|
-
this._engine = engine;
|
|
8
|
-
}
|
|
9
|
-
getPlugin(container) {
|
|
10
|
-
return new PolygonMaskInstance(container, this._engine);
|
|
11
|
-
}
|
|
12
|
-
loadOptions(options, source) {
|
|
13
|
-
if (!this.needsPlugin(options) && !this.needsPlugin(source)) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
let polygonOptions = options.polygon;
|
|
17
|
-
if (polygonOptions?.load === undefined) {
|
|
18
|
-
options.polygon = polygonOptions = new PolygonMask();
|
|
19
|
-
}
|
|
20
|
-
polygonOptions.load(source?.polygon);
|
|
21
|
-
}
|
|
22
|
-
needsPlugin(options) {
|
|
23
|
-
return (options?.polygon?.enable ??
|
|
24
|
-
(options?.polygon?.type !== undefined && options.polygon.type !== "none"));
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
2
|
export async function loadPolygonMaskPlugin(engine, refresh = true) {
|
|
3
|
+
const { PolygonMaskPlugin } = await import("./PolygonMaskPlugin.js");
|
|
28
4
|
await engine.addPlugin(new PolygonMaskPlugin(engine), refresh);
|
|
29
5
|
}
|
|
30
6
|
export * from "./Enums/PolygonMaskInlineArrangement.js";
|
package/browser/utils.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { Vector, getDistances, getStyleFromRgb, rangeColorToRgb } from "@tsparticles/engine";
|
|
2
|
+
const squareExp = 2, inSegmentRange = {
|
|
3
|
+
min: 0,
|
|
4
|
+
max: 1,
|
|
5
|
+
}, double = 2;
|
|
2
6
|
export function drawPolygonMask(context, rawData, stroke) {
|
|
3
7
|
const color = rangeColorToRgb(stroke.color);
|
|
4
8
|
if (!color) {
|
|
5
9
|
return;
|
|
6
10
|
}
|
|
11
|
+
const firstIndex = 0, firstItem = rawData[firstIndex];
|
|
7
12
|
context.beginPath();
|
|
8
|
-
context.moveTo(
|
|
13
|
+
context.moveTo(firstItem.x, firstItem.y);
|
|
9
14
|
for (const item of rawData) {
|
|
10
15
|
context.lineTo(item.x, item.y);
|
|
11
16
|
}
|
|
@@ -15,7 +20,13 @@ export function drawPolygonMask(context, rawData, stroke) {
|
|
|
15
20
|
context.stroke();
|
|
16
21
|
}
|
|
17
22
|
export function drawPolygonMaskPath(context, path, stroke, position) {
|
|
18
|
-
|
|
23
|
+
const defaultTransform = {
|
|
24
|
+
a: 1,
|
|
25
|
+
b: 0,
|
|
26
|
+
c: 0,
|
|
27
|
+
d: 1,
|
|
28
|
+
};
|
|
29
|
+
context.setTransform(defaultTransform.a, defaultTransform.b, defaultTransform.c, defaultTransform.d, position.x, position.y);
|
|
19
30
|
const color = rangeColorToRgb(stroke.color);
|
|
20
31
|
if (!color) {
|
|
21
32
|
return;
|
|
@@ -23,12 +34,12 @@ export function drawPolygonMaskPath(context, path, stroke, position) {
|
|
|
23
34
|
context.strokeStyle = getStyleFromRgb(color, stroke.opacity);
|
|
24
35
|
context.lineWidth = stroke.width;
|
|
25
36
|
context.stroke(path);
|
|
26
|
-
context.
|
|
37
|
+
context.resetTransform();
|
|
27
38
|
}
|
|
28
39
|
export function parsePaths(paths, scale, offset) {
|
|
29
|
-
const res = [];
|
|
40
|
+
const res = [], defaultCount = 0;
|
|
30
41
|
for (const path of paths) {
|
|
31
|
-
const segments = path.element.pathSegList, len = segments?.numberOfItems ??
|
|
42
|
+
const segments = path.element.pathSegList, len = segments?.numberOfItems ?? defaultCount, p = {
|
|
32
43
|
x: 0,
|
|
33
44
|
y: 0,
|
|
34
45
|
};
|
|
@@ -83,24 +94,24 @@ export function parsePaths(paths, scale, offset) {
|
|
|
83
94
|
}
|
|
84
95
|
return res;
|
|
85
96
|
}
|
|
86
|
-
export function
|
|
87
|
-
const { dx: dx1, dy: dy1 } = getDistances(pos, s1), { dx: dx2, dy: dy2 } = getDistances(s2, s1), t = (dx1 * dx2 + dy1 * dy2) / (dx2 **
|
|
97
|
+
export function calcClosestPointOnSegment(s1, s2, pos) {
|
|
98
|
+
const { dx: dx1, dy: dy1 } = getDistances(pos, s1), { dx: dx2, dy: dy2 } = getDistances(s2, s1), t = (dx1 * dx2 + dy1 * dy2) / (dx2 ** squareExp + dy2 ** squareExp), res = {
|
|
88
99
|
x: s1.x + dx2 * t,
|
|
89
100
|
y: s1.y + dy2 * t,
|
|
90
|
-
isOnSegment: t >=
|
|
101
|
+
isOnSegment: t >= inSegmentRange.min && t <= inSegmentRange.max,
|
|
91
102
|
};
|
|
92
|
-
if (t <
|
|
103
|
+
if (t < inSegmentRange.min) {
|
|
93
104
|
res.x = s1.x;
|
|
94
105
|
res.y = s1.y;
|
|
95
106
|
}
|
|
96
|
-
else if (t >
|
|
107
|
+
else if (t > inSegmentRange.max) {
|
|
97
108
|
res.x = s2.x;
|
|
98
109
|
res.y = s2.y;
|
|
99
110
|
}
|
|
100
111
|
return res;
|
|
101
112
|
}
|
|
102
113
|
export function segmentBounce(start, stop, velocity) {
|
|
103
|
-
const { dx, dy } = getDistances(start, stop), wallAngle = Math.atan2(dy, dx), wallNormal = Vector.create(Math.sin(wallAngle), -Math.cos(wallAngle)), d =
|
|
114
|
+
const { dx, dy } = getDistances(start, stop), wallAngle = Math.atan2(dy, dx), wallNormal = Vector.create(Math.sin(wallAngle), -Math.cos(wallAngle)), d = double * (velocity.x * wallNormal.x + velocity.y * wallNormal.y);
|
|
104
115
|
wallNormal.multTo(d);
|
|
105
116
|
velocity.subFrom(wallNormal);
|
|
106
117
|
}
|