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 }