1 | import { InteractionManager } from "./Utils/InteractionManager";
|
2 | import { Particle } from "./Particle";
|
3 | import { Point } from "./Utils/Point";
|
4 | import { QuadTree } from "./Utils/QuadTree";
|
5 | import { Rectangle } from "./Utils/Rectangle";
|
6 | import { calcPositionFromSize } from "../Utils/NumberUtils";
|
7 | export 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 | }
|