332 lines
15 KiB
JavaScript
332 lines
15 KiB
JavaScript
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));
|
|
}
|
|
}
|
|
}
|