1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.Particle = void 0;
|
4 | const Updater_1 = require("./Particle/Updater");
|
5 | const ParticlesOptions_1 = require("../Options/Classes/Particles/ParticlesOptions");
|
6 | const Shape_1 = require("../Options/Classes/Particles/Shape/Shape");
|
7 | const Enums_1 = require("../Enums");
|
8 | const Utils_1 = require("../Utils");
|
9 | const Infecter_1 = require("./Particle/Infecter");
|
10 | const Mover_1 = require("./Particle/Mover");
|
11 | const Vector_1 = require("./Particle/Vector");
|
12 | class Particle {
|
13 | constructor(id, container, position, overrideOptions) {
|
14 | var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
15 | this.id = id;
|
16 | this.container = container;
|
17 | this.links = [];
|
18 | this.fill = true;
|
19 | this.close = true;
|
20 | this.lastPathTime = 0;
|
21 | this.destroyed = false;
|
22 | this.unbreakable = false;
|
23 | this.splitCount = 0;
|
24 | this.misplaced = false;
|
25 | this.loops = {
|
26 | opacity: 0,
|
27 | size: 0,
|
28 | };
|
29 | const pxRatio = container.retina.pixelRatio;
|
30 | const options = container.actualOptions;
|
31 | const particlesOptions = new ParticlesOptions_1.ParticlesOptions();
|
32 | particlesOptions.load(options.particles);
|
33 | const shapeType = particlesOptions.shape.type;
|
34 | const reduceDuplicates = particlesOptions.reduceDuplicates;
|
35 | this.shape = shapeType instanceof Array ? Utils_1.Utils.itemFromArray(shapeType, this.id, reduceDuplicates) : shapeType;
|
36 | if (overrideOptions === null || overrideOptions === void 0 ? void 0 : overrideOptions.shape) {
|
37 | if (overrideOptions.shape.type) {
|
38 | const overrideShapeType = overrideOptions.shape.type;
|
39 | this.shape =
|
40 | overrideShapeType instanceof Array
|
41 | ? Utils_1.Utils.itemFromArray(overrideShapeType, this.id, reduceDuplicates)
|
42 | : overrideShapeType;
|
43 | }
|
44 | const shapeOptions = new Shape_1.Shape();
|
45 | shapeOptions.load(overrideOptions.shape);
|
46 | if (this.shape) {
|
47 | const shapeData = shapeOptions.options[this.shape];
|
48 | if (shapeData) {
|
49 | this.shapeData = Utils_1.Utils.deepExtend({}, shapeData instanceof Array
|
50 | ? Utils_1.Utils.itemFromArray(shapeData, this.id, reduceDuplicates)
|
51 | : shapeData);
|
52 | }
|
53 | }
|
54 | }
|
55 | else {
|
56 | const shapeData = particlesOptions.shape.options[this.shape];
|
57 | if (shapeData) {
|
58 | this.shapeData = Utils_1.Utils.deepExtend({}, shapeData instanceof Array ? Utils_1.Utils.itemFromArray(shapeData, this.id, reduceDuplicates) : shapeData);
|
59 | }
|
60 | }
|
61 | if (overrideOptions !== undefined) {
|
62 | particlesOptions.load(overrideOptions);
|
63 | }
|
64 | if (((_a = this.shapeData) === null || _a === void 0 ? void 0 : _a.particles) !== undefined) {
|
65 | particlesOptions.load((_b = this.shapeData) === null || _b === void 0 ? void 0 : _b.particles);
|
66 | }
|
67 | this.fill = (_d = (_c = this.shapeData) === null || _c === void 0 ? void 0 : _c.fill) !== null && _d !== void 0 ? _d : this.fill;
|
68 | this.close = (_f = (_e = this.shapeData) === null || _e === void 0 ? void 0 : _e.close) !== null && _f !== void 0 ? _f : this.close;
|
69 | this.options = particlesOptions;
|
70 | this.pathDelay = Utils_1.NumberUtils.getValue(this.options.move.path.delay) * 1000;
|
71 | container.retina.initParticle(this);
|
72 | const color = this.options.color;
|
73 | const sizeOptions = this.options.size;
|
74 | const sizeValue = Utils_1.NumberUtils.getValue(sizeOptions) * container.retina.pixelRatio;
|
75 | const randomSize = typeof sizeOptions.random === "boolean" ? sizeOptions.random : sizeOptions.random.enable;
|
76 | this.size = {
|
77 | value: sizeValue,
|
78 | };
|
79 | this.direction = this.options.move.direction;
|
80 | this.bubble = {
|
81 | inRange: false,
|
82 | };
|
83 | this.initialVelocity = this.calculateVelocity();
|
84 | this.velocity = this.initialVelocity.copy();
|
85 | const rotateOptions = this.options.rotate;
|
86 | this.rotate = {
|
87 | value: (Utils_1.NumberUtils.getRangeValue(rotateOptions.value) * Math.PI) / 180,
|
88 | };
|
89 | let rotateDirection = rotateOptions.direction;
|
90 | if (rotateDirection === Enums_1.RotateDirection.random) {
|
91 | const index = Math.floor(Math.random() * 2);
|
92 | rotateDirection = index > 0 ? Enums_1.RotateDirection.counterClockwise : Enums_1.RotateDirection.clockwise;
|
93 | }
|
94 | switch (rotateDirection) {
|
95 | case Enums_1.RotateDirection.counterClockwise:
|
96 | case "counterClockwise":
|
97 | this.rotate.status = Enums_1.AnimationStatus.decreasing;
|
98 | break;
|
99 | case Enums_1.RotateDirection.clockwise:
|
100 | this.rotate.status = Enums_1.AnimationStatus.increasing;
|
101 | break;
|
102 | }
|
103 | const rotateAnimation = this.options.rotate.animation;
|
104 | if (rotateAnimation.enable) {
|
105 | this.rotate.velocity = (rotateAnimation.speed / 360) * container.retina.reduceFactor;
|
106 | if (!rotateAnimation.sync) {
|
107 | this.rotate.velocity *= Math.random();
|
108 | }
|
109 | }
|
110 | const sizeAnimation = this.options.size.animation;
|
111 | if (sizeAnimation.enable) {
|
112 | this.size.status = Enums_1.AnimationStatus.increasing;
|
113 | if (!randomSize) {
|
114 | switch (sizeAnimation.startValue) {
|
115 | case Enums_1.StartValueType.min:
|
116 | this.size.value = sizeAnimation.minimumValue * pxRatio;
|
117 | break;
|
118 | case Enums_1.StartValueType.random:
|
119 | this.size.value = Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(sizeAnimation.minimumValue * pxRatio, this.size.value));
|
120 | break;
|
121 | case Enums_1.StartValueType.max:
|
122 | default:
|
123 | this.size.status = Enums_1.AnimationStatus.decreasing;
|
124 | break;
|
125 | }
|
126 | }
|
127 | this.size.velocity =
|
128 | (((_g = this.sizeAnimationSpeed) !== null && _g !== void 0 ? _g : container.retina.sizeAnimationSpeed) / 100) *
|
129 | container.retina.reduceFactor;
|
130 | if (!sizeAnimation.sync) {
|
131 | this.size.velocity *= Math.random();
|
132 | }
|
133 | }
|
134 | const hslColor = Utils_1.ColorUtils.colorToHsl(color, this.id, reduceDuplicates);
|
135 | if (hslColor) {
|
136 | this.color = {
|
137 | h: {
|
138 | value: hslColor.h,
|
139 | },
|
140 | s: {
|
141 | value: hslColor.s,
|
142 | },
|
143 | l: {
|
144 | value: hslColor.l,
|
145 | },
|
146 | };
|
147 | const colorAnimation = this.options.color.animation;
|
148 | this.setColorAnimation(colorAnimation.h, this.color.h);
|
149 | this.setColorAnimation(colorAnimation.s, this.color.s);
|
150 | this.setColorAnimation(colorAnimation.l, this.color.l);
|
151 | }
|
152 | this.position = this.calcPosition(this.container, position);
|
153 | this.initialPosition = this.position.copy();
|
154 | this.offset = Vector_1.Vector.create(0, 0);
|
155 | const opacityOptions = this.options.opacity;
|
156 | const randomOpacity = typeof opacityOptions.random === "boolean" ? opacityOptions.random : opacityOptions.random.enable;
|
157 | this.opacity = {
|
158 | value: Utils_1.NumberUtils.getValue(opacityOptions),
|
159 | };
|
160 | const opacityAnimation = opacityOptions.animation;
|
161 | if (opacityAnimation.enable) {
|
162 | this.opacity.status = Enums_1.AnimationStatus.increasing;
|
163 | if (!randomOpacity) {
|
164 | switch (opacityAnimation.startValue) {
|
165 | case Enums_1.StartValueType.min:
|
166 | this.opacity.value = opacityAnimation.minimumValue;
|
167 | break;
|
168 | case Enums_1.StartValueType.random:
|
169 | this.opacity.value = Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(opacityAnimation.minimumValue, this.opacity.value));
|
170 | break;
|
171 | case Enums_1.StartValueType.max:
|
172 | default:
|
173 | this.opacity.status = Enums_1.AnimationStatus.decreasing;
|
174 | break;
|
175 | }
|
176 | }
|
177 | this.opacity.velocity = (opacityAnimation.speed / 100) * container.retina.reduceFactor;
|
178 | if (!opacityAnimation.sync) {
|
179 | this.opacity.velocity *= Math.random();
|
180 | }
|
181 | }
|
182 | this.sides = 24;
|
183 | let drawer = container.drawers.get(this.shape);
|
184 | if (!drawer) {
|
185 | drawer = Utils_1.Plugins.getShapeDrawer(this.shape);
|
186 | if (drawer) {
|
187 | container.drawers.set(this.shape, drawer);
|
188 | }
|
189 | }
|
190 | const sideCountFunc = drawer === null || drawer === void 0 ? void 0 : drawer.getSidesCount;
|
191 | if (sideCountFunc) {
|
192 | this.sides = sideCountFunc(this);
|
193 | }
|
194 | const imageShape = this.loadImageShape(container, drawer);
|
195 | if (imageShape) {
|
196 | this.image = imageShape.image;
|
197 | this.fill = imageShape.fill;
|
198 | this.close = imageShape.close;
|
199 | }
|
200 | this.stroke =
|
201 | this.options.stroke instanceof Array
|
202 | ? Utils_1.Utils.itemFromArray(this.options.stroke, this.id, reduceDuplicates)
|
203 | : this.options.stroke;
|
204 | this.strokeWidth = this.stroke.width * container.retina.pixelRatio;
|
205 | const strokeHslColor = (_h = Utils_1.ColorUtils.colorToHsl(this.stroke.color)) !== null && _h !== void 0 ? _h : this.getFillColor();
|
206 | if (strokeHslColor) {
|
207 | this.strokeColor = {
|
208 | h: {
|
209 | value: strokeHslColor.h,
|
210 | },
|
211 | s: {
|
212 | value: strokeHslColor.s,
|
213 | },
|
214 | l: {
|
215 | value: strokeHslColor.l,
|
216 | },
|
217 | };
|
218 | const strokeColorAnimation = (_j = this.stroke.color) === null || _j === void 0 ? void 0 : _j.animation;
|
219 | if (strokeColorAnimation && this.strokeColor) {
|
220 | this.setColorAnimation(strokeColorAnimation.h, this.strokeColor.h);
|
221 | this.setColorAnimation(strokeColorAnimation.s, this.strokeColor.s);
|
222 | this.setColorAnimation(strokeColorAnimation.l, this.strokeColor.l);
|
223 | }
|
224 | }
|
225 | const lifeOptions = particlesOptions.life;
|
226 | this.lifeDelay = container.retina.reduceFactor
|
227 | ? ((Utils_1.NumberUtils.getValue(lifeOptions.delay) * (lifeOptions.delay.sync ? 1 : Math.random())) /
|
228 | container.retina.reduceFactor) *
|
229 | 1000
|
230 | : 0;
|
231 | this.lifeDelayTime = 0;
|
232 | this.lifeDuration = container.retina.reduceFactor
|
233 | ? ((Utils_1.NumberUtils.getValue(lifeOptions.duration) * (lifeOptions.duration.sync ? 1 : Math.random())) /
|
234 | container.retina.reduceFactor) *
|
235 | 1000
|
236 | : 0;
|
237 | this.lifeTime = 0;
|
238 | this.livesRemaining = particlesOptions.life.count;
|
239 | this.spawning = this.lifeDelay > 0;
|
240 | if (this.lifeDuration <= 0) {
|
241 | this.lifeDuration = -1;
|
242 | }
|
243 | if (this.livesRemaining <= 0) {
|
244 | this.livesRemaining = -1;
|
245 | }
|
246 | this.shadowColor = Utils_1.ColorUtils.colorToRgb(this.options.shadow.color);
|
247 | this.updater = new Updater_1.Updater(container, this);
|
248 | this.infecter = new Infecter_1.Infecter(container);
|
249 | this.mover = new Mover_1.Mover(container, this);
|
250 | if (drawer && drawer.particleInit) {
|
251 | drawer.particleInit(container, this);
|
252 | }
|
253 | }
|
254 | move(delta) {
|
255 | this.mover.move(delta);
|
256 | }
|
257 | update(delta) {
|
258 | this.updater.update(delta);
|
259 | }
|
260 | draw(delta) {
|
261 | this.container.canvas.drawParticle(this, delta);
|
262 | }
|
263 | getPosition() {
|
264 | return this.position.add(this.offset);
|
265 | }
|
266 | getRadius() {
|
267 | return this.bubble.radius || this.size.value;
|
268 | }
|
269 | getMass() {
|
270 | const radius = this.getRadius();
|
271 | return (Math.pow(radius, 2) * Math.PI) / 2;
|
272 | }
|
273 | getFillColor() {
|
274 | var _a;
|
275 | return (_a = this.bubble.color) !== null && _a !== void 0 ? _a : Utils_1.ColorUtils.getHslFromAnimation(this.color);
|
276 | }
|
277 | getStrokeColor() {
|
278 | var _a, _b;
|
279 | return (_b = (_a = this.bubble.color) !== null && _a !== void 0 ? _a : Utils_1.ColorUtils.getHslFromAnimation(this.strokeColor)) !== null && _b !== void 0 ? _b : this.getFillColor();
|
280 | }
|
281 | destroy(override) {
|
282 | this.destroyed = true;
|
283 | this.bubble.inRange = false;
|
284 | this.links = [];
|
285 | if (this.unbreakable) {
|
286 | return;
|
287 | }
|
288 | this.destroyed = true;
|
289 | this.bubble.inRange = false;
|
290 | for (const [, plugin] of this.container.plugins) {
|
291 | if (plugin.particleDestroyed) {
|
292 | plugin.particleDestroyed(this, override);
|
293 | }
|
294 | }
|
295 | if (override) {
|
296 | return;
|
297 | }
|
298 | const destroyOptions = this.options.destroy;
|
299 | if (destroyOptions.mode === Enums_1.DestroyMode.split) {
|
300 | this.split();
|
301 | }
|
302 | }
|
303 | reset() {
|
304 | this.loops.opacity = 0;
|
305 | this.loops.size = 0;
|
306 | }
|
307 | split() {
|
308 | const splitOptions = this.options.destroy.split;
|
309 | if (splitOptions.count >= 0 && this.splitCount++ > splitOptions.count) {
|
310 | return;
|
311 | }
|
312 | const rate = Utils_1.NumberUtils.getRangeValue(splitOptions.rate.value);
|
313 | for (let i = 0; i < rate; i++) {
|
314 | this.container.particles.addSplitParticle(this);
|
315 | }
|
316 | }
|
317 | setColorAnimation(colorAnimation, colorValue) {
|
318 | if (colorAnimation.enable) {
|
319 | colorValue.velocity = (colorAnimation.speed / 100) * this.container.retina.reduceFactor;
|
320 | if (colorAnimation.sync) {
|
321 | return;
|
322 | }
|
323 | colorValue.status = Enums_1.AnimationStatus.increasing;
|
324 | colorValue.velocity *= Math.random();
|
325 | if (colorValue.value) {
|
326 | colorValue.value *= Math.random();
|
327 | }
|
328 | }
|
329 | else {
|
330 | colorValue.velocity = 0;
|
331 | }
|
332 | }
|
333 | calcPosition(container, position, tryCount = 0) {
|
334 | var _a, _b;
|
335 | for (const [, plugin] of container.plugins) {
|
336 | const pluginPos = plugin.particlePosition !== undefined ? plugin.particlePosition(position, this) : undefined;
|
337 | if (pluginPos !== undefined) {
|
338 | return Vector_1.Vector.create(pluginPos.x, pluginPos.y);
|
339 | }
|
340 | }
|
341 | const pos = Vector_1.Vector.create((_a = position === null || position === void 0 ? void 0 : position.x) !== null && _a !== void 0 ? _a : Math.random() * container.canvas.size.width, (_b = position === null || position === void 0 ? void 0 : position.y) !== null && _b !== void 0 ? _b : Math.random() * container.canvas.size.height);
|
342 | const outMode = this.options.move.outMode;
|
343 | if (Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounce) || Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounceHorizontal)) {
|
344 | if (pos.x > container.canvas.size.width - this.size.value * 2) {
|
345 | pos.x -= this.size.value;
|
346 | }
|
347 | else if (pos.x < this.size.value * 2) {
|
348 | pos.x += this.size.value;
|
349 | }
|
350 | }
|
351 | if (Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounce) || Utils_1.Utils.isInArray(outMode, Enums_1.OutMode.bounceVertical)) {
|
352 | if (pos.y > container.canvas.size.height - this.size.value * 2) {
|
353 | pos.y -= this.size.value;
|
354 | }
|
355 | else if (pos.y < this.size.value * 2) {
|
356 | pos.y += this.size.value;
|
357 | }
|
358 | }
|
359 | if (this.checkOverlap(pos, tryCount)) {
|
360 | return this.calcPosition(container, undefined, tryCount + 1);
|
361 | }
|
362 | return pos;
|
363 | }
|
364 | checkOverlap(pos, tryCount = 0) {
|
365 | const overlapOptions = this.options.collisions.overlap;
|
366 | if (!overlapOptions.enable) {
|
367 | const retries = overlapOptions.retries;
|
368 | if (retries >= 0 && tryCount > retries) {
|
369 | throw new Error("Particle is overlapping and can't be placed");
|
370 | }
|
371 | let overlaps = false;
|
372 | for (const particle of this.container.particles.array) {
|
373 | if (Utils_1.NumberUtils.getDistance(pos, particle.position) < this.size.value + particle.size.value) {
|
374 | overlaps = true;
|
375 | break;
|
376 | }
|
377 | }
|
378 | return overlaps;
|
379 | }
|
380 | return false;
|
381 | }
|
382 | calculateVelocity() {
|
383 | const baseVelocity = Utils_1.NumberUtils.getParticleBaseVelocity(this.direction);
|
384 | const res = baseVelocity.copy();
|
385 | const moveOptions = this.options.move;
|
386 | let rad;
|
387 | let radOffset = Math.PI / 4;
|
388 | if (typeof moveOptions.angle === "number") {
|
389 | rad = (Math.PI / 180) * moveOptions.angle;
|
390 | }
|
391 | else {
|
392 | rad = (Math.PI / 180) * moveOptions.angle.value;
|
393 | radOffset = (Math.PI / 180) * moveOptions.angle.offset;
|
394 | }
|
395 | const range = {
|
396 | left: Math.sin(radOffset + rad / 2) - Math.sin(radOffset - rad / 2),
|
397 | right: Math.cos(radOffset + rad / 2) - Math.cos(radOffset - rad / 2),
|
398 | };
|
399 | if (!moveOptions.straight || moveOptions.random) {
|
400 | res.x += Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(range.left, range.right)) / 2;
|
401 | res.y += Utils_1.NumberUtils.randomInRange(Utils_1.NumberUtils.setRangeValue(range.left, range.right)) / 2;
|
402 | }
|
403 | return res;
|
404 | }
|
405 | loadImageShape(container, drawer) {
|
406 | var _a, _b, _c, _d, _e;
|
407 | if (!(this.shape === Enums_1.ShapeType.image || this.shape === Enums_1.ShapeType.images)) {
|
408 | return;
|
409 | }
|
410 | const imageDrawer = drawer;
|
411 | const images = imageDrawer.getImages(container).images;
|
412 | const imageData = this.shapeData;
|
413 | const image = (_a = images.find((t) => t.source === imageData.src)) !== null && _a !== void 0 ? _a : images[0];
|
414 | const color = this.getFillColor();
|
415 | let imageRes;
|
416 | if (!image) {
|
417 | return;
|
418 | }
|
419 | if (image.svgData !== undefined && imageData.replaceColor && color) {
|
420 | const svgColoredData = Utils_1.ColorUtils.replaceColorSvg(image, color, this.opacity.value);
|
421 | const svg = new Blob([svgColoredData], { type: "image/svg+xml" });
|
422 | const domUrl = URL || window.URL || window.webkitURL || window;
|
423 | const url = domUrl.createObjectURL(svg);
|
424 | const img = new Image();
|
425 | imageRes = {
|
426 | data: image,
|
427 | loaded: false,
|
428 | ratio: imageData.width / imageData.height,
|
429 | replaceColor: (_b = imageData.replaceColor) !== null && _b !== void 0 ? _b : imageData.replace_color,
|
430 | source: imageData.src,
|
431 | };
|
432 | img.addEventListener("load", () => {
|
433 | if (this.image) {
|
434 | this.image.loaded = true;
|
435 | image.element = img;
|
436 | }
|
437 | domUrl.revokeObjectURL(url);
|
438 | });
|
439 | img.addEventListener("error", () => {
|
440 | domUrl.revokeObjectURL(url);
|
441 | Utils_1.Utils.loadImage(imageData.src).then((img2) => {
|
442 | if (this.image && img2) {
|
443 | image.element = img2.element;
|
444 | this.image.loaded = true;
|
445 | }
|
446 | });
|
447 | });
|
448 | img.src = url;
|
449 | }
|
450 | else {
|
451 | imageRes = {
|
452 | data: image,
|
453 | loaded: true,
|
454 | ratio: imageData.width / imageData.height,
|
455 | replaceColor: (_c = imageData.replaceColor) !== null && _c !== void 0 ? _c : imageData.replace_color,
|
456 | source: imageData.src,
|
457 | };
|
458 | }
|
459 | if (!imageRes.ratio) {
|
460 | imageRes.ratio = 1;
|
461 | }
|
462 | const fill = (_d = imageData.fill) !== null && _d !== void 0 ? _d : this.fill;
|
463 | const close = (_e = imageData.close) !== null && _e !== void 0 ? _e : this.close;
|
464 | return {
|
465 | image: imageRes,
|
466 | fill,
|
467 | close,
|
468 | };
|
469 | }
|
470 | }
|
471 | exports.Particle = Particle;
|