1 | import { ParticlesOptions } from "../Options/Classes/Particles/ParticlesOptions";
|
2 | import { Shape } from "../Options/Classes/Particles/Shape/Shape";
|
3 | import { AnimationStatus, DestroyMode, OutMode, RotateDirection, StartValueType, } from "../Enums";
|
4 | import { alterHsl, clamp, colorToRgb, deepExtend, getDistance, getHslFromAnimation, getParticleBaseVelocity, getParticleDirectionAngle, getRangeMax, getRangeMin, getRangeValue, getValue, isInArray, itemFromArray, Plugins, randomInRange, setRangeValue, } from "../Utils";
|
5 | import { Vector } from "./Particle/Vector";
|
6 | import { Vector3d } from "./Particle/Vector3d";
|
7 | const fixOutMode = (data) => {
|
8 | if (isInArray(data.outMode, data.checkModes) || isInArray(data.outMode, data.checkModes)) {
|
9 | if (data.coord > data.maxCoord - data.radius * 2) {
|
10 | data.setCb(-data.radius);
|
11 | }
|
12 | else if (data.coord < data.radius * 2) {
|
13 | data.setCb(data.radius);
|
14 | }
|
15 | }
|
16 | };
|
17 |
|
18 |
|
19 |
|
20 |
|
21 | export class Particle {
|
22 | constructor(id, container, position, overrideOptions, group) {
|
23 | var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
24 | this.id = id;
|
25 | this.container = container;
|
26 | this.group = group;
|
27 | this.fill = true;
|
28 | this.close = true;
|
29 | this.lastPathTime = 0;
|
30 | this.destroyed = false;
|
31 | this.unbreakable = false;
|
32 | this.splitCount = 0;
|
33 | this.misplaced = false;
|
34 | this.retina = {
|
35 | maxDistance: {},
|
36 | };
|
37 | const pxRatio = container.retina.pixelRatio;
|
38 | const mainOptions = container.actualOptions;
|
39 | const particlesOptions = new ParticlesOptions();
|
40 | particlesOptions.load(mainOptions.particles);
|
41 | const shapeType = particlesOptions.shape.type;
|
42 | const reduceDuplicates = particlesOptions.reduceDuplicates;
|
43 | this.shape = shapeType instanceof Array ? itemFromArray(shapeType, this.id, reduceDuplicates) : shapeType;
|
44 | if (overrideOptions === null || overrideOptions === void 0 ? void 0 : overrideOptions.shape) {
|
45 | if (overrideOptions.shape.type) {
|
46 | const overrideShapeType = overrideOptions.shape.type;
|
47 | this.shape =
|
48 | overrideShapeType instanceof Array
|
49 | ? itemFromArray(overrideShapeType, this.id, reduceDuplicates)
|
50 | : overrideShapeType;
|
51 | }
|
52 | const shapeOptions = new Shape();
|
53 | shapeOptions.load(overrideOptions.shape);
|
54 | if (this.shape) {
|
55 | this.shapeData = this.loadShapeData(shapeOptions, reduceDuplicates);
|
56 | }
|
57 | }
|
58 | else {
|
59 | this.shapeData = this.loadShapeData(particlesOptions.shape, reduceDuplicates);
|
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 = getValue(this.options.move.path.delay) * 1000;
|
71 | const zIndexValue = getRangeValue(this.options.zIndex.value);
|
72 | container.retina.initParticle(this);
|
73 |
|
74 | const sizeOptions = this.options.size, sizeRange = sizeOptions.value;
|
75 | this.size = {
|
76 | enable: sizeOptions.animation.enable,
|
77 | value: getValue(sizeOptions) * container.retina.pixelRatio,
|
78 | max: getRangeMax(sizeRange) * pxRatio,
|
79 | min: getRangeMin(sizeRange) * pxRatio,
|
80 | loops: 0,
|
81 | maxLoops: sizeOptions.animation.count,
|
82 | };
|
83 | const sizeAnimation = sizeOptions.animation;
|
84 | if (sizeAnimation.enable) {
|
85 | this.size.status = AnimationStatus.increasing;
|
86 | switch (sizeAnimation.startValue) {
|
87 | case StartValueType.min:
|
88 | this.size.value = this.size.min;
|
89 | this.size.status = AnimationStatus.increasing;
|
90 | break;
|
91 | case StartValueType.random:
|
92 | this.size.value = randomInRange(this.size) * pxRatio;
|
93 | this.size.status = Math.random() >= 0.5 ? AnimationStatus.increasing : AnimationStatus.decreasing;
|
94 | break;
|
95 | case StartValueType.max:
|
96 | default:
|
97 | this.size.value = this.size.max;
|
98 | this.size.status = AnimationStatus.decreasing;
|
99 | break;
|
100 | }
|
101 | this.size.velocity =
|
102 | (((_g = this.retina.sizeAnimationSpeed) !== null && _g !== void 0 ? _g : container.retina.sizeAnimationSpeed) / 100) *
|
103 | container.retina.reduceFactor;
|
104 | if (!sizeAnimation.sync) {
|
105 | this.size.velocity *= Math.random();
|
106 | }
|
107 | }
|
108 | this.direction = getParticleDirectionAngle(this.options.move.direction);
|
109 | this.bubble = {
|
110 | inRange: false,
|
111 | };
|
112 |
|
113 | this.initialVelocity = this.calculateVelocity();
|
114 | this.velocity = this.initialVelocity.copy();
|
115 | this.moveDecay = 1 - getRangeValue(this.options.move.decay);
|
116 |
|
117 | this.position = this.calcPosition(container, position, clamp(zIndexValue, 0, container.zLayers));
|
118 | this.initialPosition = this.position.copy();
|
119 |
|
120 | this.offset = Vector.origin;
|
121 | const particles = container.particles;
|
122 | particles.needsSort = particles.needsSort || particles.lastZIndex < this.position.z;
|
123 | particles.lastZIndex = this.position.z;
|
124 |
|
125 | this.zIndexFactor = this.position.z / container.zLayers;
|
126 | this.sides = 24;
|
127 | let drawer = container.drawers.get(this.shape);
|
128 | if (!drawer) {
|
129 | drawer = Plugins.getShapeDrawer(this.shape);
|
130 | if (drawer) {
|
131 | container.drawers.set(this.shape, drawer);
|
132 | }
|
133 | }
|
134 | if (drawer === null || drawer === void 0 ? void 0 : drawer.loadShape) {
|
135 | drawer === null || drawer === void 0 ? void 0 : drawer.loadShape(this);
|
136 | }
|
137 | const sideCountFunc = drawer === null || drawer === void 0 ? void 0 : drawer.getSidesCount;
|
138 | if (sideCountFunc) {
|
139 | this.sides = sideCountFunc(this);
|
140 | }
|
141 | this.life = this.loadLife();
|
142 | this.spawning = this.life.delay > 0;
|
143 | if (this.options.move.spin.enable) {
|
144 | const spinPos = (_h = this.options.move.spin.position) !== null && _h !== void 0 ? _h : { x: 50, y: 50 };
|
145 | const spinCenter = {
|
146 | x: (spinPos.x / 100) * container.canvas.size.width,
|
147 | y: (spinPos.y / 100) * container.canvas.size.height,
|
148 | };
|
149 | const pos = this.getPosition();
|
150 | const distance = getDistance(pos, spinCenter);
|
151 | this.spin = {
|
152 | center: spinCenter,
|
153 | direction: this.velocity.x >= 0 ? RotateDirection.clockwise : RotateDirection.counterClockwise,
|
154 | angle: this.velocity.angle,
|
155 | radius: distance,
|
156 | acceleration: (_j = this.retina.spinAcceleration) !== null && _j !== void 0 ? _j : getRangeValue(this.options.move.spin.acceleration),
|
157 | };
|
158 | }
|
159 | this.shadowColor = colorToRgb(this.options.shadow.color);
|
160 | for (const updater of container.particles.updaters) {
|
161 | if (updater.init) {
|
162 | updater.init(this);
|
163 | }
|
164 | }
|
165 | if (drawer && drawer.particleInit) {
|
166 | drawer.particleInit(container, this);
|
167 | }
|
168 | for (const [, plugin] of container.plugins) {
|
169 | if (plugin.particleCreated) {
|
170 | plugin.particleCreated(this);
|
171 | }
|
172 | }
|
173 | }
|
174 | isVisible() {
|
175 | return !this.destroyed && !this.spawning && this.isInsideCanvas();
|
176 | }
|
177 | isInsideCanvas() {
|
178 | const radius = this.getRadius();
|
179 | const canvasSize = this.container.canvas.size;
|
180 | return (this.position.x >= -radius &&
|
181 | this.position.y >= -radius &&
|
182 | this.position.y <= canvasSize.height + radius &&
|
183 | this.position.x <= canvasSize.width + radius);
|
184 | }
|
185 | draw(delta) {
|
186 | const container = this.container;
|
187 | for (const [, plugin] of container.plugins) {
|
188 | container.canvas.drawParticlePlugin(plugin, this, delta);
|
189 | }
|
190 | container.canvas.drawParticle(this, delta);
|
191 | }
|
192 | getPosition() {
|
193 | return {
|
194 | x: this.position.x + this.offset.x,
|
195 | y: this.position.y + this.offset.y,
|
196 | z: this.position.z,
|
197 | };
|
198 | }
|
199 | getRadius() {
|
200 | var _a;
|
201 | return (_a = this.bubble.radius) !== null && _a !== void 0 ? _a : this.size.value;
|
202 | }
|
203 | getMass() {
|
204 | return (this.getRadius() ** 2 * Math.PI) / 2;
|
205 | }
|
206 | getFillColor() {
|
207 | var _a, _b, _c;
|
208 | const color = (_a = this.bubble.color) !== null && _a !== void 0 ? _a : getHslFromAnimation(this.color);
|
209 | if (color && this.roll && (this.backColor || this.roll.alter)) {
|
210 | const rolled = Math.floor(((_c = (_b = this.roll) === null || _b === void 0 ? void 0 : _b.angle) !== null && _c !== void 0 ? _c : 0) / (Math.PI / 2)) % 2;
|
211 | if (rolled) {
|
212 | if (this.backColor) {
|
213 | return this.backColor;
|
214 | }
|
215 | if (this.roll.alter) {
|
216 | return alterHsl(color, this.roll.alter.type, this.roll.alter.value);
|
217 | }
|
218 | }
|
219 | }
|
220 | return color;
|
221 | }
|
222 | getStrokeColor() {
|
223 | var _a, _b;
|
224 | return (_b = (_a = this.bubble.color) !== null && _a !== void 0 ? _a : getHslFromAnimation(this.strokeColor)) !== null && _b !== void 0 ? _b : this.getFillColor();
|
225 | }
|
226 | destroy(override) {
|
227 | this.destroyed = true;
|
228 | this.bubble.inRange = false;
|
229 | if (this.unbreakable) {
|
230 | return;
|
231 | }
|
232 | this.destroyed = true;
|
233 | this.bubble.inRange = false;
|
234 | for (const [, plugin] of this.container.plugins) {
|
235 | if (plugin.particleDestroyed) {
|
236 | plugin.particleDestroyed(this, override);
|
237 | }
|
238 | }
|
239 | if (override) {
|
240 | return;
|
241 | }
|
242 | const destroyOptions = this.options.destroy;
|
243 | if (destroyOptions.mode === DestroyMode.split) {
|
244 | this.split();
|
245 | }
|
246 | }
|
247 | |
248 |
|
249 |
|
250 | reset() {
|
251 | if (this.opacity) {
|
252 | this.opacity.loops = 0;
|
253 | }
|
254 | this.size.loops = 0;
|
255 | }
|
256 | split() {
|
257 | const splitOptions = this.options.destroy.split;
|
258 | if (splitOptions.count >= 0 && this.splitCount++ > splitOptions.count) {
|
259 | return;
|
260 | }
|
261 | const rate = getRangeValue(splitOptions.rate.value);
|
262 | for (let i = 0; i < rate; i++) {
|
263 | this.container.particles.addSplitParticle(this);
|
264 | }
|
265 | }
|
266 | calcPosition(container, position, zIndex, tryCount = 0) {
|
267 | var _a, _b, _c, _d, _e, _f;
|
268 | for (const [, plugin] of container.plugins) {
|
269 | const pluginPos = plugin.particlePosition !== undefined ? plugin.particlePosition(position, this) : undefined;
|
270 | if (pluginPos !== undefined) {
|
271 | return Vector3d.create(pluginPos.x, pluginPos.y, zIndex);
|
272 | }
|
273 | }
|
274 | const canvasSize = container.canvas.size;
|
275 | const pos = Vector3d.create((_a = position === null || position === void 0 ? void 0 : position.x) !== null && _a !== void 0 ? _a : Math.random() * canvasSize.width, (_b = position === null || position === void 0 ? void 0 : position.y) !== null && _b !== void 0 ? _b : Math.random() * canvasSize.height, zIndex);
|
276 | const radius = this.getRadius();
|
277 |
|
278 | const outModes = this.options.move.outModes, fixHorizontal = (outMode) => {
|
279 | fixOutMode({
|
280 | outMode,
|
281 | checkModes: [OutMode.bounce, OutMode.bounceHorizontal],
|
282 | coord: pos.x,
|
283 | maxCoord: container.canvas.size.width,
|
284 | setCb: (value) => (pos.x += value),
|
285 | radius,
|
286 | });
|
287 | }, fixVertical = (outMode) => {
|
288 | fixOutMode({
|
289 | outMode,
|
290 | checkModes: [OutMode.bounce, OutMode.bounceVertical],
|
291 | coord: pos.y,
|
292 | maxCoord: container.canvas.size.height,
|
293 | setCb: (value) => (pos.y += value),
|
294 | radius,
|
295 | });
|
296 | };
|
297 | fixHorizontal((_c = outModes.left) !== null && _c !== void 0 ? _c : outModes.default);
|
298 | fixHorizontal((_d = outModes.right) !== null && _d !== void 0 ? _d : outModes.default);
|
299 | fixVertical((_e = outModes.top) !== null && _e !== void 0 ? _e : outModes.default);
|
300 | fixVertical((_f = outModes.bottom) !== null && _f !== void 0 ? _f : outModes.default);
|
301 | if (this.checkOverlap(pos, tryCount)) {
|
302 | return this.calcPosition(container, undefined, zIndex, tryCount + 1);
|
303 | }
|
304 | return pos;
|
305 | }
|
306 | checkOverlap(pos, tryCount = 0) {
|
307 | const collisionsOptions = this.options.collisions;
|
308 | const radius = this.getRadius();
|
309 | if (!collisionsOptions.enable) {
|
310 | return false;
|
311 | }
|
312 | const overlapOptions = collisionsOptions.overlap;
|
313 | if (overlapOptions.enable) {
|
314 | return false;
|
315 | }
|
316 | const retries = overlapOptions.retries;
|
317 | if (retries >= 0 && tryCount > retries) {
|
318 | throw new Error("Particle is overlapping and can't be placed");
|
319 | }
|
320 | let overlaps = false;
|
321 | for (const particle of this.container.particles.array) {
|
322 | if (getDistance(pos, particle.position) < radius + particle.getRadius()) {
|
323 | overlaps = true;
|
324 | break;
|
325 | }
|
326 | }
|
327 | return overlaps;
|
328 | }
|
329 | calculateVelocity() {
|
330 | const baseVelocity = getParticleBaseVelocity(this.direction);
|
331 | const res = baseVelocity.copy();
|
332 | const moveOptions = this.options.move;
|
333 | const rad = (Math.PI / 180) * moveOptions.angle.value;
|
334 | const radOffset = (Math.PI / 180) * moveOptions.angle.offset;
|
335 | const range = {
|
336 | left: radOffset - rad / 2,
|
337 | right: radOffset + rad / 2,
|
338 | };
|
339 | if (!moveOptions.straight) {
|
340 | res.angle += randomInRange(setRangeValue(range.left, range.right));
|
341 | }
|
342 | if (moveOptions.random && typeof moveOptions.speed === "number") {
|
343 | res.length *= Math.random();
|
344 | }
|
345 | return res;
|
346 | }
|
347 | loadShapeData(shapeOptions, reduceDuplicates) {
|
348 | const shapeData = shapeOptions.options[this.shape];
|
349 | if (shapeData) {
|
350 | return deepExtend({}, shapeData instanceof Array ? itemFromArray(shapeData, this.id, reduceDuplicates) : shapeData);
|
351 | }
|
352 | }
|
353 | loadLife() {
|
354 | const container = this.container;
|
355 | const particlesOptions = this.options;
|
356 | const lifeOptions = particlesOptions.life;
|
357 | const life = {
|
358 | delay: container.retina.reduceFactor
|
359 | ? ((getRangeValue(lifeOptions.delay.value) * (lifeOptions.delay.sync ? 1 : Math.random())) /
|
360 | container.retina.reduceFactor) *
|
361 | 1000
|
362 | : 0,
|
363 | delayTime: 0,
|
364 | duration: container.retina.reduceFactor
|
365 | ? ((getRangeValue(lifeOptions.duration.value) * (lifeOptions.duration.sync ? 1 : Math.random())) /
|
366 | container.retina.reduceFactor) *
|
367 | 1000
|
368 | : 0,
|
369 | time: 0,
|
370 | count: particlesOptions.life.count,
|
371 | };
|
372 | if (life.duration <= 0) {
|
373 | life.duration = -1;
|
374 | }
|
375 | if (life.count <= 0) {
|
376 | life.count = -1;
|
377 | }
|
378 | return life;
|
379 | }
|
380 | }
|