import { calcExactPositionOrRandomFromSize, clamp, getDistance, getParticleBaseVelocity, getParticleDirectionAngle, getRandom, getRangeMax, getRangeMin, getRangeValue, getValue, randomInRange, setRangeValue, } from "../Utils/NumberUtils"; import { deepExtend, isInArray, itemFromSingleOrMultiple } from "../Utils/Utils"; import { getHslFromAnimation, rangeColorToRgb } from "../Utils/ColorUtils"; import { Interactivity } from "../Options/Classes/Interactivity/Interactivity"; import { Vector } from "./Utils/Vector"; import { Vector3d } from "./Utils/Vector3d"; import { alterHsl } from "../Utils/CanvasUtils"; import { loadParticlesOptions } from "../Utils/OptionsUtils"; const fixOutMode = (data) => { if (!isInArray(data.outMode, data.checkModes)) { return; } if (data.coord > data.maxCoord - data.radius * 2) { data.setCb(-data.radius); } else if (data.coord < data.radius * 2) { data.setCb(data.radius); } }; export class Particle { constructor(engine, id, container, position, overrideOptions, group) { this.container = container; this._engine = engine; this.init(id, position, overrideOptions, group); } destroy(override) { var _a; if (this.unbreakable || this.destroyed) { return; } this.destroyed = true; this.bubble.inRange = false; this.slow.inRange = false; for (const [, plugin] of this.container.plugins) { if (plugin.particleDestroyed) { plugin.particleDestroyed(this, override); } } for (const updater of this.container.particles.updaters) { if (updater.particleDestroyed) { updater.particleDestroyed(this, override); } } (_a = this.pathGenerator) === null || _a === void 0 ? void 0 : _a.reset(this); } draw(delta) { const container = this.container; for (const [, plugin] of container.plugins) { container.canvas.drawParticlePlugin(plugin, this, delta); } container.canvas.drawParticle(this, delta); } getFillColor() { var _a; return this._getRollColor((_a = this.bubble.color) !== null && _a !== void 0 ? _a : getHslFromAnimation(this.color)); } getMass() { return (this.getRadius() ** 2 * Math.PI) / 2; } getPosition() { return { x: this.position.x + this.offset.x, y: this.position.y + this.offset.y, z: this.position.z, }; } getRadius() { var _a; return (_a = this.bubble.radius) !== null && _a !== void 0 ? _a : this.size.value; } getStrokeColor() { var _a; return this._getRollColor((_a = this.bubble.color) !== null && _a !== void 0 ? _a : getHslFromAnimation(this.strokeColor)); } init(id, position, overrideOptions, group) { var _a, _b, _c, _d, _e, _f, _g, _h, _j; const container = this.container, engine = this._engine; this.id = id; this.group = group; this.fill = true; this.pathRotation = false; this.close = true; this.lastPathTime = 0; this.destroyed = false; this.unbreakable = false; this.rotation = 0; this.misplaced = false; this.retina = { maxDistance: {}, }; this.outType = "normal"; this.ignoresResizeRatio = true; const pxRatio = container.retina.pixelRatio, mainOptions = container.actualOptions, particlesOptions = loadParticlesOptions(this._engine, container, mainOptions.particles), shapeType = particlesOptions.shape.type, { reduceDuplicates } = particlesOptions; this.shape = itemFromSingleOrMultiple(shapeType, this.id, reduceDuplicates); const shapeOptions = particlesOptions.shape; if (overrideOptions && overrideOptions.shape && overrideOptions.shape.type) { const overrideShapeType = overrideOptions.shape.type, shape = itemFromSingleOrMultiple(overrideShapeType, this.id, reduceDuplicates); if (shape) { this.shape = shape; shapeOptions.load(overrideOptions.shape); } } this.shapeData = this._loadShapeData(shapeOptions, reduceDuplicates); particlesOptions.load(overrideOptions); particlesOptions.load((_a = this.shapeData) === null || _a === void 0 ? void 0 : _a.particles); this.interactivity = new Interactivity(engine, container); this.interactivity.load(container.actualOptions.interactivity); this.interactivity.load(particlesOptions.interactivity); this.fill = (_c = (_b = this.shapeData) === null || _b === void 0 ? void 0 : _b.fill) !== null && _c !== void 0 ? _c : this.fill; this.close = (_e = (_d = this.shapeData) === null || _d === void 0 ? void 0 : _d.close) !== null && _e !== void 0 ? _e : this.close; this.options = particlesOptions; const pathOptions = this.options.move.path; this.pathDelay = getValue(pathOptions.delay) * 1000; if (pathOptions.generator) { this.pathGenerator = this._engine.plugins.getPathGenerator(pathOptions.generator); if (this.pathGenerator && container.addPath(pathOptions.generator, this.pathGenerator)) { this.pathGenerator.init(container); } } const zIndexValue = getRangeValue(this.options.zIndex.value); container.retina.initParticle(this); const sizeOptions = this.options.size, sizeRange = sizeOptions.value, sizeAnimation = sizeOptions.animation; this.size = { enable: sizeOptions.animation.enable, value: getRangeValue(sizeOptions.value) * container.retina.pixelRatio, max: getRangeMax(sizeRange) * pxRatio, min: getRangeMin(sizeRange) * pxRatio, loops: 0, maxLoops: getRangeValue(sizeOptions.animation.count), }; if (sizeAnimation.enable) { this.size.status = "increasing"; this.size.decay = 1 - getRangeValue(sizeAnimation.decay); switch (sizeAnimation.startValue) { case "min": this.size.value = this.size.min; this.size.status = "increasing"; break; case "random": this.size.value = randomInRange(this.size); this.size.status = getRandom() >= 0.5 ? "increasing" : "decreasing"; break; case "max": default: this.size.value = this.size.max; this.size.status = "decreasing"; break; } } this.size.initialValue = this.size.value; this.bubble = { inRange: false, }; this.slow = { inRange: false, factor: 1, }; this.position = this._calcPosition(container, position, clamp(zIndexValue, 0, container.zLayers)); this.initialPosition = this.position.copy(); const canvasSize = container.canvas.size, moveCenter = Object.assign({}, this.options.move.center), isCenterPercent = moveCenter.mode === "percent"; this.moveCenter = { x: moveCenter.x * (isCenterPercent ? canvasSize.width / 100 : 1), y: moveCenter.y * (isCenterPercent ? canvasSize.height / 100 : 1), radius: (_f = this.options.move.center.radius) !== null && _f !== void 0 ? _f : 0, mode: (_g = this.options.move.center.mode) !== null && _g !== void 0 ? _g : "percent", }; this.direction = getParticleDirectionAngle(this.options.move.direction, this.position, this.moveCenter); switch (this.options.move.direction) { case "inside": this.outType = "inside"; break; case "outside": this.outType = "outside"; break; } this.initialVelocity = this._calculateVelocity(); this.velocity = this.initialVelocity.copy(); this.moveDecay = 1 - getRangeValue(this.options.move.decay); this.offset = Vector.origin; const particles = container.particles; particles.needsSort = particles.needsSort || particles.lastZIndex < this.position.z; particles.lastZIndex = this.position.z; this.zIndexFactor = this.position.z / container.zLayers; this.sides = 24; let drawer = container.drawers.get(this.shape); if (!drawer) { drawer = this._engine.plugins.getShapeDrawer(this.shape); if (drawer) { container.drawers.set(this.shape, drawer); } } if (drawer === null || drawer === void 0 ? void 0 : drawer.loadShape) { drawer === null || drawer === void 0 ? void 0 : drawer.loadShape(this); } const sideCountFunc = drawer === null || drawer === void 0 ? void 0 : drawer.getSidesCount; if (sideCountFunc) { this.sides = sideCountFunc(this); } this.spawning = false; this.shadowColor = rangeColorToRgb(this.options.shadow.color); for (const updater of container.particles.updaters) { updater.init(this); } for (const mover of container.particles.movers) { (_h = mover.init) === null || _h === void 0 ? void 0 : _h.call(mover, this); } if (drawer === null || drawer === void 0 ? void 0 : drawer.particleInit) { drawer.particleInit(container, this); } for (const [, plugin] of container.plugins) { (_j = plugin.particleCreated) === null || _j === void 0 ? void 0 : _j.call(plugin, this); } } isInsideCanvas() { const radius = this.getRadius(), canvasSize = this.container.canvas.size; return (this.position.x >= -radius && this.position.y >= -radius && this.position.y <= canvasSize.height + radius && this.position.x <= canvasSize.width + radius); } isVisible() { return !this.destroyed && !this.spawning && this.isInsideCanvas(); } reset() { var _a; for (const updater of this.container.particles.updaters) { (_a = updater.reset) === null || _a === void 0 ? void 0 : _a.call(updater, this); } } _calcPosition(container, position, zIndex, tryCount = 0) { var _a, _b, _c, _d; for (const [, plugin] of container.plugins) { const pluginPos = plugin.particlePosition !== undefined ? plugin.particlePosition(position, this) : undefined; if (pluginPos !== undefined) { return Vector3d.create(pluginPos.x, pluginPos.y, zIndex); } } const canvasSize = container.canvas.size, exactPosition = calcExactPositionOrRandomFromSize({ size: canvasSize, position: position, }), pos = Vector3d.create(exactPosition.x, exactPosition.y, zIndex), radius = this.getRadius(), outModes = this.options.move.outModes, fixHorizontal = (outMode) => { fixOutMode({ outMode, checkModes: ["bounce", "bounce-horizontal"], coord: pos.x, maxCoord: container.canvas.size.width, setCb: (value) => (pos.x += value), radius, }); }, fixVertical = (outMode) => { fixOutMode({ outMode, checkModes: ["bounce", "bounce-vertical"], coord: pos.y, maxCoord: container.canvas.size.height, setCb: (value) => (pos.y += value), radius, }); }; fixHorizontal((_a = outModes.left) !== null && _a !== void 0 ? _a : outModes.default); fixHorizontal((_b = outModes.right) !== null && _b !== void 0 ? _b : outModes.default); fixVertical((_c = outModes.top) !== null && _c !== void 0 ? _c : outModes.default); fixVertical((_d = outModes.bottom) !== null && _d !== void 0 ? _d : outModes.default); if (this._checkOverlap(pos, tryCount)) { return this._calcPosition(container, undefined, zIndex, tryCount + 1); } return pos; } _calculateVelocity() { const baseVelocity = getParticleBaseVelocity(this.direction), res = baseVelocity.copy(), moveOptions = this.options.move; if (moveOptions.direction === "inside" || moveOptions.direction === "outside") { return res; } const rad = (Math.PI / 180) * getRangeValue(moveOptions.angle.value), radOffset = (Math.PI / 180) * getRangeValue(moveOptions.angle.offset), range = { left: radOffset - rad / 2, right: radOffset + rad / 2, }; if (!moveOptions.straight) { res.angle += randomInRange(setRangeValue(range.left, range.right)); } if (moveOptions.random && typeof moveOptions.speed === "number") { res.length *= getRandom(); } return res; } _checkOverlap(pos, tryCount = 0) { const collisionsOptions = this.options.collisions, radius = this.getRadius(); if (!collisionsOptions.enable) { return false; } const overlapOptions = collisionsOptions.overlap; if (overlapOptions.enable) { return false; } const retries = overlapOptions.retries; if (retries >= 0 && tryCount > retries) { throw new Error("Particle is overlapping and can't be placed"); } let overlaps = false; for (const particle of this.container.particles.array) { if (getDistance(pos, particle.position) < radius + particle.getRadius()) { overlaps = true; break; } } return overlaps; } _getRollColor(color) { var _a; if (!color || !this.roll || (!this.backColor && !this.roll.alter)) { return color; } const backFactor = this.roll.horizontal && this.roll.vertical ? 2 : 1, backSum = this.roll.horizontal ? Math.PI / 2 : 0, rolled = Math.floor((((_a = this.roll.angle) !== null && _a !== void 0 ? _a : 0) + backSum) / (Math.PI / backFactor)) % 2; if (!rolled) { return color; } if (this.backColor) { return this.backColor; } if (this.roll.alter) { return alterHsl(color, this.roll.alter.type, this.roll.alter.value); } return color; } _loadShapeData(shapeOptions, reduceDuplicates) { const shapeData = shapeOptions.options[this.shape]; if (shapeData) { return deepExtend({}, itemFromSingleOrMultiple(shapeData, this.id, reduceDuplicates)); } } }