UNPKG

12.8 kBJavaScriptView Raw
1import { Particle } from "./Particle";
2import { getRangeValue, Plugins, Point, QuadTree, randomInRange, Rectangle, setRangeValue } from "../Utils";
3import { InteractionManager } from "./InteractionManager";
4import { ParticlesOptions } from "../Options/Classes/Particles/ParticlesOptions";
5import { Mover } from "./Particle/Mover";
6/**
7 * Particles manager object
8 * @category Core
9 */
10export class Particles {
11 constructor(container) {
12 this.container = container;
13 this.nextId = 0;
14 this.array = [];
15 this.zArray = [];
16 this.mover = new Mover(container);
17 this.limit = 0;
18 this.needsSort = false;
19 this.lastZIndex = 0;
20 this.freqs = {
21 links: new Map(),
22 triangles: new Map(),
23 };
24 this.interactionManager = new InteractionManager(container);
25 const canvasSize = this.container.canvas.size;
26 this.linksColors = new Map();
27 this.quadTree = new QuadTree(new Rectangle(-canvasSize.width / 4, -canvasSize.height / 4, (canvasSize.width * 3) / 2, (canvasSize.height * 3) / 2), 4);
28 this.updaters = Plugins.getUpdaters(container);
29 }
30 get count() {
31 return this.array.length;
32 }
33 /* --------- tsParticles functions - particles ----------- */
34 init() {
35 var _a;
36 const container = this.container;
37 const options = container.actualOptions;
38 this.lastZIndex = 0;
39 this.needsSort = false;
40 this.freqs.links = new Map();
41 this.freqs.triangles = new Map();
42 let handled = false;
43 for (const [, plugin] of container.plugins) {
44 if (plugin.particlesInitialization !== undefined) {
45 handled = plugin.particlesInitialization();
46 }
47 if (handled) {
48 break;
49 }
50 }
51 this.addManualParticles();
52 if (!handled) {
53 for (const group in options.particles.groups) {
54 const groupOptions = options.particles.groups[group];
55 for (let i = this.count, j = 0; j < ((_a = groupOptions.number) === null || _a === void 0 ? void 0 : _a.value) && i < options.particles.number.value; i++, j++) {
56 this.addParticle(undefined, groupOptions, group);
57 }
58 }
59 for (let i = this.count; i < options.particles.number.value; i++) {
60 this.addParticle();
61 }
62 }
63 container.pathGenerator.init(container);
64 }
65 redraw() {
66 this.clear();
67 this.init();
68 this.draw({ value: 0, factor: 0 });
69 }
70 removeAt(index, quantity = 1, group, override) {
71 if (!(index >= 0 && index <= this.count)) {
72 return;
73 }
74 let deleted = 0;
75 for (let i = index; deleted < quantity && i < this.count; i++) {
76 const particle = this.array[i];
77 if (!particle || particle.group !== group) {
78 continue;
79 }
80 particle.destroy(override);
81 this.array.splice(i--, 1);
82 const zIdx = this.zArray.indexOf(particle);
83 this.zArray.splice(zIdx, 1);
84 deleted++;
85 }
86 }
87 remove(particle, group, override) {
88 this.removeAt(this.array.indexOf(particle), undefined, group, override);
89 }
90 update(delta) {
91 const container = this.container;
92 const particlesToDelete = [];
93 container.pathGenerator.update();
94 for (const [, plugin] of container.plugins) {
95 if (plugin.update !== undefined) {
96 plugin.update(delta);
97 }
98 }
99 for (const particle of this.array) {
100 // let d = ( dx = container.interactivity.mouse.click_pos_x - p.x ) * dx +
101 // ( dy = container.interactivity.mouse.click_pos_y - p.y ) * dy;
102 // let f = -BANG_SIZE / d;
103 // if ( d < BANG_SIZE ) {
104 // let t = Math.atan2( dy, dx );
105 // p.vx = f * Math.cos(t);
106 // p.vy = f * Math.sin(t);
107 // }
108 const resizeFactor = container.canvas.resizeFactor;
109 if (resizeFactor) {
110 particle.position.x *= resizeFactor.width;
111 particle.position.y *= resizeFactor.height;
112 }
113 particle.bubble.inRange = false;
114 for (const [, plugin] of this.container.plugins) {
115 if (particle.destroyed) {
116 break;
117 }
118 if (plugin.particleUpdate) {
119 plugin.particleUpdate(particle, delta);
120 }
121 }
122 this.mover.move(particle, delta);
123 if (particle.destroyed) {
124 particlesToDelete.push(particle);
125 continue;
126 }
127 this.quadTree.insert(new Point(particle.getPosition(), particle));
128 }
129 for (const particle of particlesToDelete) {
130 this.remove(particle);
131 }
132 this.interactionManager.externalInteract(delta);
133 // this loop is required to be done after mouse interactions
134 for (const particle of container.particles.array) {
135 for (const updater of this.updaters) {
136 updater.update(particle, delta);
137 }
138 if (!particle.destroyed && !particle.spawning) {
139 this.interactionManager.particlesInteract(particle, delta);
140 }
141 }
142 delete container.canvas.resizeFactor;
143 }
144 draw(delta) {
145 const container = this.container;
146 /* clear canvas */
147 container.canvas.clear();
148 const canvasSize = this.container.canvas.size;
149 this.quadTree = new QuadTree(new Rectangle(-canvasSize.width / 4, -canvasSize.height / 4, (canvasSize.width * 3) / 2, (canvasSize.height * 3) / 2), 4);
150 /* update each particles param */
151 this.update(delta);
152 if (this.needsSort) {
153 this.zArray.sort((a, b) => b.position.z - a.position.z || a.id - b.id);
154 this.lastZIndex = this.zArray[this.zArray.length - 1].position.z;
155 this.needsSort = false;
156 }
157 /* draw polygon shape in debug mode */
158 for (const [, plugin] of container.plugins) {
159 container.canvas.drawPlugin(plugin, delta);
160 }
161 /*if (container.canvas.context) {
162 this.quadTree.draw(container.canvas.context);
163 }*/
164 /* draw each particle */
165 for (const p of this.zArray) {
166 p.draw(delta);
167 }
168 }
169 /**
170 * Removes all particles from the array
171 */
172 clear() {
173 this.array = [];
174 this.zArray = [];
175 }
176 /* ---------- tsParticles functions - modes events ------------ */
177 push(nb, mouse, overrideOptions, group) {
178 this.pushing = true;
179 for (let i = 0; i < nb; i++) {
180 this.addParticle(mouse === null || mouse === void 0 ? void 0 : mouse.position, overrideOptions, group);
181 }
182 this.pushing = false;
183 }
184 addParticle(position, overrideOptions, group) {
185 const container = this.container;
186 const options = container.actualOptions;
187 const limit = options.particles.number.limit * container.density;
188 if (limit > 0) {
189 const countToRemove = this.count + 1 - limit;
190 if (countToRemove > 0) {
191 this.removeQuantity(countToRemove);
192 }
193 }
194 return this.pushParticle(position, overrideOptions, group);
195 }
196 addSplitParticle(parent) {
197 const splitOptions = parent.options.destroy.split;
198 const options = new ParticlesOptions();
199 options.load(parent.options);
200 const factor = getRangeValue(splitOptions.factor.value);
201 options.color.load({
202 value: {
203 hsl: parent.getFillColor(),
204 },
205 });
206 if (typeof options.size.value === "number") {
207 options.size.value /= factor;
208 }
209 else {
210 options.size.value.min /= factor;
211 options.size.value.max /= factor;
212 }
213 options.load(splitOptions.particles);
214 const offset = splitOptions.sizeOffset ? setRangeValue(-parent.size.value, parent.size.value) : 0;
215 const position = {
216 x: parent.position.x + randomInRange(offset),
217 y: parent.position.y + randomInRange(offset),
218 };
219 return this.pushParticle(position, options, parent.group, (particle) => {
220 if (particle.size.value < 0.5) {
221 return false;
222 }
223 particle.velocity.length = randomInRange(setRangeValue(parent.velocity.length, particle.velocity.length));
224 particle.splitCount = parent.splitCount + 1;
225 particle.unbreakable = true;
226 setTimeout(() => {
227 particle.unbreakable = false;
228 }, 500);
229 return true;
230 });
231 }
232 removeQuantity(quantity, group) {
233 this.removeAt(0, quantity, group);
234 }
235 getLinkFrequency(p1, p2) {
236 const key = `${Math.min(p1.id, p2.id)}_${Math.max(p1.id, p2.id)}`;
237 let res = this.freqs.links.get(key);
238 if (res === undefined) {
239 res = Math.random();
240 this.freqs.links.set(key, res);
241 }
242 return res;
243 }
244 getTriangleFrequency(p1, p2, p3) {
245 let [id1, id2, id3] = [p1.id, p2.id, p3.id];
246 if (id1 > id2) {
247 [id2, id1] = [id1, id2];
248 }
249 if (id2 > id3) {
250 [id3, id2] = [id2, id3];
251 }
252 if (id1 > id3) {
253 [id3, id1] = [id1, id3];
254 }
255 const key = `${id1}_${id2}_${id3}`;
256 let res = this.freqs.triangles.get(key);
257 if (res === undefined) {
258 res = Math.random();
259 this.freqs.triangles.set(key, res);
260 }
261 return res;
262 }
263 addManualParticles() {
264 const container = this.container;
265 const options = container.actualOptions;
266 for (const particle of options.manualParticles) {
267 const pos = particle.position
268 ? {
269 x: (particle.position.x * container.canvas.size.width) / 100,
270 y: (particle.position.y * container.canvas.size.height) / 100,
271 }
272 : undefined;
273 this.addParticle(pos, particle.options);
274 }
275 }
276 setDensity() {
277 const options = this.container.actualOptions;
278 for (const group in options.particles.groups) {
279 this.applyDensity(options.particles.groups[group], 0, group);
280 }
281 this.applyDensity(options.particles, options.manualParticles.length);
282 }
283 applyDensity(options, manualCount, group) {
284 var _a;
285 if (!((_a = options.number.density) === null || _a === void 0 ? void 0 : _a.enable)) {
286 return;
287 }
288 const numberOptions = options.number;
289 const densityFactor = this.initDensityFactor(numberOptions.density);
290 const optParticlesNumber = numberOptions.value;
291 const optParticlesLimit = numberOptions.limit > 0 ? numberOptions.limit : optParticlesNumber;
292 const particlesNumber = Math.min(optParticlesNumber, optParticlesLimit) * densityFactor + manualCount;
293 const particlesCount = Math.min(this.count, this.array.filter((t) => t.group === group).length);
294 this.limit = numberOptions.limit * densityFactor;
295 if (particlesCount < particlesNumber) {
296 this.push(Math.abs(particlesNumber - particlesCount), undefined, options, group);
297 }
298 else if (particlesCount > particlesNumber) {
299 this.removeQuantity(particlesCount - particlesNumber, group);
300 }
301 }
302 initDensityFactor(densityOptions) {
303 const container = this.container;
304 if (!container.canvas.element || !densityOptions.enable) {
305 return 1;
306 }
307 const canvas = container.canvas.element;
308 const pxRatio = container.retina.pixelRatio;
309 return (canvas.width * canvas.height) / (densityOptions.factor * pxRatio ** 2 * densityOptions.area);
310 }
311 pushParticle(position, overrideOptions, group, initializer) {
312 try {
313 const particle = new Particle(this.nextId, this.container, position, overrideOptions, group);
314 let canAdd = true;
315 if (initializer) {
316 canAdd = initializer(particle);
317 }
318 if (!canAdd) {
319 return;
320 }
321 this.array.push(particle);
322 this.zArray.push(particle);
323 this.nextId++;
324 return particle;
325 }
326 catch (e) {
327 console.warn(`error adding particle: ${e}`);
328 return;
329 }
330 }
331}