1 module demos.particles;
2
3 import app;
4
5 import bindbc.sdl;
6
7 import cimgui.cimgui;
8
9 import bubel.ecs.attributes;
10 import bubel.ecs.core;
11 import bubel.ecs.entity;
12 import bubel.ecs.manager;
13 import bubel.ecs.std;
14
15 import ecs_utils.gfx.texture;
16 import ecs_utils.math.vector;
17 import ecs_utils.utils;
18
19 import game_core.basic;
20 import game_core.rendering;
21
22 import gui.attributes;
23
24 extern(C):
25
26 private enum float px = 1.0/512.0;
27
28 /*#######################################################################################################################
29 ------------------------------------------------ Components ------------------------------------------------------------------
30 #######################################################################################################################*/
31
32 /*struct CLocation
33 {
34 mixin ECS.Component;
35
36 alias location this;
37
38 vec2 location;
39 }
40
41 struct CColor
42 {
43 mixin ECS.Component;
44
45 alias value this;
46
47 @GUIColor uint value = uint.max;
48 }
49
50 struct CTexCoords
51 {
52 mixin ECS.Component;
53
54 vec4 value;
55 }*/
56
57 // struct CVelocity
58 // {
59 // mixin ECS.Component;
60
61 // alias value this;
62
63 // vec2 value = vec2(0);
64 // }
65
66 struct CForceRange
67 {
68 mixin ECS.Component;
69
70 vec2 range = vec2(20,200);
71 }
72
73 struct CAttractor
74 {
75 mixin ECS.Component;
76
77 //alias value this;
78 float strength = 0.2;
79 }
80
81 struct CVortex
82 {
83 mixin ECS.Component;
84
85 float strength = 0.6;
86 }
87
88 // struct CDamping
89 // {
90 // mixin ECS.Component;
91
92 // alias power this;
93
94 // @GUIRange(0,9) ubyte power = 0;
95 // }
96
97 struct CGravity
98 {
99 mixin ECS.Component;
100 }
101
102 struct CParticleLife
103 {
104 mixin ECS.Component;
105
106 this(float life_in_secs)
107 {
108 life = cast(int)(life_in_secs * 1000_000);
109 }
110
111 alias life this;
112
113 int life = 1000000;
114 }
115
116 /*#######################################################################################################################
117 ------------------------------------------------ Systems ------------------------------------------------------------------
118 #######################################################################################################################*/
119
120 struct MouseAttractSystem
121 {
122 mixin ECS.System!64;
123
124 struct EntitiesData
125 {
126 uint length;
127 @readonly CLocation[] locations;
128 CVelocity[] velocity;
129 }
130
131 vec2 mouse_pos;
132
133 bool onBegin()
134 {
135 if(!launcher.getKeyState(SDL_SCANCODE_SPACE))return false;
136 mouse_pos = launcher.mouse.position;
137 mouse_pos = vec2(mouse_pos.x, mouse_pos.y) * launcher.scalling - launcher.render_position;
138 return true;
139 }
140
141 void onUpdate(EntitiesData data)
142 {
143 float speed = launcher.deltaTime * 0.01;
144 foreach(i;0..data.length)
145 {
146 vec2 rel_pos = mouse_pos - data.locations[i];
147 float len2 = rel_pos.x * rel_pos.x + rel_pos.y * rel_pos.y;
148 if(len2 < 0.1)len2 = 0.1;
149 data.velocity[i] = data.velocity[i] + rel_pos / len2 * speed;
150 }
151 }
152 }
153
154 struct AttractSystem
155 {
156 mixin ECS.System!64;
157
158 struct EntitiesData
159 {
160 uint length;
161 @readonly CLocation[] locations;
162 CVelocity[] velocity;
163 }
164
165 struct Updater
166 {
167 AttractSystem.EntitiesData data;
168
169 void onUpdate(AttractorIterator.EntitiesData adata)
170 {
171 float speed = launcher.deltaTime * 0.00004;
172 if(adata.vortex)
173 {
174 foreach(i;0..data.length)
175 {
176 foreach(j;0..adata.length)
177 {
178 vec2 rel_pos = data.locations[i] - adata.locations[j];
179 float len2 = rel_pos.length2();
180 float inv_len = rsqrt(len2);
181
182 if(1 < adata.force_range[j].range.y*inv_len)
183 {
184 float dist = (adata.force_range[j].range.y - 0.4)*inv_len - 1;
185
186 vec2 vec = rel_pos * inv_len;
187 vec2 cvec = vec2(-vec.y,vec.x);
188
189 float sign = -1;
190 if(1 < adata.force_range[j].range.x*inv_len)sign = 1;
191
192 float str = adata.attractor[j].strength * sign;
193 float vortex_str = adata.vortex[j].strength;
194 data.velocity[i] = data.velocity[i] + (rel_pos * str + cvec * vortex_str) * speed * dist;
195 }
196 }
197 }
198 }
199 else
200 {
201 foreach(i;0..data.length)
202 {
203 foreach(j;0..adata.length)
204 {
205 vec2 rel_pos = data.locations[i] - adata.locations[j];
206 float len2 = rel_pos.length2();
207 float inv_len = rsqrt(len2);
208
209 if(1 < adata.force_range[j].range.y*inv_len)
210 {
211 float dist = (adata.force_range[j].range.y - 0.4)*inv_len - 1;
212
213 vec2 vec = rel_pos;
214
215 float sign = -1;
216 if(1 < adata.force_range[j].range.x*inv_len)sign = 1;
217
218 float str = adata.attractor[j].strength * speed * dist * sign;
219 data.velocity[i] = data.velocity[i] + vec * str;
220 }
221 }
222 }
223 }
224 }
225 }
226
227 void onUpdate(EntitiesData data)
228 {
229 Updater updater;
230 updater.data = data;
231 gEntityManager.callEntitiesFunction!AttractorIterator(&updater.onUpdate);
232 }
233 }
234
235 struct AttractorIterator
236 {
237 mixin ECS.System!1;
238
239 struct EntitiesData
240 {
241 uint length;
242 @readonly CLocation[] locations;
243 @readonly CAttractor[] attractor;
244 @readonly CForceRange[] force_range;
245 @optional @readonly CVortex[] vortex;
246 }
247
248 bool onBegin()
249 {
250 return false;
251 }
252
253 void onUpdate(EntitiesData data)
254 {
255
256 }
257 }
258
259 struct PlayAreaSystem
260 {
261 mixin ECS.System!32;
262
263 struct EntitiesData
264 {
265 uint length;
266 Entity[] entity;
267 @readonly CLocation[] locations;
268 }
269
270 void onUpdate(EntitiesData data)
271 {
272 foreach(i; 0..data.length)
273 {
274 if(data.locations[i].x > 440)gEntityManager.removeEntity(data.entity[i].id);
275 else if(data.locations[i].x < -40)gEntityManager.removeEntity(data.entity[i].id);
276 if(data.locations[i].y > 340)gEntityManager.removeEntity(data.entity[i].id);
277 else if(data.locations[i].y < -40)gEntityManager.removeEntity(data.entity[i].id);
278 }
279 }
280 }
281
282 struct ParticleLifeSystem
283 {
284 mixin ECS.System!32;
285
286 struct EntitiesData
287 {
288 uint length;
289 const (Entity)[] entity;
290 CParticleLife[] life;
291 }
292
293 int delta_time;
294
295 bool onBegin()
296 {
297 delta_time = cast(int)(launcher.deltaTime * 1000);
298 return true;
299 }
300
301 void onUpdate(EntitiesData data)
302 {
303 foreach(i; 0..data.length)
304 {
305 data.life[i] -= delta_time;
306 if(data.life[i] < 0)gEntityManager.removeEntity(data.entity[i].id);
307 }
308 }
309 }
310
311 struct GravitySystem
312 {
313 mixin ECS.System!32;
314
315 struct EntitiesData
316 {
317 uint length;
318 const (Entity)[] entity;
319 @readonly CGravity[] gravity;
320 CVelocity[] velocity;
321 }
322
323 void onUpdate(EntitiesData data)
324 {
325 float delta_time = launcher.deltaTime * 0.00_092;
326 foreach(i; 0..data.length)
327 {
328 data.velocity[i].y -= delta_time;
329 }
330 }
331 }
332
333 /*#######################################################################################################################
334 ------------------------------------------------ Functions ------------------------------------------------------------------
335 #######################################################################################################################*/
336
337 struct ParticlesDemo
338 {
339 __gshared const (char)* tips = "Particles by default have no velocity. You can spawn \"Attractor\" which attract particles, or \"Vortex\" which attracts and spin particles.
340 Please do not spawn to many of them as every \"Attractor\" iterate over all particles (brute force).";
341
342 Texture texture;
343 }
344
345 __gshared ParticlesDemo* particles_demo;
346
347 void particlesRegister()
348 {
349 particles_demo = Mallocator.make!ParticlesDemo;
350
351 particles_demo.texture.create();
352 particles_demo.texture.load("assets/textures/atlas.png");
353
354 gEntityManager.beginRegister();
355
356 registerRenderingModule(gEntityManager);
357
358 gEntityManager.registerComponent!CLocation;
359 //gEntityManager.registerComponent!CTexCoords;
360 gEntityManager.registerComponent!CColor;
361 gEntityManager.registerComponent!CVelocity;
362 gEntityManager.registerComponent!CScale;
363 gEntityManager.registerComponent!CTexCoords;
364 gEntityManager.registerComponent!CTexCoordsIndex;
365 gEntityManager.registerComponent!CRotation;
366 gEntityManager.registerComponent!CDepth;
367 gEntityManager.registerComponent!CAttractor;
368 gEntityManager.registerComponent!CDamping;
369 gEntityManager.registerComponent!CGravity;
370 gEntityManager.registerComponent!CVortex;
371 gEntityManager.registerComponent!CParticleLife;
372 gEntityManager.registerComponent!CForceRange;
373 gEntityManager.registerComponent!CMaterialIndex;
374 gEntityManager.registerComponent!CVelocityFactor;
375
376 gEntityManager.registerSystem!MoveSystem(0);
377 gEntityManager.registerSystem!DrawSystem(100);
378 gEntityManager.registerSystem!PlayAreaSystem(102);
379 gEntityManager.registerSystem!AttractSystem(-1);
380 gEntityManager.registerSystem!MouseAttractSystem(1);
381 gEntityManager.registerSystem!DampingSystem(101);
382 gEntityManager.registerSystem!ParticleLifeSystem(-10);
383 gEntityManager.registerSystem!GravitySystem(-2);
384
385 gEntityManager.registerSystem!AttractorIterator(-1);
386
387 gEntityManager.endRegister();
388 }
389
390 void particlesStart()
391 {
392 DrawSystem* draw_system = gEntityManager.getSystem!DrawSystem;
393 draw_system.default_data.size = vec2(2,2);
394 draw_system.default_data.coords = vec4(246,64,2,2)*px;
395 draw_system.default_data.material_id = 2;
396 draw_system.default_data.texture = particles_demo.texture;
397
398 launcher.gui_manager.addSystem(becsID!MoveSystem,"Move System");
399 launcher.gui_manager.addSystem(becsID!DrawSystem,"Draw System");
400 launcher.gui_manager.addSystem(becsID!PlayAreaSystem,"Play Area System");
401 launcher.gui_manager.addSystem(becsID!AttractSystem,"Attract System");
402 launcher.gui_manager.addSystem(becsID!MouseAttractSystem,"Mouse Attract System");
403 launcher.gui_manager.addSystem(becsID!DampingSystem,"Damping System");
404 launcher.gui_manager.addSystem(becsID!ParticleLifeSystem,"Particle Life System");
405 launcher.gui_manager.addSystem(becsID!GravitySystem,"Gravity System");
406
407 // launcher.gui_manager.addComponent(CColor(),"Color (white)");
408 // launcher.gui_manager.addComponent(CColor(0xFF101540),"Color (red)");
409 // launcher.gui_manager.addComponent(CColor(0xFF251010),"Color (blue)");
410 // launcher.gui_manager.addComponent(CColor(0xFF102010),"Color (green)");
411 launcher.gui_manager.addComponent(CLocation(),"Location");
412 launcher.gui_manager.addComponent(CScale(),"Scale");
413 launcher.gui_manager.addComponent(CTexCoords(),"Texture Coords");
414 launcher.gui_manager.addComponent(CTexCoordsIndex(),"Texture Coords Index");
415 launcher.gui_manager.addComponent(CRotation(),"Rotation");
416 launcher.gui_manager.addComponent(CDepth(),"Depth");
417 launcher.gui_manager.addComponent(CMaterialIndex(),"Material ID");
418 launcher.gui_manager.addComponent(CVelocityFactor(),"Velocity Factor");
419 launcher.gui_manager.addComponent(CAttractor(0.1),"Attractor");
420 launcher.gui_manager.addComponent(CForceRange(vec2(5,40)),"ForceRange");
421 launcher.gui_manager.addComponent(CVelocity(),"Velocity");
422 launcher.gui_manager.addComponent(CDamping(),"Damping");
423 launcher.gui_manager.addComponent(CVortex(),"Vortex");
424 launcher.gui_manager.addComponent(CParticleLife(),"Particle Life");
425 launcher.gui_manager.addComponent(CGravity(),"Gravity");
426
427 EntityTemplate* tmpl;
428 EntityTemplate* base_tmpl = gEntityManager.allocateTemplate([becsID!CTexCoords, becsID!CLocation, becsID!CColor, becsID!CVelocity, becsID!CDamping, becsID!CScale, becsID!CMaterialIndex].staticArray);
429 base_tmpl.getComponent!CColor().value = 0xFF251010;
430 base_tmpl.getComponent!CScale().value = vec2(2);
431 base_tmpl.getComponent!CTexCoords().value = vec4(246,64,2,2)*px;
432 base_tmpl.getComponent!CMaterialIndex().value = 2;
433 launcher.gui_manager.addTemplate(base_tmpl,"Particle");
434 // tmpl = gEntityManager.allocateTemplate(base_tmpl);
435 // tmpl.getComponent!CColor().value = 0xFF251010;
436 // launcher.gui_manager.addTemplate(tmpl,"Particle (blue)");
437 // tmpl = gEntityManager.allocateTemplate(base_tmpl);
438 // tmpl.getComponent!CColor().value = 0xFF102010;
439 // launcher.gui_manager.addTemplate(tmpl,"Particle (green)");
440 // tmpl = gEntityManager.allocateTemplate(base_tmpl);
441 // tmpl.getComponent!CColor().value = 0xFF101540;
442 // launcher.gui_manager.addTemplate(tmpl,"Particle (red)");
443 // tmpl = gEntityManager.allocateTemplate(tmpl, [becsID!CDamping].staticArray);
444 // launcher.gui_manager.addTemplate(tmpl,"Particle (damping)");
445 // tmpl = gEntityManager.allocateTemplate(tmpl);
446 // tmpl.getComponent!CDamping().power = 4;
447 // launcher.gui_manager.addTemplate(tmpl,"Particle (damping!)");
448 tmpl = gEntityManager.allocateTemplate([becsID!CAttractor, becsID!CLocation, becsID!CForceRange, becsID!CScale].staticArray);
449 tmpl.getComponent!CScale().value = vec2(4);
450 launcher.gui_manager.addTemplate(tmpl,"Attractor");
451 tmpl = gEntityManager.allocateTemplate(tmpl, [becsID!CVortex].staticArray);
452 launcher.gui_manager.addTemplate(tmpl,"Vortex");
453 // tmpl = gEntityManager.allocateTemplate(tmpl);
454 // tmpl.getComponent!CVortex().strength = -0.6;
455 // launcher.gui_manager.addTemplate(tmpl,"Vortex (reversed)");
456
457 }
458
459 void particlesEnd()
460 {
461 particles_demo.texture.destroy();
462
463 //gEntityManager.freeTemplate(simple.tmpl);
464 Mallocator.dispose(particles_demo);
465 }
466
467 void particlesEvent(SDL_Event* event)
468 {
469 }
470
471 bool particlesLoop()
472 {
473 launcher.render_position = (vec2(launcher.window_size.x,launcher.window_size.y)*launcher.scalling - vec2(400,300)) * 0.5;
474
475 gEntityManager.begin();
476 if(launcher.multithreading)
477 {
478 launcher.job_updater.begin();
479 gEntityManager.updateMT();
480 launcher.job_updater.call();
481 }
482 else
483 {
484 gEntityManager.update();
485 }
486 gEntityManager.end();
487
488 return true;
489 }
490
491 DemoCallbacks getParticlesDemo()
492 {
493 DemoCallbacks demo;
494 demo.register = &particlesRegister;
495 demo.initialize = &particlesStart;
496 demo.deinitialize = &particlesEnd;
497 demo.loop = &particlesLoop;
498 demo.tips = ParticlesDemo.tips;
499 return demo;
500 }