@tsparticles/plugin-polygon-mask 4.0.0-alpha.2 → 4.0.0-alpha.20
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/185.min.js +1 -0
- package/459.min.js +2 -0
- package/browser/Options/Classes/PolygonMask.js +9 -0
- package/browser/Options/Classes/PolygonMaskDraw.js +2 -0
- package/browser/Options/Classes/PolygonMaskDrawStroke.js +4 -0
- package/browser/Options/Classes/PolygonMaskInline.js +1 -0
- package/browser/Options/Classes/PolygonMaskLocalSvg.js +2 -0
- package/browser/Options/Classes/PolygonMaskMove.js +2 -0
- package/browser/PolygonMaskInstance.js +313 -308
- package/browser/PolygonMaskPlugin.js +6 -5
- package/browser/index.js +3 -3
- package/browser/pathseg.js +5 -15
- package/browser/utils.js +4 -2
- package/cjs/Options/Classes/PolygonMask.js +9 -0
- package/cjs/Options/Classes/PolygonMaskDraw.js +2 -0
- package/cjs/Options/Classes/PolygonMaskDrawStroke.js +4 -0
- package/cjs/Options/Classes/PolygonMaskInline.js +1 -0
- package/cjs/Options/Classes/PolygonMaskLocalSvg.js +2 -0
- package/cjs/Options/Classes/PolygonMaskMove.js +2 -0
- package/cjs/PolygonMaskInstance.js +313 -308
- package/cjs/PolygonMaskPlugin.js +6 -5
- package/cjs/index.js +3 -3
- package/cjs/pathseg.js +5 -15
- package/cjs/utils.js +4 -2
- package/dist_browser_PolygonMaskInstance_js.js +50 -0
- package/dist_browser_PolygonMaskPlugin_js.js +8 -38
- package/esm/Options/Classes/PolygonMask.js +9 -0
- package/esm/Options/Classes/PolygonMaskDraw.js +2 -0
- package/esm/Options/Classes/PolygonMaskDrawStroke.js +4 -0
- package/esm/Options/Classes/PolygonMaskInline.js +1 -0
- package/esm/Options/Classes/PolygonMaskLocalSvg.js +2 -0
- package/esm/Options/Classes/PolygonMaskMove.js +2 -0
- package/esm/PolygonMaskInstance.js +313 -308
- package/esm/PolygonMaskPlugin.js +6 -5
- package/esm/index.js +3 -3
- package/esm/pathseg.js +5 -15
- package/esm/utils.js +4 -2
- package/package.json +2 -2
- package/report.html +3 -3
- package/tsparticles.plugin.polygon-mask.js +15 -15
- package/tsparticles.plugin.polygon-mask.min.js +2 -2
- package/types/PolygonMaskPlugin.d.ts +4 -5
- package/types/index.d.ts +1 -1
- package/umd/Options/Classes/PolygonMask.js +9 -0
- package/umd/Options/Classes/PolygonMaskDraw.js +2 -0
- package/umd/Options/Classes/PolygonMaskDrawStroke.js +4 -0
- package/umd/Options/Classes/PolygonMaskInline.js +1 -0
- package/umd/Options/Classes/PolygonMaskLocalSvg.js +2 -0
- package/umd/Options/Classes/PolygonMaskMove.js +2 -0
- package/umd/PolygonMaskInstance.js +312 -307
- package/umd/PolygonMaskPlugin.js +41 -6
- package/umd/index.js +3 -3
- package/umd/pathseg.js +5 -15
- package/umd/utils.js +4 -2
- package/409.min.js +0 -2
- package/409.min.js.LICENSE.txt +0 -1
- package/tsparticles.plugin.polygon-mask.min.js.LICENSE.txt +0 -1
|
@@ -1,317 +1,20 @@
|
|
|
1
1
|
import "./pathseg.js";
|
|
2
|
-
import { OutModeDirection, deepExtend, getDistance, getDistances, getRandom, isArray, isString, itemFromArray, percentDenominator, safeDocument, } from "@tsparticles/engine";
|
|
2
|
+
import { OutModeDirection, deepExtend, double, getDistance, getDistances, getRandom, half, isArray, isString, itemFromArray, originPoint, percentDenominator, safeDocument, } from "@tsparticles/engine";
|
|
3
3
|
import { calcClosestPointOnSegment, drawPolygonMask, drawPolygonMaskPath, parsePaths, segmentBounce } from "./utils.js";
|
|
4
4
|
import { PolygonMaskInlineArrangement } from "./Enums/PolygonMaskInlineArrangement.js";
|
|
5
5
|
import { PolygonMaskType } from "./Enums/PolygonMaskType.js";
|
|
6
|
-
const noPolygonDataLoaded = `No polygon data loaded.`, noPolygonFound = `No polygon found, you need to specify SVG url in config
|
|
7
|
-
x: 0,
|
|
8
|
-
y: 0,
|
|
9
|
-
}, half = 0.5, double = 2;
|
|
6
|
+
const noPolygonDataLoaded = `No polygon data loaded.`, noPolygonFound = `No polygon found, you need to specify SVG url in config.`;
|
|
10
7
|
export class PolygonMaskInstance {
|
|
8
|
+
dimension;
|
|
9
|
+
offset;
|
|
10
|
+
paths;
|
|
11
|
+
raw;
|
|
12
|
+
redrawTimeout;
|
|
13
|
+
_container;
|
|
14
|
+
_engine;
|
|
15
|
+
_moveRadius;
|
|
16
|
+
_scale;
|
|
11
17
|
constructor(container, engine) {
|
|
12
|
-
this._checkInsidePolygon = position => {
|
|
13
|
-
const container = this._container, options = container.actualOptions.polygon;
|
|
14
|
-
if (!options?.enable || options.type === PolygonMaskType.none || options.type === PolygonMaskType.inline) {
|
|
15
|
-
return true;
|
|
16
|
-
}
|
|
17
|
-
if (!this.raw) {
|
|
18
|
-
throw new Error(noPolygonFound);
|
|
19
|
-
}
|
|
20
|
-
const canvasSize = container.canvas.size, x = position?.x ?? getRandom() * canvasSize.width, y = position?.y ?? getRandom() * canvasSize.height, indexOffset = 1;
|
|
21
|
-
let inside = false;
|
|
22
|
-
for (let i = 0, j = this.raw.length - indexOffset; i < this.raw.length; j = i++) {
|
|
23
|
-
const pi = this.raw[i], pj = this.raw[j];
|
|
24
|
-
if (!pi || !pj) {
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
const intersect = pi.y > y !== pj.y > y && x < ((pj.x - pi.x) * (y - pi.y)) / (pj.y - pi.y) + pi.x;
|
|
28
|
-
if (intersect) {
|
|
29
|
-
inside = !inside;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (options.type === PolygonMaskType.inside) {
|
|
33
|
-
return inside;
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
return !inside;
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
this._createPath2D = () => {
|
|
40
|
-
const container = this._container, options = container.actualOptions.polygon;
|
|
41
|
-
if (!options || !this.paths?.length) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
for (const path of this.paths) {
|
|
45
|
-
const pathData = path.element.getAttribute("d");
|
|
46
|
-
if (pathData) {
|
|
47
|
-
const path2d = new Path2D(pathData), matrix = safeDocument().createElementNS("http://www.w3.org/2000/svg", "svg").createSVGMatrix(), finalPath = new Path2D(), transform = matrix.scale(this._scale);
|
|
48
|
-
finalPath.addPath(path2d, transform);
|
|
49
|
-
path.path2d = finalPath;
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
delete path.path2d;
|
|
53
|
-
}
|
|
54
|
-
if (path.path2d ?? !this.raw) {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
path.path2d = new Path2D();
|
|
58
|
-
const firstIndex = 0, firstPoint = this.raw[firstIndex];
|
|
59
|
-
if (!firstPoint) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
path.path2d.moveTo(firstPoint.x, firstPoint.y);
|
|
63
|
-
this.raw.forEach((pos, i) => {
|
|
64
|
-
if (i > firstIndex) {
|
|
65
|
-
path.path2d?.lineTo(pos.x, pos.y);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
path.path2d.closePath();
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
this._downloadSvgPath = async (svgUrl, force) => {
|
|
72
|
-
const options = this._container.actualOptions.polygon;
|
|
73
|
-
if (!options) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const url = svgUrl ?? options.url, forceDownload = force ?? false;
|
|
77
|
-
if (!url || (this.paths !== undefined && !forceDownload)) {
|
|
78
|
-
return this.raw;
|
|
79
|
-
}
|
|
80
|
-
const req = await fetch(url);
|
|
81
|
-
if (!req.ok) {
|
|
82
|
-
throw new Error(`Error occurred during polygon mask download`);
|
|
83
|
-
}
|
|
84
|
-
return this._parseSvgPath(await req.text(), force);
|
|
85
|
-
};
|
|
86
|
-
this._drawPoints = () => {
|
|
87
|
-
if (!this.raw) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
for (const item of this.raw) {
|
|
91
|
-
void this._container.particles.addParticle({
|
|
92
|
-
x: item.x,
|
|
93
|
-
y: item.y,
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
this._getEquidistantPointByIndex = index => {
|
|
98
|
-
const container = this._container, options = container.actualOptions, polygonMaskOptions = options.polygon;
|
|
99
|
-
if (!polygonMaskOptions) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
if (!this.raw?.length || !this.paths?.length) {
|
|
103
|
-
throw new Error(noPolygonDataLoaded);
|
|
104
|
-
}
|
|
105
|
-
let offset = 0, point;
|
|
106
|
-
const baseAccumulator = 0, totalLength = this.paths.reduce((tot, path) => tot + path.length, baseAccumulator), distance = totalLength / options.particles.number.value;
|
|
107
|
-
for (const path of this.paths) {
|
|
108
|
-
const pathDistance = distance * index - offset;
|
|
109
|
-
if (pathDistance <= path.length) {
|
|
110
|
-
point = path.element.getPointAtLength(pathDistance);
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
else {
|
|
114
|
-
offset += path.length;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
const scale = this._scale;
|
|
118
|
-
return {
|
|
119
|
-
x: (point?.x ?? origin.x) * scale + (this.offset?.x ?? origin.x),
|
|
120
|
-
y: (point?.y ?? origin.y) * scale + (this.offset?.y ?? origin.y),
|
|
121
|
-
};
|
|
122
|
-
};
|
|
123
|
-
this._getPointByIndex = index => {
|
|
124
|
-
if (!this.raw?.length) {
|
|
125
|
-
throw new Error(noPolygonDataLoaded);
|
|
126
|
-
}
|
|
127
|
-
const coords = this.raw[index % this.raw.length];
|
|
128
|
-
if (!coords) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
return {
|
|
132
|
-
x: coords.x,
|
|
133
|
-
y: coords.y,
|
|
134
|
-
};
|
|
135
|
-
};
|
|
136
|
-
this._getRandomPoint = () => {
|
|
137
|
-
if (!this.raw?.length) {
|
|
138
|
-
throw new Error(noPolygonDataLoaded);
|
|
139
|
-
}
|
|
140
|
-
const coords = itemFromArray(this.raw);
|
|
141
|
-
if (!coords) {
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
return {
|
|
145
|
-
x: coords.x,
|
|
146
|
-
y: coords.y,
|
|
147
|
-
};
|
|
148
|
-
};
|
|
149
|
-
this._getRandomPointByLength = () => {
|
|
150
|
-
const container = this._container, options = container.actualOptions.polygon;
|
|
151
|
-
if (!options) {
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
if (!this.raw?.length || !this.paths?.length) {
|
|
155
|
-
throw new Error(noPolygonDataLoaded);
|
|
156
|
-
}
|
|
157
|
-
const path = itemFromArray(this.paths);
|
|
158
|
-
if (!path) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const offset = 1, distance = Math.floor(getRandom() * path.length) + offset, point = path.element.getPointAtLength(distance), scale = this._scale;
|
|
162
|
-
return {
|
|
163
|
-
x: point.x * scale + (this.offset?.x ?? origin.x),
|
|
164
|
-
y: point.y * scale + (this.offset?.y ?? origin.y),
|
|
165
|
-
};
|
|
166
|
-
};
|
|
167
|
-
this._initRawData = async (force) => {
|
|
168
|
-
const options = this._container.actualOptions.polygon;
|
|
169
|
-
if (!options) {
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (options.url) {
|
|
173
|
-
this.raw = await this._downloadSvgPath(options.url, force);
|
|
174
|
-
}
|
|
175
|
-
else if (options.data) {
|
|
176
|
-
const data = options.data;
|
|
177
|
-
let svg;
|
|
178
|
-
if (isString(data)) {
|
|
179
|
-
svg = data;
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
const getPath = (p) => `<path d="${p}" />`, path = isArray(data.path) ? data.path.map(getPath).join("") : getPath(data.path);
|
|
183
|
-
const namespaces = 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"';
|
|
184
|
-
svg = `<svg ${namespaces} width="${data.size.width.toString()}" height="${data.size.height.toString()}">${path}</svg>`;
|
|
185
|
-
}
|
|
186
|
-
this.raw = this._parseSvgPath(svg, force);
|
|
187
|
-
}
|
|
188
|
-
this._createPath2D();
|
|
189
|
-
this._engine.dispatchEvent("polygonMaskLoaded", {
|
|
190
|
-
container: this._container,
|
|
191
|
-
});
|
|
192
|
-
};
|
|
193
|
-
this._parseSvgPath = (xml, force) => {
|
|
194
|
-
const forceDownload = force ?? false;
|
|
195
|
-
if (this.paths !== undefined && !forceDownload) {
|
|
196
|
-
return this.raw;
|
|
197
|
-
}
|
|
198
|
-
const container = this._container, options = container.actualOptions.polygon;
|
|
199
|
-
if (!options) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
const parser = new DOMParser(), doc = parser.parseFromString(xml, "image/svg+xml"), firstIndex = 0, svg = doc.getElementsByTagName("svg")[firstIndex];
|
|
203
|
-
if (!svg) {
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
let svgPaths = svg.getElementsByTagName("path");
|
|
207
|
-
if (!svgPaths.length) {
|
|
208
|
-
svgPaths = doc.getElementsByTagName("path");
|
|
209
|
-
}
|
|
210
|
-
this.paths = [];
|
|
211
|
-
for (let i = 0; i < svgPaths.length; i++) {
|
|
212
|
-
const path = svgPaths.item(i);
|
|
213
|
-
if (path) {
|
|
214
|
-
this.paths.push({
|
|
215
|
-
element: path,
|
|
216
|
-
length: path.getTotalLength(),
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
const scale = this._scale;
|
|
221
|
-
this.dimension.width = parseFloat(svg.getAttribute("width") ?? "0") * scale;
|
|
222
|
-
this.dimension.height = parseFloat(svg.getAttribute("height") ?? "0") * scale;
|
|
223
|
-
const position = options.position ?? {
|
|
224
|
-
x: 50,
|
|
225
|
-
y: 50,
|
|
226
|
-
}, canvasSize = container.canvas.size;
|
|
227
|
-
this.offset = {
|
|
228
|
-
x: (canvasSize.width * position.x) / percentDenominator - this.dimension.width * half,
|
|
229
|
-
y: (canvasSize.height * position.y) / percentDenominator - this.dimension.height * half,
|
|
230
|
-
};
|
|
231
|
-
return parsePaths(this.paths, scale, this.offset);
|
|
232
|
-
};
|
|
233
|
-
this._polygonBounce = (particle, _delta, direction) => {
|
|
234
|
-
const options = this._container.actualOptions.polygon;
|
|
235
|
-
if (!this.raw || !options?.enable || direction !== OutModeDirection.top) {
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
if (options.type === PolygonMaskType.inside || options.type === PolygonMaskType.outside) {
|
|
239
|
-
let closest, dx, dy;
|
|
240
|
-
const pos = particle.getPosition(), radius = particle.getRadius(), offset = 1;
|
|
241
|
-
for (let i = 0, j = this.raw.length - offset; i < this.raw.length; j = i++) {
|
|
242
|
-
const pi = this.raw[i], pj = this.raw[j];
|
|
243
|
-
if (!pi || !pj) {
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
closest = calcClosestPointOnSegment(pi, pj, pos);
|
|
247
|
-
const dist = getDistances(pos, closest);
|
|
248
|
-
[dx, dy] = [dist.dx, dist.dy];
|
|
249
|
-
if (dist.distance < radius) {
|
|
250
|
-
segmentBounce(pi, pj, particle.velocity);
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
if (closest && dx !== undefined && dy !== undefined && !this._checkInsidePolygon(pos)) {
|
|
255
|
-
const factor = { x: 1, y: 1 }, diameter = radius * double, inverse = -1;
|
|
256
|
-
if (pos.x >= closest.x) {
|
|
257
|
-
factor.x = -1;
|
|
258
|
-
}
|
|
259
|
-
if (pos.y >= closest.y) {
|
|
260
|
-
factor.y = -1;
|
|
261
|
-
}
|
|
262
|
-
particle.position.x = closest.x + diameter * factor.x;
|
|
263
|
-
particle.position.y = closest.y + diameter * factor.y;
|
|
264
|
-
particle.velocity.mult(inverse);
|
|
265
|
-
return true;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
else if (options.type === PolygonMaskType.inline) {
|
|
269
|
-
const dist = getDistance(particle.initialPosition, particle.getPosition()), { velocity } = particle;
|
|
270
|
-
if (dist > this._moveRadius) {
|
|
271
|
-
velocity.x = velocity.y * half - velocity.x;
|
|
272
|
-
velocity.y = velocity.x * half - velocity.y;
|
|
273
|
-
return true;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
return false;
|
|
277
|
-
};
|
|
278
|
-
this._randomPoint = () => {
|
|
279
|
-
const container = this._container, options = container.actualOptions.polygon;
|
|
280
|
-
if (!options) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
let position;
|
|
284
|
-
if (options.type === PolygonMaskType.inline) {
|
|
285
|
-
switch (options.inline.arrangement) {
|
|
286
|
-
case PolygonMaskInlineArrangement.randomPoint:
|
|
287
|
-
position = this._getRandomPoint();
|
|
288
|
-
break;
|
|
289
|
-
case PolygonMaskInlineArrangement.randomLength:
|
|
290
|
-
position = this._getRandomPointByLength();
|
|
291
|
-
break;
|
|
292
|
-
case PolygonMaskInlineArrangement.equidistant:
|
|
293
|
-
position = this._getEquidistantPointByIndex(container.particles.count);
|
|
294
|
-
break;
|
|
295
|
-
case PolygonMaskInlineArrangement.onePerPoint:
|
|
296
|
-
case PolygonMaskInlineArrangement.perPoint:
|
|
297
|
-
default:
|
|
298
|
-
position = this._getPointByIndex(container.particles.count);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
302
|
-
const canvasSize = container.canvas.size;
|
|
303
|
-
position = {
|
|
304
|
-
x: getRandom() * canvasSize.width,
|
|
305
|
-
y: getRandom() * canvasSize.height,
|
|
306
|
-
};
|
|
307
|
-
}
|
|
308
|
-
if (this._checkInsidePolygon(position)) {
|
|
309
|
-
return position;
|
|
310
|
-
}
|
|
311
|
-
else {
|
|
312
|
-
return this._randomPoint();
|
|
313
|
-
}
|
|
314
|
-
};
|
|
315
18
|
this._container = container;
|
|
316
19
|
this._engine = engine;
|
|
317
20
|
this.dimension = {
|
|
@@ -403,4 +106,306 @@ export class PolygonMaskInstance {
|
|
|
403
106
|
delete this.raw;
|
|
404
107
|
delete this.paths;
|
|
405
108
|
}
|
|
109
|
+
_checkInsidePolygon = position => {
|
|
110
|
+
const container = this._container, options = container.actualOptions.polygon;
|
|
111
|
+
if (!options?.enable || options.type === PolygonMaskType.none || options.type === PolygonMaskType.inline) {
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
if (!this.raw) {
|
|
115
|
+
throw new Error(noPolygonFound);
|
|
116
|
+
}
|
|
117
|
+
const canvasSize = container.canvas.size, x = position?.x ?? getRandom() * canvasSize.width, y = position?.y ?? getRandom() * canvasSize.height, indexOffset = 1;
|
|
118
|
+
let inside = false;
|
|
119
|
+
for (let i = 0, j = this.raw.length - indexOffset; i < this.raw.length; j = i++) {
|
|
120
|
+
const pi = this.raw[i], pj = this.raw[j];
|
|
121
|
+
if (!pi || !pj) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const intersect = pi.y > y !== pj.y > y && x < ((pj.x - pi.x) * (y - pi.y)) / (pj.y - pi.y) + pi.x;
|
|
125
|
+
if (intersect) {
|
|
126
|
+
inside = !inside;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (options.type === PolygonMaskType.inside) {
|
|
130
|
+
return inside;
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
return !inside;
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
_createPath2D = () => {
|
|
137
|
+
const container = this._container, options = container.actualOptions.polygon;
|
|
138
|
+
if (!options || !this.paths?.length) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
for (const path of this.paths) {
|
|
142
|
+
const pathData = path.element.getAttribute("d");
|
|
143
|
+
if (pathData) {
|
|
144
|
+
const path2d = new Path2D(pathData), matrix = safeDocument().createElementNS("http://www.w3.org/2000/svg", "svg").createSVGMatrix(), finalPath = new Path2D(), transform = matrix.scale(this._scale);
|
|
145
|
+
finalPath.addPath(path2d, transform);
|
|
146
|
+
path.path2d = finalPath;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
delete path.path2d;
|
|
150
|
+
}
|
|
151
|
+
if (path.path2d ?? !this.raw) {
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
path.path2d = new Path2D();
|
|
155
|
+
const firstIndex = 0, firstPoint = this.raw[firstIndex];
|
|
156
|
+
if (!firstPoint) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
path.path2d.moveTo(firstPoint.x, firstPoint.y);
|
|
160
|
+
this.raw.forEach((pos, i) => {
|
|
161
|
+
if (i > firstIndex) {
|
|
162
|
+
path.path2d?.lineTo(pos.x, pos.y);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
path.path2d.closePath();
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
_downloadSvgPath = async (svgUrl, force) => {
|
|
169
|
+
const options = this._container.actualOptions.polygon;
|
|
170
|
+
if (!options) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const url = svgUrl ?? options.url, forceDownload = force ?? false;
|
|
174
|
+
if (!url || (this.paths !== undefined && !forceDownload)) {
|
|
175
|
+
return this.raw;
|
|
176
|
+
}
|
|
177
|
+
const req = await fetch(url);
|
|
178
|
+
if (!req.ok) {
|
|
179
|
+
throw new Error(`Error occurred during polygon mask download`);
|
|
180
|
+
}
|
|
181
|
+
return this._parseSvgPath(await req.text(), force);
|
|
182
|
+
};
|
|
183
|
+
_drawPoints = () => {
|
|
184
|
+
if (!this.raw) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
for (const item of this.raw) {
|
|
188
|
+
this._container.particles.addParticle({
|
|
189
|
+
x: item.x,
|
|
190
|
+
y: item.y,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
_getEquidistantPointByIndex = index => {
|
|
195
|
+
const container = this._container, options = container.actualOptions, polygonMaskOptions = options.polygon;
|
|
196
|
+
if (!polygonMaskOptions) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (!this.raw?.length || !this.paths?.length) {
|
|
200
|
+
throw new Error(noPolygonDataLoaded);
|
|
201
|
+
}
|
|
202
|
+
let offset = 0, point;
|
|
203
|
+
const baseAccumulator = 0, totalLength = this.paths.reduce((tot, path) => tot + path.length, baseAccumulator), distance = totalLength / options.particles.number.value;
|
|
204
|
+
for (const path of this.paths) {
|
|
205
|
+
const pathDistance = distance * index - offset;
|
|
206
|
+
if (pathDistance <= path.length) {
|
|
207
|
+
point = path.element.getPointAtLength(pathDistance);
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
offset += path.length;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
const scale = this._scale;
|
|
215
|
+
return {
|
|
216
|
+
x: (point?.x ?? originPoint.x) * scale + (this.offset?.x ?? originPoint.x),
|
|
217
|
+
y: (point?.y ?? originPoint.y) * scale + (this.offset?.y ?? originPoint.y),
|
|
218
|
+
};
|
|
219
|
+
};
|
|
220
|
+
_getPointByIndex = index => {
|
|
221
|
+
if (!this.raw?.length) {
|
|
222
|
+
throw new Error(noPolygonDataLoaded);
|
|
223
|
+
}
|
|
224
|
+
const coords = this.raw[index % this.raw.length];
|
|
225
|
+
if (!coords) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
x: coords.x,
|
|
230
|
+
y: coords.y,
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
_getRandomPoint = () => {
|
|
234
|
+
if (!this.raw?.length) {
|
|
235
|
+
throw new Error(noPolygonDataLoaded);
|
|
236
|
+
}
|
|
237
|
+
const coords = itemFromArray(this.raw);
|
|
238
|
+
if (!coords) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
x: coords.x,
|
|
243
|
+
y: coords.y,
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
_getRandomPointByLength = () => {
|
|
247
|
+
const container = this._container, options = container.actualOptions.polygon;
|
|
248
|
+
if (!options) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
if (!this.raw?.length || !this.paths?.length) {
|
|
252
|
+
throw new Error(noPolygonDataLoaded);
|
|
253
|
+
}
|
|
254
|
+
const path = itemFromArray(this.paths);
|
|
255
|
+
if (!path) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
const offset = 1, distance = Math.floor(getRandom() * path.length) + offset, point = path.element.getPointAtLength(distance), scale = this._scale;
|
|
259
|
+
return {
|
|
260
|
+
x: point.x * scale + (this.offset?.x ?? originPoint.x),
|
|
261
|
+
y: point.y * scale + (this.offset?.y ?? originPoint.y),
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
_initRawData = async (force) => {
|
|
265
|
+
const options = this._container.actualOptions.polygon;
|
|
266
|
+
if (!options) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (options.url) {
|
|
270
|
+
this.raw = await this._downloadSvgPath(options.url, force);
|
|
271
|
+
}
|
|
272
|
+
else if (options.data) {
|
|
273
|
+
const data = options.data;
|
|
274
|
+
let svg;
|
|
275
|
+
if (isString(data)) {
|
|
276
|
+
svg = data;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
const getPath = (p) => `<path d="${p}" />`, path = isArray(data.path) ? data.path.map(getPath).join("") : getPath(data.path), namespaces = 'xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"';
|
|
280
|
+
svg = `<svg ${namespaces} width="${data.size.width.toString()}" height="${data.size.height.toString()}">${path}</svg>`;
|
|
281
|
+
}
|
|
282
|
+
this.raw = this._parseSvgPath(svg, force);
|
|
283
|
+
}
|
|
284
|
+
this._createPath2D();
|
|
285
|
+
this._engine.dispatchEvent("polygonMaskLoaded", {
|
|
286
|
+
container: this._container,
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
_parseSvgPath = (xml, force) => {
|
|
290
|
+
const forceDownload = force ?? false;
|
|
291
|
+
if (this.paths !== undefined && !forceDownload) {
|
|
292
|
+
return this.raw;
|
|
293
|
+
}
|
|
294
|
+
const container = this._container, options = container.actualOptions.polygon;
|
|
295
|
+
if (!options) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const parser = new DOMParser(), doc = parser.parseFromString(xml, "image/svg+xml"), firstIndex = 0, svg = doc.getElementsByTagName("svg")[firstIndex];
|
|
299
|
+
if (!svg) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
let svgPaths = svg.getElementsByTagName("path");
|
|
303
|
+
if (!svgPaths.length) {
|
|
304
|
+
svgPaths = doc.getElementsByTagName("path");
|
|
305
|
+
}
|
|
306
|
+
this.paths = [];
|
|
307
|
+
for (let i = 0; i < svgPaths.length; i++) {
|
|
308
|
+
const path = svgPaths.item(i);
|
|
309
|
+
if (path) {
|
|
310
|
+
this.paths.push({
|
|
311
|
+
element: path,
|
|
312
|
+
length: path.getTotalLength(),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
const scale = this._scale;
|
|
317
|
+
this.dimension.width = parseFloat(svg.getAttribute("width") ?? "0") * scale;
|
|
318
|
+
this.dimension.height = parseFloat(svg.getAttribute("height") ?? "0") * scale;
|
|
319
|
+
const position = options.position ?? {
|
|
320
|
+
x: 50,
|
|
321
|
+
y: 50,
|
|
322
|
+
}, canvasSize = container.canvas.size;
|
|
323
|
+
this.offset = {
|
|
324
|
+
x: (canvasSize.width * position.x) / percentDenominator - this.dimension.width * half,
|
|
325
|
+
y: (canvasSize.height * position.y) / percentDenominator - this.dimension.height * half,
|
|
326
|
+
};
|
|
327
|
+
return parsePaths(this.paths, scale, this.offset);
|
|
328
|
+
};
|
|
329
|
+
_polygonBounce = (particle, _delta, direction) => {
|
|
330
|
+
const options = this._container.actualOptions.polygon;
|
|
331
|
+
if (!this.raw || !options?.enable || direction !== OutModeDirection.top) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
if (options.type === PolygonMaskType.inside || options.type === PolygonMaskType.outside) {
|
|
335
|
+
let closest, dx, dy;
|
|
336
|
+
const pos = particle.getPosition(), radius = particle.getRadius(), offset = 1;
|
|
337
|
+
for (let i = 0, j = this.raw.length - offset; i < this.raw.length; j = i++) {
|
|
338
|
+
const pi = this.raw[i], pj = this.raw[j];
|
|
339
|
+
if (!pi || !pj) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
closest = calcClosestPointOnSegment(pi, pj, pos);
|
|
343
|
+
const dist = getDistances(pos, closest);
|
|
344
|
+
[dx, dy] = [dist.dx, dist.dy];
|
|
345
|
+
if (dist.distance < radius) {
|
|
346
|
+
segmentBounce(pi, pj, particle.velocity);
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
if (closest && dx !== undefined && dy !== undefined && !this._checkInsidePolygon(pos)) {
|
|
351
|
+
const factor = { x: 1, y: 1 }, diameter = radius * double, inverse = -1;
|
|
352
|
+
if (pos.x >= closest.x) {
|
|
353
|
+
factor.x = -1;
|
|
354
|
+
}
|
|
355
|
+
if (pos.y >= closest.y) {
|
|
356
|
+
factor.y = -1;
|
|
357
|
+
}
|
|
358
|
+
particle.position.x = closest.x + diameter * factor.x;
|
|
359
|
+
particle.position.y = closest.y + diameter * factor.y;
|
|
360
|
+
particle.velocity.mult(inverse);
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
else if (options.type === PolygonMaskType.inline) {
|
|
365
|
+
const dist = getDistance(particle.initialPosition, particle.getPosition()), { velocity } = particle;
|
|
366
|
+
if (dist > this._moveRadius) {
|
|
367
|
+
velocity.x = velocity.y * half - velocity.x;
|
|
368
|
+
velocity.y = velocity.x * half - velocity.y;
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
};
|
|
374
|
+
_randomPoint = () => {
|
|
375
|
+
const container = this._container, options = container.actualOptions.polygon;
|
|
376
|
+
if (!options) {
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
let position;
|
|
380
|
+
if (options.type === PolygonMaskType.inline) {
|
|
381
|
+
switch (options.inline.arrangement) {
|
|
382
|
+
case PolygonMaskInlineArrangement.randomPoint:
|
|
383
|
+
position = this._getRandomPoint();
|
|
384
|
+
break;
|
|
385
|
+
case PolygonMaskInlineArrangement.randomLength:
|
|
386
|
+
position = this._getRandomPointByLength();
|
|
387
|
+
break;
|
|
388
|
+
case PolygonMaskInlineArrangement.equidistant:
|
|
389
|
+
position = this._getEquidistantPointByIndex(container.particles.count);
|
|
390
|
+
break;
|
|
391
|
+
case PolygonMaskInlineArrangement.onePerPoint:
|
|
392
|
+
case PolygonMaskInlineArrangement.perPoint:
|
|
393
|
+
default:
|
|
394
|
+
position = this._getPointByIndex(container.particles.count);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
const canvasSize = container.canvas.size;
|
|
399
|
+
position = {
|
|
400
|
+
x: getRandom() * canvasSize.width,
|
|
401
|
+
y: getRandom() * canvasSize.height,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
if (this._checkInsidePolygon(position)) {
|
|
405
|
+
return position;
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
return this._randomPoint();
|
|
409
|
+
}
|
|
410
|
+
};
|
|
406
411
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { PolygonMask } from "./Options/Classes/PolygonMask.js";
|
|
2
|
-
import { PolygonMaskInstance } from "./PolygonMaskInstance.js";
|
|
3
2
|
import { PolygonMaskType } from "./Enums/PolygonMaskType.js";
|
|
4
3
|
export class PolygonMaskPlugin {
|
|
4
|
+
id = "polygon-mask";
|
|
5
|
+
_engine;
|
|
5
6
|
constructor(engine) {
|
|
6
|
-
this.id = "polygonMask";
|
|
7
7
|
this._engine = engine;
|
|
8
8
|
}
|
|
9
|
-
getPlugin(container) {
|
|
10
|
-
|
|
9
|
+
async getPlugin(container) {
|
|
10
|
+
const { PolygonMaskInstance } = await import("./PolygonMaskInstance.js");
|
|
11
|
+
return new PolygonMaskInstance(container, this._engine);
|
|
11
12
|
}
|
|
12
|
-
loadOptions(options, source) {
|
|
13
|
+
loadOptions(_container, options, source) {
|
|
13
14
|
if (!this.needsPlugin(options) && !this.needsPlugin(source)) {
|
|
14
15
|
return;
|
|
15
16
|
}
|
package/browser/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export function loadPolygonMaskPlugin(engine) {
|
|
2
|
-
engine.checkVersion("4.0.0-alpha.
|
|
3
|
-
engine.register(async (e) => {
|
|
1
|
+
export async function loadPolygonMaskPlugin(engine) {
|
|
2
|
+
engine.checkVersion("4.0.0-alpha.20");
|
|
3
|
+
await engine.register(async (e) => {
|
|
4
4
|
const { PolygonMaskPlugin } = await import("./PolygonMaskPlugin.js");
|
|
5
5
|
e.addPlugin(new PolygonMaskPlugin(engine));
|
|
6
6
|
});
|