UNPKG

9.89 kBJavaScriptView Raw
1import { Particle } from "./Particle";
2import { NumberUtils, Point, QuadTree, Rectangle, Utils } from "../Utils";
3import { InteractionManager } from "./Particle/InteractionManager";
4import { ParticlesOptions } from "../Options/Classes/Particles/ParticlesOptions";
5export class Particles {
6 constructor(container) {
7 this.container = container;
8 this.nextId = 0;
9 this.array = [];
10 this.limit = 0;
11 this.linksFreq = new Map();
12 this.trianglesFreq = new Map();
13 this.interactionManager = new InteractionManager(container);
14 const canvasSize = this.container.canvas.size;
15 this.linksColors = new Map();
16 this.quadTree = new QuadTree(new Rectangle(-canvasSize.width / 4, -canvasSize.height / 4, (canvasSize.width * 3) / 2, (canvasSize.height * 3) / 2), 4);
17 }
18 get count() {
19 return this.array.length;
20 }
21 init() {
22 const container = this.container;
23 const options = container.actualOptions;
24 this.linksFreq = new Map();
25 this.trianglesFreq = new Map();
26 let handled = false;
27 for (const particle of options.manualParticles) {
28 const pos = particle.position
29 ? {
30 x: (particle.position.x * container.canvas.size.width) / 100,
31 y: (particle.position.y * container.canvas.size.height) / 100,
32 }
33 : undefined;
34 this.addParticle(pos, particle.options);
35 }
36 for (const [, plugin] of container.plugins) {
37 if (plugin.particlesInitialization !== undefined) {
38 handled = plugin.particlesInitialization();
39 }
40 if (handled) {
41 break;
42 }
43 }
44 if (!handled) {
45 for (let i = this.count; i < options.particles.number.value; i++) {
46 this.addParticle();
47 }
48 }
49 if (options.infection.enable) {
50 for (let i = 0; i < options.infection.infections; i++) {
51 const notInfected = this.array.filter((p) => p.infecter.infectionStage === undefined);
52 const infected = Utils.itemFromArray(notInfected);
53 infected.infecter.startInfection(0);
54 }
55 }
56 this.interactionManager.init();
57 container.pathGenerator.init();
58 }
59 redraw() {
60 this.clear();
61 this.init();
62 this.draw({ value: 0, factor: 0 });
63 }
64 removeAt(index, quantity, override) {
65 if (index >= 0 && index <= this.count) {
66 for (const particle of this.array.splice(index, quantity !== null && quantity !== void 0 ? quantity : 1)) {
67 particle.destroy(override);
68 }
69 }
70 }
71 remove(particle, override) {
72 this.removeAt(this.array.indexOf(particle), undefined, override);
73 }
74 update(delta) {
75 const container = this.container;
76 const particlesToDelete = [];
77 container.pathGenerator.update();
78 for (const [, plugin] of container.plugins) {
79 if (plugin.update !== undefined) {
80 plugin.update(delta);
81 }
82 }
83 for (const particle of this.array) {
84 const resizeFactor = this.container.canvas.resizeFactor;
85 if (resizeFactor) {
86 particle.position.x *= resizeFactor.width;
87 particle.position.y *= resizeFactor.height;
88 }
89 particle.move(delta);
90 if (particle.destroyed) {
91 particlesToDelete.push(particle);
92 continue;
93 }
94 this.quadTree.insert(new Point(particle.getPosition(), particle));
95 }
96 for (const particle of particlesToDelete) {
97 this.remove(particle);
98 }
99 this.interactionManager.externalInteract(delta);
100 for (const particle of this.container.particles.array) {
101 particle.update(delta);
102 if (!particle.destroyed && !particle.spawning) {
103 this.interactionManager.particlesInteract(particle, delta);
104 }
105 }
106 delete container.canvas.resizeFactor;
107 }
108 draw(delta) {
109 const container = this.container;
110 container.canvas.clear();
111 const canvasSize = this.container.canvas.size;
112 this.quadTree = new QuadTree(new Rectangle(-canvasSize.width / 4, -canvasSize.height / 4, (canvasSize.width * 3) / 2, (canvasSize.height * 3) / 2), 4);
113 this.update(delta);
114 for (const [, plugin] of container.plugins) {
115 container.canvas.drawPlugin(plugin, delta);
116 }
117 for (const p of this.array) {
118 p.draw(delta);
119 }
120 }
121 clear() {
122 this.array = [];
123 }
124 push(nb, mouse, overrideOptions) {
125 const container = this.container;
126 const options = container.actualOptions;
127 const limit = options.particles.number.limit * container.density;
128 this.pushing = true;
129 if (limit > 0) {
130 const countToRemove = this.count + nb - limit;
131 if (countToRemove > 0) {
132 this.removeQuantity(countToRemove);
133 }
134 }
135 for (let i = 0; i < nb; i++) {
136 this.addParticle(mouse === null || mouse === void 0 ? void 0 : mouse.position, overrideOptions);
137 }
138 this.pushing = false;
139 }
140 addParticle(position, overrideOptions) {
141 return this.pushParticle(position, overrideOptions);
142 }
143 addSplitParticle(parent) {
144 const splitOptions = parent.options.destroy.split;
145 const options = new ParticlesOptions();
146 options.load(parent.options);
147 const factor = NumberUtils.getRangeValue(splitOptions.factor.value);
148 options.color.load({
149 value: {
150 hsl: parent.getFillColor(),
151 },
152 });
153 if (typeof options.size.value === "number") {
154 options.size.value /= factor;
155 }
156 else {
157 options.size.value.min /= factor;
158 options.size.value.max /= factor;
159 }
160 options.load(splitOptions.particles);
161 const offset = NumberUtils.setRangeValue(-parent.size.value, parent.size.value);
162 const position = {
163 x: parent.position.x + NumberUtils.randomInRange(offset),
164 y: parent.position.y + NumberUtils.randomInRange(offset),
165 };
166 return this.pushParticle(position, options, (particle) => {
167 if (particle.size.value < 0.5) {
168 return false;
169 }
170 particle.velocity.length = NumberUtils.randomInRange(NumberUtils.setRangeValue(parent.velocity.length, particle.velocity.length));
171 particle.splitCount = parent.splitCount + 1;
172 particle.unbreakable = true;
173 setTimeout(() => {
174 particle.unbreakable = false;
175 }, 500);
176 return true;
177 });
178 }
179 removeQuantity(quantity) {
180 this.removeAt(0, quantity);
181 }
182 getLinkFrequency(p1, p2) {
183 const key = `${Math.min(p1.id, p2.id)}_${Math.max(p1.id, p2.id)}`;
184 let res = this.linksFreq.get(key);
185 if (res === undefined) {
186 res = Math.random();
187 this.linksFreq.set(key, res);
188 }
189 return res;
190 }
191 getTriangleFrequency(p1, p2, p3) {
192 let [id1, id2, id3] = [p1.id, p2.id, p3.id];
193 if (id1 > id2) {
194 [id2, id1] = [id1, id2];
195 }
196 if (id2 > id3) {
197 [id3, id2] = [id2, id3];
198 }
199 if (id1 > id3) {
200 [id3, id1] = [id1, id3];
201 }
202 const key = `${id1}_${id2}_${id3}`;
203 let res = this.trianglesFreq.get(key);
204 if (res === undefined) {
205 res = Math.random();
206 this.trianglesFreq.set(key, res);
207 }
208 return res;
209 }
210 setDensity() {
211 const options = this.container.actualOptions;
212 this.applyDensity(options.particles);
213 }
214 applyDensity(options) {
215 var _a;
216 if (!((_a = options.number.density) === null || _a === void 0 ? void 0 : _a.enable)) {
217 return;
218 }
219 const numberOptions = options.number;
220 const densityFactor = this.initDensityFactor(numberOptions.density);
221 const optParticlesNumber = numberOptions.value;
222 const optParticlesLimit = numberOptions.limit > 0 ? numberOptions.limit : optParticlesNumber;
223 const particlesNumber = Math.min(optParticlesNumber, optParticlesLimit) * densityFactor;
224 const particlesCount = this.count;
225 this.limit = numberOptions.limit * densityFactor;
226 if (particlesCount < particlesNumber) {
227 this.push(Math.abs(particlesNumber - particlesCount), undefined, options);
228 }
229 else if (particlesCount > particlesNumber) {
230 this.removeQuantity(particlesCount - particlesNumber);
231 }
232 }
233 initDensityFactor(densityOptions) {
234 const container = this.container;
235 if (!container.canvas.element || !densityOptions.enable) {
236 return 1;
237 }
238 const canvas = container.canvas.element;
239 const pxRatio = container.retina.pixelRatio;
240 return (canvas.width * canvas.height) / (densityOptions.factor * pxRatio * pxRatio * densityOptions.area);
241 }
242 pushParticle(position, overrideOptions, initializer) {
243 try {
244 const particle = new Particle(this.nextId, this.container, position, overrideOptions);
245 let canAdd = true;
246 if (initializer) {
247 canAdd = initializer(particle);
248 }
249 if (!canAdd) {
250 return;
251 }
252 this.array.push(particle);
253 this.nextId++;
254 return particle;
255 }
256 catch (e) {
257 console.warn(`error adding particle: ${e}`);
258 return;
259 }
260 }
261}