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 }