UNPKG

10.6 kBJavaScriptView Raw
1import { InteractionManager } from "./Utils/InteractionManager";
2import { Particle } from "./Particle";
3import { Point } from "./Utils/Point";
4import { QuadTree } from "./Utils/QuadTree";
5import { Rectangle } from "./Utils/Rectangle";
6import { calcPositionFromSize } from "../Utils/NumberUtils";
7export class Particles {
8 constructor(engine, container) {
9 this.container = container;
10 this._engine = engine;
11 this.nextId = 0;
12 this.array = [];
13 this.zArray = [];
14 this.pool = [];
15 this.limit = 0;
16 this.needsSort = false;
17 this.lastZIndex = 0;
18 this.interactionManager = new InteractionManager(this._engine, container);
19 const canvasSize = this.container.canvas.size;
20 this.quadTree = new QuadTree(new Rectangle(-canvasSize.width / 4, -canvasSize.height / 4, (canvasSize.width * 3) / 2, (canvasSize.height * 3) / 2), 4);
21 this.movers = this._engine.plugins.getMovers(container, true);
22 this.updaters = this._engine.plugins.getUpdaters(container, true);
23 }
24 get count() {
25 return this.array.length;
26 }
27 addManualParticles() {
28 const container = this.container, options = container.actualOptions;
29 for (const particle of options.manualParticles) {
30 this.addParticle(calcPositionFromSize({
31 size: container.canvas.size,
32 position: particle.position,
33 }), particle.options);
34 }
35 }
36 addParticle(position, overrideOptions, group, initializer) {
37 const container = this.container, options = container.actualOptions, limit = options.particles.number.limit;
38 if (limit > 0) {
39 const countToRemove = this.count + 1 - limit;
40 if (countToRemove > 0) {
41 this.removeQuantity(countToRemove);
42 }
43 }
44 return this._pushParticle(position, overrideOptions, group, initializer);
45 }
46 clear() {
47 this.array = [];
48 this.zArray = [];
49 }
50 destroy() {
51 this.array = [];
52 this.zArray = [];
53 this.movers = [];
54 this.updaters = [];
55 }
56 async draw(delta) {
57 const container = this.container, canvasSize = this.container.canvas.size;
58 this.quadTree = new QuadTree(new Rectangle(-canvasSize.width / 4, -canvasSize.height / 4, (canvasSize.width * 3) / 2, (canvasSize.height * 3) / 2), 4);
59 container.canvas.clear();
60 await this.update(delta);
61 if (this.needsSort) {
62 this.zArray.sort((a, b) => b.position.z - a.position.z || a.id - b.id);
63 this.lastZIndex = this.zArray[this.zArray.length - 1].position.z;
64 this.needsSort = false;
65 }
66 for (const [, plugin] of container.plugins) {
67 container.canvas.drawPlugin(plugin, delta);
68 }
69 for (const p of this.zArray) {
70 p.draw(delta);
71 }
72 }
73 handleClickMode(mode) {
74 this.interactionManager.handleClickMode(mode);
75 }
76 init() {
77 var _a;
78 const container = this.container, options = container.actualOptions;
79 this.lastZIndex = 0;
80 this.needsSort = false;
81 let handled = false;
82 this.updaters = this._engine.plugins.getUpdaters(container, true);
83 this.interactionManager.init();
84 for (const [, plugin] of container.plugins) {
85 if (plugin.particlesInitialization !== undefined) {
86 handled = plugin.particlesInitialization();
87 }
88 if (handled) {
89 break;
90 }
91 }
92 this.interactionManager.init();
93 for (const [, pathGenerator] of container.pathGenerators) {
94 pathGenerator.init(container);
95 }
96 this.addManualParticles();
97 if (!handled) {
98 for (const group in options.particles.groups) {
99 const groupOptions = options.particles.groups[group];
100 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++) {
101 this.addParticle(undefined, groupOptions, group);
102 }
103 }
104 for (let i = this.count; i < options.particles.number.value; i++) {
105 this.addParticle();
106 }
107 }
108 }
109 push(nb, mouse, overrideOptions, group) {
110 this.pushing = true;
111 for (let i = 0; i < nb; i++) {
112 this.addParticle(mouse === null || mouse === void 0 ? void 0 : mouse.position, overrideOptions, group);
113 }
114 this.pushing = false;
115 }
116 async redraw() {
117 this.clear();
118 this.init();
119 await this.draw({ value: 0, factor: 0 });
120 }
121 remove(particle, group, override) {
122 this.removeAt(this.array.indexOf(particle), undefined, group, override);
123 }
124 removeAt(index, quantity = 1, group, override) {
125 if (index < 0 || index > this.count) {
126 return;
127 }
128 let deleted = 0;
129 for (let i = index; deleted < quantity && i < this.count; i++) {
130 const particle = this.array[i];
131 if (!particle || particle.group !== group) {
132 continue;
133 }
134 particle.destroy(override);
135 this.array.splice(i--, 1);
136 const zIdx = this.zArray.indexOf(particle);
137 this.zArray.splice(zIdx, 1);
138 this.pool.push(particle);
139 deleted++;
140 this._engine.dispatchEvent("particleRemoved", {
141 container: this.container,
142 data: {
143 particle,
144 },
145 });
146 }
147 }
148 removeQuantity(quantity, group) {
149 this.removeAt(0, quantity, group);
150 }
151 setDensity() {
152 const options = this.container.actualOptions;
153 for (const group in options.particles.groups) {
154 this._applyDensity(options.particles.groups[group], 0, group);
155 }
156 this._applyDensity(options.particles, options.manualParticles.length);
157 }
158 async update(delta) {
159 var _a, _b;
160 const container = this.container, particlesToDelete = [];
161 for (const [, pathGenerator] of container.pathGenerators) {
162 pathGenerator.update();
163 }
164 for (const [, plugin] of container.plugins) {
165 (_a = plugin.update) === null || _a === void 0 ? void 0 : _a.call(plugin, delta);
166 }
167 for (const particle of this.array) {
168 const resizeFactor = container.canvas.resizeFactor;
169 if (resizeFactor && !particle.ignoresResizeRatio) {
170 particle.position.x *= resizeFactor.width;
171 particle.position.y *= resizeFactor.height;
172 particle.initialPosition.x *= resizeFactor.width;
173 particle.initialPosition.y *= resizeFactor.height;
174 }
175 particle.ignoresResizeRatio = false;
176 await this.interactionManager.reset(particle);
177 for (const [, plugin] of this.container.plugins) {
178 if (particle.destroyed) {
179 break;
180 }
181 (_b = plugin.particleUpdate) === null || _b === void 0 ? void 0 : _b.call(plugin, particle, delta);
182 }
183 for (const mover of this.movers) {
184 if (mover.isEnabled(particle)) {
185 mover.move(particle, delta);
186 }
187 }
188 if (particle.destroyed) {
189 particlesToDelete.push(particle);
190 continue;
191 }
192 this.quadTree.insert(new Point(particle.getPosition(), particle));
193 }
194 for (const particle of particlesToDelete) {
195 this.remove(particle);
196 }
197 await this.interactionManager.externalInteract(delta);
198 for (const particle of this.array) {
199 for (const updater of this.updaters) {
200 updater.update(particle, delta);
201 }
202 if (!particle.destroyed && !particle.spawning) {
203 await this.interactionManager.particlesInteract(particle, delta);
204 }
205 }
206 delete container.canvas.resizeFactor;
207 }
208 _applyDensity(options, manualCount, group) {
209 var _a;
210 if (!((_a = options.number.density) === null || _a === void 0 ? void 0 : _a.enable)) {
211 return;
212 }
213 const numberOptions = options.number, densityFactor = this._initDensityFactor(numberOptions.density), optParticlesNumber = numberOptions.value, optParticlesLimit = numberOptions.limit > 0 ? numberOptions.limit : optParticlesNumber, particlesNumber = Math.min(optParticlesNumber, optParticlesLimit) * densityFactor + manualCount, particlesCount = Math.min(this.count, this.array.filter((t) => t.group === group).length);
214 this.limit = numberOptions.limit * densityFactor;
215 if (particlesCount < particlesNumber) {
216 this.push(Math.abs(particlesNumber - particlesCount), undefined, options, group);
217 }
218 else if (particlesCount > particlesNumber) {
219 this.removeQuantity(particlesCount - particlesNumber, group);
220 }
221 }
222 _initDensityFactor(densityOptions) {
223 const container = this.container;
224 if (!container.canvas.element || !densityOptions.enable) {
225 return 1;
226 }
227 const canvas = container.canvas.element, pxRatio = container.retina.pixelRatio;
228 return (canvas.width * canvas.height) / (densityOptions.factor * pxRatio ** 2 * densityOptions.area);
229 }
230 _pushParticle(position, overrideOptions, group, initializer) {
231 try {
232 let particle = this.pool.pop();
233 if (particle) {
234 particle.init(this.nextId, position, overrideOptions, group);
235 }
236 else {
237 particle = new Particle(this._engine, this.nextId, this.container, position, overrideOptions, group);
238 }
239 let canAdd = true;
240 if (initializer) {
241 canAdd = initializer(particle);
242 }
243 if (!canAdd) {
244 return;
245 }
246 this.array.push(particle);
247 this.zArray.push(particle);
248 this.nextId++;
249 this._engine.dispatchEvent("particleAdded", {
250 container: this.container,
251 data: {
252 particle,
253 },
254 });
255 return particle;
256 }
257 catch (e) {
258 console.warn(`error adding particle: ${e}`);
259 return;
260 }
261 }
262}