├── .gitignore ├── CMakeLists.txt ├── README.md ├── demo.c └── partikel.h /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | 75 | ### CUSTOM 76 | Debug 77 | Release 78 | CMakeLists.txt.user 79 | build/ 80 | 81 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | set (CMAKE_C_COMPILER clang) 3 | 4 | #Needed for old Qt Creator versions, so header files are shown in project tree 5 | #FILE(GLOB_RECURSE LibFiles "*.h") 6 | #add_custom_target(headers SOURCES ${LibFiles}) 7 | 8 | project(libpartikel LANGUAGES C) 9 | 10 | set (CMAKE_C_STANDARD 99) 11 | 12 | set (CMAKE_C_FLAGS_INIT "-Wall -std=c11") 13 | set (CMAKE_C_FLAGS_DEBUG "-g") 14 | set (CMAKE_C_FLAGS_RELEASE "-O2 -DNDEBUG") 15 | 16 | add_executable(demo "demo.c") 17 | target_link_libraries(demo raylib glfw m X11) 18 | 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libpartikel 2 | Libpartikel is a header only particle system written in C99 with and for [raylib](https://github.com/raysan5/raylib). It is currently in a very early stage (alpha) and thus: 3 | 4 | * There will be bugs. 5 | * The API will most certainly change. 6 | * Feature requests and bug tickets are welcome. 7 | 8 | **LICENSE: zlib/libpng** (see partikel.h) 9 | 10 | ## GIFs 11 | Some examples of particle systems made with libpartikel recorded as 60fps GIFs. 12 | 13 | Large files.. avoid to watch on mobile :) 14 | 15 | * [fountain](https://github.com/dbriemann/expo/blob/master/partikel-fountain.gif) 16 | * [swirl](https://github.com/dbriemann/expo/blob/master/partikel-swirl.gif) 17 | * [flame](https://github.com/dbriemann/expo/blob/master/partikel-flame.gif) 18 | 19 | ## Dependencies 20 | * [raylib](https://github.com/raysan5/raylib) 21 | 22 | ## Usage 23 | Just have raylib installed on your system and copy partikel.h to your project and include it. 24 | 25 | ## Run demo 26 | Note: the cmake is currently only configured for Linux. If you can help with Mac or Windows just submit a pull request. 27 | 28 | #### Linux 29 | 1. `git clone https://github.com/dbriemann/libpartikel.git` 30 | 2. `cd libpartikel && mkdir build && cd build` 31 | 3. `cmake .. -G"Unix Makefiles" -DCMAKE_BUILD_TYPE=Release` 32 | 4. `make` 33 | 5. `./demo` 34 | 35 | #### Windows 36 | You are on your own at the moment, sorry. 37 | 38 | #### Mac 39 | You are on your own at the moment, sorry. 40 | 41 | ## Documentation 42 | Currently the only documentation are the comments in the header file. Also demo.c can be used as inspiration. 43 | 44 | Real documentation will follow as soon as I'm sure that the library will not have further API changes. 45 | 46 | ## Plans for the future 47 | * Particle system editor built with raygui. 48 | -------------------------------------------------------------------------------- /demo.c: -------------------------------------------------------------------------------- 1 | /******************************************************************************************* 2 | * 3 | * libpartikel example - Show some simple particle systems / effects. 4 | * 5 | * This example has been created using 6 | * - raylib 1.8 (www.raylib.com) 7 | * - libpartikel (https://github.com/dbriemann/libpartikel) 8 | * 9 | * raylib is licensed under an unmodified zlib/libpng license (View raylib.h for details) 10 | * libpartikel is licensed under an unmodified zlib/libpng license (View partikel.h for details) 11 | * 12 | * Copyright (c) 2O19 David Linus Briemann (@Raging_Dave) 13 | * 14 | ********************************************************************************************/ 15 | 16 | #define LIBPARTIKEL_IMPLEMENTATION 17 | 18 | #include "partikel.h" 19 | #include "raylib.h" 20 | #include "stdio.h" 21 | 22 | // Global data. 23 | //---------------------------------------------------------------------------------- 24 | static int screenWidth = 1000; 25 | static int screenHeight = 800; 26 | static unsigned long counter = 0; 27 | static Camera2D camera; 28 | 29 | static Texture2D texCircle16; 30 | static Texture2D texCircle8; 31 | static Texture2D texCircle4; 32 | 33 | static int activePS = 1; 34 | 35 | static ParticleSystem * ps1 = NULL; 36 | static Emitter * emitterFountain1 = NULL; 37 | static Emitter * emitterFountain2 = NULL; 38 | static Emitter * emitterFountain3 = NULL; 39 | 40 | static ParticleSystem * ps2 = NULL; 41 | static Emitter * emitterSwirl1 = NULL; 42 | static Emitter * emitterSwirl2 = NULL; 43 | static Emitter * emitterSwirl3 = NULL; 44 | 45 | static ParticleSystem * ps3 = NULL; 46 | static Emitter * emitterFlame1 = NULL; 47 | static Emitter * emitterFlame2 = NULL; 48 | static Emitter * emitterFlame3 = NULL; 49 | 50 | // Define a custom particle deactivator function. 51 | bool Particle_DeactivatorFountain(Particle * p) { 52 | return (p->position.y > (camera.target.y + camera.offset.y) // bottom 53 | || p->position.x < (camera.target.x - camera.offset.x) // left 54 | || p->position.x > (camera.target.x + camera.offset.x) // right 55 | || Particle_DeactivatorAge(p)); 56 | } 57 | 58 | bool Particle_DeactivatorOutsideCam(Particle * p) { 59 | return (p->position.y < (camera.target.y - camera.offset.y) || Particle_DeactivatorAge(p)); 60 | } 61 | 62 | void OOMExit() { 63 | printf("OUT OF MEMORY.. BYE\n"); 64 | exit(0); 65 | } 66 | 67 | void InitFountain() { 68 | ps1 = ParticleSystem_New(); 69 | if (ps1 == NULL) { 70 | OOMExit(); 71 | } 72 | 73 | EmitterConfig ecfg1 = { 74 | .capacity = 600, 75 | .emissionRate = 200, 76 | .origin = (Vector2){.x = 0, .y = 0}, 77 | .originAcceleration = (FloatRange){.min = 0, .max = 0}, 78 | .direction = (Vector2){.x = 0, .y = -1}, // go up 79 | .directionAngle = (FloatRange){.min = -6, .max = 6}, // angle range -8 to +8 degree deviation from direction 80 | .velocityAngle = (FloatRange){.min = 0, .max = 0}, 81 | .velocity = (FloatRange){.min = 700, .max = 730}, 82 | .externalAcceleration = (Vector2){.x = 0, .y = 981}, 83 | .startColor = (Color){.r = 0, .g = 20, .b = 255, .a = 255}, 84 | .endColor = (Color){.r = 0, .g = 150, .b = 100, .a = 0}, 85 | .age = (FloatRange){.min = 1.0, .max = 3.0}, 86 | .texture = texCircle16, 87 | .blendMode = BLEND_ADDITIVE, 88 | 89 | .particle_Deactivator = Particle_DeactivatorFountain 90 | }; 91 | emitterFountain1 = Emitter_New(ecfg1); 92 | if (emitterFountain1 == NULL) { 93 | OOMExit(); 94 | } 95 | ParticleSystem_Register(ps1, emitterFountain1); 96 | 97 | ecfg1.directionAngle = (FloatRange){.min = -1.5, .max = 1.5}; 98 | ecfg1.velocity = (FloatRange){.min = 800, .max = 850}; 99 | ecfg1.texture = texCircle8; 100 | emitterFountain2 = Emitter_New(ecfg1); 101 | if (emitterFountain2 == NULL) { 102 | OOMExit(); 103 | } 104 | ParticleSystem_Register(ps1, emitterFountain2); 105 | 106 | ecfg1.capacity = 3000; 107 | ecfg1.emissionRate = 1000; 108 | ecfg1.directionAngle = (FloatRange){.min = -20, .max = 20}; 109 | ecfg1.velocity = (FloatRange){.min = 500, .max = 550}; 110 | ecfg1.texture = texCircle16; 111 | ecfg1.age = (FloatRange){.min = 0.0, .max = 3.0}; 112 | emitterFountain3 = Emitter_New(ecfg1); 113 | if (emitterFountain3 == NULL) { 114 | OOMExit(); 115 | } 116 | ParticleSystem_Register(ps1, emitterFountain3); 117 | 118 | ParticleSystem_Start(ps1); 119 | } 120 | 121 | void InitSwirl() { 122 | ps2 = ParticleSystem_New(); 123 | if (ps2 == NULL) { 124 | OOMExit(); 125 | } 126 | 127 | EmitterConfig ecfg = { 128 | .capacity = 2500, 129 | .emissionRate = 500, 130 | .origin = (Vector2){.x = 0, .y = 0}, 131 | .originAcceleration = (FloatRange){.min = 400, .max = 500}, 132 | .offset = (FloatRange){.min = 30, .max = 40}, 133 | .direction = (Vector2){.x = 0, .y = -1}, // go up 134 | .directionAngle = (FloatRange){.min = -180, .max = 180}, 135 | .velocityAngle = (FloatRange){.min = 90, .max = 90}, 136 | .velocity = (FloatRange){.min = 200, .max = 500}, 137 | .startColor = (Color){.r = 244, .g = 20, .b = 0, .a = 255}, 138 | .endColor = (Color){.r = 244, .g = 20, .b = 0, .a = 0}, 139 | .age = (FloatRange){.min = 2.5, .max = 5.0}, 140 | .texture = texCircle8, 141 | .blendMode = BLEND_ADDITIVE, 142 | 143 | .particle_Deactivator = Particle_DeactivatorOutsideCam 144 | }; 145 | 146 | emitterSwirl1 = Emitter_New(ecfg); 147 | if (emitterSwirl1 == NULL) { 148 | OOMExit(); 149 | } 150 | ParticleSystem_Register(ps2, emitterSwirl1); 151 | 152 | ecfg.capacity = 1000; 153 | ecfg.emissionRate = 200; 154 | ecfg.offset = (FloatRange){.min = 40, .max = 50}; 155 | ecfg.startColor = (Color){.r = 244, .g = 0, .b = 111, .a = 255}; 156 | ecfg.endColor = (Color){.r = 244, .g = 0, .b = 111, .a = 0}; 157 | 158 | emitterSwirl2 = Emitter_New(ecfg); 159 | if (emitterSwirl2 == NULL) { 160 | OOMExit(); 161 | } 162 | ParticleSystem_Register(ps2, emitterSwirl2); 163 | 164 | ecfg.capacity = 150; 165 | ecfg.emissionRate = 30; 166 | ecfg.offset = (FloatRange){.min = 20, .max = 30}; 167 | ecfg.velocity = (FloatRange){.min = 100, .max = 200}; 168 | ecfg.startColor = (Color){.r = 255, .g = 211, .b = 0, .a = 255}; 169 | ecfg.endColor = (Color){.r = 255, .g = 211, .b = 0, .a = 0}; 170 | 171 | emitterSwirl3 = Emitter_New(ecfg); 172 | if (emitterSwirl3 == NULL) { 173 | OOMExit(); 174 | } 175 | ParticleSystem_Register(ps2, emitterSwirl3); 176 | 177 | ParticleSystem_Start(ps2); 178 | } 179 | 180 | void InitFlame() { 181 | ps3 = ParticleSystem_New(); 182 | if (ps3 == NULL) { 183 | OOMExit(); 184 | } 185 | 186 | EmitterConfig ecfg = { 187 | .capacity = 1000, 188 | .emissionRate = 500, 189 | .origin = (Vector2){.x = 0, .y = 0}, 190 | .originAcceleration = (FloatRange){.min = 50, .max = 100}, 191 | .offset = (FloatRange){.min = 0, .max = 10}, 192 | .direction = (Vector2){.x = 0, .y = -1}, // go up 193 | .directionAngle = (FloatRange){.min = -90, .max = -90}, 194 | .velocityAngle = (FloatRange){.min = 90, .max = 90}, 195 | .velocity = (FloatRange){.min = 30, .max = 150}, 196 | .startColor = (Color){.r = 255, .g = 20, .b = 0, .a = 255}, 197 | .endColor = (Color){.r = 255, .g = 20, .b = 0, .a = 0}, 198 | .age = (FloatRange){.min = 1.0, .max = 2.0}, 199 | .texture = texCircle16, 200 | .blendMode = BLEND_ADDITIVE, 201 | 202 | .particle_Deactivator = Particle_DeactivatorFountain 203 | }; 204 | 205 | emitterFlame1 = Emitter_New(ecfg); 206 | if (emitterFlame1 == NULL) { 207 | OOMExit(); 208 | } 209 | ParticleSystem_Register(ps3, emitterFlame1); 210 | 211 | ecfg.capacity = 20; 212 | ecfg.emissionRate = 20; 213 | ecfg.startColor = (Color){.r = 255, .g = 255, .b = 255, .a = 255}; 214 | ecfg.endColor = (Color){.r = 255, .g = 255, .b = 255, .a = 0}; 215 | ecfg.age = (FloatRange){.min = 0.5, .max = 1.0}; 216 | 217 | emitterFlame2 = Emitter_New(ecfg); 218 | if (emitterFlame2 == NULL) { 219 | OOMExit(); 220 | } 221 | ParticleSystem_Register(ps3, emitterFlame2); 222 | 223 | ecfg.capacity = 500; 224 | ecfg.emissionRate = 100; 225 | ecfg.directionAngle = (FloatRange){.min = -3, .max = 3}; 226 | ecfg.velocityAngle = (FloatRange){.min = 0, .max = 0}; 227 | ecfg.originAcceleration = (FloatRange){.min = 0, .max = 0}; 228 | ecfg.startColor = (Color){.r = 125, .g = 125, .b = 125, .a = 30}; 229 | ecfg.endColor = (Color){.r = 125, .g = 125, .b = 125, .a = 10}; 230 | ecfg.age = (FloatRange){.min = 3.0, .max = 5.0}; 231 | 232 | emitterFlame3 = Emitter_New(ecfg); 233 | if (emitterFlame3 == NULL) { 234 | OOMExit(); 235 | } 236 | ParticleSystem_Register(ps3, emitterFlame3); 237 | 238 | ParticleSystem_Start(ps3); 239 | } 240 | 241 | void DestroyFountain() { 242 | Emitter_Free(emitterFountain1); 243 | Emitter_Free(emitterFountain2); 244 | Emitter_Free(emitterFountain3); 245 | 246 | ParticleSystem_Free(ps1); 247 | } 248 | 249 | void DestroySwirl() { 250 | Emitter_Free(emitterSwirl1); 251 | Emitter_Free(emitterSwirl2); 252 | Emitter_Free(emitterSwirl3); 253 | 254 | ParticleSystem_Free(ps2); 255 | } 256 | 257 | void DestroyFlame() { 258 | Emitter_Free(emitterFlame1); 259 | Emitter_Free(emitterFlame2); 260 | Emitter_Free(emitterFlame3); 261 | 262 | ParticleSystem_Free(ps3); 263 | } 264 | 265 | // Init sets up all relevant data. 266 | void Init() { 267 | InitWindow(screenWidth, screenHeight, "libpartikel demo"); 268 | SetTargetFPS(121); 269 | HideCursor(); 270 | 271 | camera.target = (Vector2){.x = 0, .y = 0}; 272 | camera.offset = (Vector2){.x = (float)screenWidth / 2, .y = (float)screenHeight / 2}; 273 | camera.rotation = 0.0f; 274 | camera.zoom = 1.0f; 275 | 276 | // Generate some simple textures. 277 | Image imgCircle16 = GenImageGradientRadial(16, 16, 0.3f, WHITE, BLACK); 278 | texCircle16 = LoadTextureFromImage(imgCircle16); 279 | Image imgCircle8 = GenImageGradientRadial(8, 8, 0.5f, WHITE, BLACK); 280 | texCircle8 = LoadTextureFromImage(imgCircle8); 281 | Image imgCircle4 = GenImageGradientRadial(4, 4, 0.5f, WHITE, BLACK); 282 | texCircle4 = LoadTextureFromImage(imgCircle4); 283 | 284 | UnloadImage(imgCircle4); 285 | UnloadImage(imgCircle8); 286 | UnloadImage(imgCircle16); 287 | 288 | InitFountain(); 289 | InitSwirl(); 290 | InitFlame(); 291 | } 292 | 293 | // Destroy and free all the global data. 294 | void Destroy() { 295 | DestroyFountain(); 296 | DestroySwirl(); 297 | DestroyFlame(); 298 | 299 | UnloadTexture(texCircle4); 300 | UnloadTexture(texCircle8); 301 | UnloadTexture(texCircle16); 302 | 303 | // Close window and OpenGL context 304 | CloseWindow(); 305 | } 306 | 307 | void Update(double dt) { 308 | Vector2 m = GetMousePosition(); 309 | m.x -= (float)screenWidth / 2; 310 | m.y -= (float)screenHeight / 2; 311 | counter = 0; 312 | 313 | bool trigger = false; 314 | 315 | int key = GetKeyPressed(); 316 | switch (key) { 317 | case KEY_ONE: 318 | activePS = 1; 319 | break; 320 | case KEY_TWO: 321 | activePS = 2; 322 | break; 323 | case KEY_THREE: 324 | activePS = 3; 325 | break; 326 | case KEY_SPACE: 327 | trigger = true; 328 | break; 329 | default: 330 | break; 331 | } 332 | 333 | switch (activePS) { 334 | case 1: 335 | ParticleSystem_SetOrigin(ps1, (Vector2){.x = m.x, .y = m.y}); 336 | counter += ParticleSystem_Update(ps1, (float)dt); 337 | break; 338 | case 2: 339 | ParticleSystem_SetOrigin(ps2, (Vector2){.x = m.x, .y = m.y}); 340 | counter += ParticleSystem_Update(ps2, (float)dt); 341 | break; 342 | case 3: 343 | ParticleSystem_SetOrigin(ps3, (Vector2){.x = m.x, .y = m.y}); 344 | counter += ParticleSystem_Update(ps3, (float)dt); 345 | break; 346 | default: 347 | break; 348 | } 349 | } 350 | 351 | void Draw() { 352 | // Draw 353 | //---------------------------------------------------------------------------------- 354 | BeginDrawing(); 355 | 356 | ClearBackground(BLACK); 357 | 358 | BeginMode2D(camera); 359 | 360 | // Draw scene here. 361 | switch (activePS) { 362 | case 1: 363 | ParticleSystem_Draw(ps1); 364 | break; 365 | case 2: 366 | ParticleSystem_Draw(ps2); 367 | break; 368 | case 3: 369 | ParticleSystem_Draw(ps3); 370 | break; 371 | default: 372 | break; 373 | } 374 | 375 | EndMode2D(); 376 | 377 | // Draw HUD etc. here. 378 | DrawFPS(10, 10); 379 | char pcount[40]; 380 | sprintf(pcount, "Particles: %lu", counter); 381 | DrawText(pcount, 10, 40, 20, DARKGREEN); 382 | sprintf(pcount, "Press number keys to switch demo."); 383 | DrawText(pcount, 580, 10, 20, DARKGREEN); 384 | sprintf(pcount, "1: Fountain / 2: Swirl / 3: Flame"); 385 | DrawText(pcount, 580, 40, 20, DARKGREEN); 386 | 387 | EndDrawing(); 388 | } 389 | 390 | int main(int argc, char * argv[argc + 1]) { 391 | 392 | (void)argv[0]; 393 | 394 | // Initialization 395 | //---------------------------------------------------------------------------------- 396 | Init(); 397 | 398 | // Main game loop 399 | //---------------------------------------------------------------------------------- 400 | while (!WindowShouldClose()) // Detect window close button or ESC key 401 | { 402 | double dt = (double)GetFrameTime(); 403 | Update(dt); 404 | 405 | Draw(); 406 | } 407 | 408 | // De-Initialization 409 | //---------------------------------------------------------------------------------- 410 | Destroy(); 411 | 412 | return 0; 413 | } 414 | -------------------------------------------------------------------------------- /partikel.h: -------------------------------------------------------------------------------- 1 | /********************************************************************************************** 2 | * 3 | * libpartikel v0.0.3 ALPHA 4 | * [https://github.com/dbriemann/libpartikel] 5 | * 6 | * 7 | * A simple particle system built with and for raylib, to be used as header 8 | *only library. 9 | * 10 | * 11 | * FEATURES: 12 | * - Supports all platforms that raylib supports 13 | * 14 | * DEPENDENCIES: 15 | * raylib >= v2.5.0 and all of its dependencies 16 | * 17 | * CONFIGURATION: 18 | * #define LIBPARTIKEL_IMPLEMENTATION 19 | * Generates the implementation of the library into the included file. 20 | * If not defined, the library is in header only mode and can be included 21 | *in other headers or source files without problems. But only ONE file should 22 | *hold the implementation. 23 | * 24 | * LICENSE: zlib/libpng 25 | * 26 | * libpartikel is licensed under an unmodified zlib/libpng license, which is 27 | *an OSI-certified, BSD-like license that allows static linking with closed 28 | *source software: 29 | * 30 | * Copyright (c) 2017 David Linus Briemann (@Raging_Dave) 31 | * 32 | * This software is provided "as-is", without any express or implied warranty. 33 | *In no event will the authors be held liable for any damages arising from the 34 | *use of this software. 35 | * 36 | * Permission is granted to anyone to use this software for any purpose, 37 | *including commercial applications, and to alter it and redistribute it freely, 38 | *subject to the following restrictions: 39 | * 40 | * 1. The origin of this software must not be misrepresented; you must not 41 | *claim that you wrote the original software. If you use this software in a 42 | *product, an acknowledgment in the product documentation would be appreciated 43 | *but is not required. 44 | * 45 | * 2. Altered source versions must be plainly marked as such, and must not 46 | *be misrepresented as being the original software. 47 | * 48 | * 3. This notice may not be removed or altered from any source 49 | *distribution. 50 | * 51 | **********************************************************************************************/ 52 | 53 | #pragma once 54 | 55 | #include "raylib.h" 56 | 57 | /** TODOs 58 | * 59 | * 0) MAYBE switch to purely function pointer based system.. handle Init, 60 | * Update, Draw etc. all as function pointers and make it possible to use a 61 | * custom one. See current handling of Particle deactivation functions as 62 | * example. 63 | * 64 | */ 65 | 66 | // ----------------------------------------------------------------------- 67 | // UNCOMMENT THE FOLLOWING LINE FOR DEVELOPMENT OF THIS HEADER FILE ONLY. 68 | // If you don't most tools, such as lsp, static analysis, etc. might not work. 69 | #define LIBPARTIKEL_IMPLEMENTATION 70 | // ----------------------------------------------------------------------- 71 | 72 | // Allow custom memory allocators. 73 | #ifndef PARTIKEL_ALLOC 74 | #define PARTIKEL_ALLOC(n,sz) calloc(n,sz) 75 | #endif 76 | #ifndef PARTIKEL_FREE 77 | #define PARTIKEL_FREE(p) free(p) 78 | #endif 79 | 80 | // Needed forward declarations. 81 | //---------------------------------------------------------------------------------- 82 | typedef struct Particle Particle; 83 | typedef struct EmitterConfig EmitterConfig; 84 | typedef struct Emitter Emitter; 85 | typedef struct ParticleSystem ParticleSystem; 86 | 87 | // Function signatures (comments are found in implementation below) 88 | //---------------------------------------------------------------------------------- 89 | float GetRandomFloat(float min, float max); 90 | Vector2 NormalizeV2(Vector2 v); 91 | Vector2 RotateV2(Vector2 v, float degrees); 92 | Color LinearFade(Color c1, Color c2, float fraction); 93 | 94 | bool Particle_DeactivatorAge(Particle *p); 95 | Particle *Particle_New(bool (*deactivatorFunc)(struct Particle *)); 96 | void Particle_Free(Particle *p); 97 | void Particle_Init(Particle *p, EmitterConfig *cfg); 98 | void Particle_Update(Particle *p, float dt); 99 | 100 | Emitter *Emitter_New(EmitterConfig cfg); 101 | bool Emitter_Reinit(Emitter *e, EmitterConfig cfg); 102 | void Emitter_Start(Emitter *e); 103 | void Emitter_Stop(Emitter *e); 104 | void Emitter_Free(Emitter *e); 105 | void Emitter_Burst(Emitter *e); 106 | unsigned long Emitter_Update(Emitter *e, float dt); 107 | void Emitter_Draw(Emitter *e); 108 | 109 | ParticleSystem *ParticleSystem_New(void); 110 | bool ParticleSystem_Register(ParticleSystem *ps, Emitter *emitter); 111 | bool ParticleSystem_Deregister(ParticleSystem *ps, Emitter *emitter); 112 | void ParticleSystem_SetOrigin(ParticleSystem *ps, Vector2 origin); 113 | void ParticleSystem_Start(ParticleSystem *ps); 114 | void ParticleSystem_Stop(ParticleSystem *ps); 115 | void ParticleSystem_Burst(ParticleSystem *ps); 116 | void ParticleSystem_Draw(ParticleSystem *ps); 117 | unsigned long ParticleSystem_Update(ParticleSystem *ps, float dt); 118 | void ParticleSystem_Free(ParticleSystem *p); 119 | 120 | #ifdef LIBPARTIKEL_IMPLEMENTATION 121 | 122 | #include "math.h" 123 | #include "stdlib.h" 124 | 125 | // Utility functions & structs. 126 | //---------------------------------------------------------------------------------- 127 | 128 | // GetRandomFloat returns a random float between 0.0 and 1.0. 129 | float GetRandomFloat(float min, float max) { 130 | float range = max - min; 131 | float n = (float)GetRandomValue(0, RAND_MAX) / (float)RAND_MAX; 132 | return n * range + min; 133 | } 134 | 135 | // NormalizeV2 normalizes a 2d Vector and returns its unit vector. 136 | Vector2 NormalizeV2(Vector2 v) { 137 | if (v.x == 0 && v.y == 0) { 138 | return v; 139 | } 140 | float len = sqrt(v.x * v.x + v.y * v.y); 141 | return (Vector2){.x = v.x / len, .y = v.y / len}; 142 | } 143 | 144 | Vector2 RotateV2(Vector2 v, float degrees) { 145 | float rad = degrees * DEG2RAD; 146 | Vector2 res = {.x = cos(rad) * v.x - sin(rad) * v.y, 147 | .y = sin(rad) * v.x + cos(rad) * v.y}; 148 | return res; 149 | } 150 | 151 | // LinearFade fades from Color c1 to Color c2. Fraction is a value between 0 152 | // and 1. The interpolation is linear. 153 | Color LinearFade(Color c1, Color c2, float fraction) { 154 | unsigned char newr = 155 | (unsigned char)((float)((int)c2.r - (int)c1.r) * fraction + (float)c1.r); 156 | unsigned char newg = 157 | (unsigned char)((float)((int)c2.g - (int)c1.g) * fraction + (float)c1.g); 158 | unsigned char newb = 159 | (unsigned char)((float)((int)c2.b - (int)c1.b) * fraction + (float)c1.b); 160 | unsigned char newa = 161 | (unsigned char)((float)((int)c2.a - (int)c1.a) * fraction + (float)c1.a); 162 | 163 | Color c = {.r = newr, .g = newg, .b = newb, .a = newa}; 164 | 165 | return c; 166 | } 167 | 168 | // Min/Max pair structs for various types. 169 | typedef struct FloatRange { 170 | float min; 171 | float max; 172 | } FloatRange; 173 | 174 | typedef struct IntRange { 175 | int min; 176 | int max; 177 | } IntRange; 178 | 179 | // EmitterConfig type. 180 | //---------------------------------------------------------------------------------- 181 | struct EmitterConfig { 182 | Vector2 direction; // Direction vector will be normalized. 183 | FloatRange velocity; // The possible range of the particle velocities. 184 | // Velocity is a scalar defining the length of the 185 | // direction vector. 186 | FloatRange directionAngle; // The angle range modiying the direction vector. 187 | FloatRange velocityAngle; // The angle range to rotate the velocity vector. 188 | FloatRange 189 | offset; // The min and max offset multiplier for the particle origin. 190 | FloatRange originAcceleration; // An acceleration towards or from 191 | // (centrifugal) the origin. 192 | IntRange burst; // The range of sudden particle bursts. 193 | size_t capacity; // Maximum amounts of particles in the system. 194 | size_t emissionRate; // Rate of emitted particles per second. 195 | Vector2 origin; // Origin is the source of the emitter. 196 | Vector2 externalAcceleration; // External constant acceleration. e.g. gravity. 197 | Color startColor; // The color the particle starts with when it spawns. 198 | Color endColor; // The color the particle ends with when it disappears. 199 | FloatRange age; // Age range of particles in seconds. 200 | BlendMode blendMode; // Color blending mode for all particles of this Emitter. 201 | Texture2D texture; // The texture used as particle texture. 202 | 203 | bool (*particle_Deactivator)( 204 | struct Particle *); // Pointer to a function that determines when 205 | // a particle is deactivated. 206 | }; 207 | 208 | // Particle type. 209 | //---------------------------------------------------------------------------------- 210 | 211 | // Particle describes one particle in a particle system. 212 | struct Particle { 213 | Vector2 origin; // The origin of the particle (never changes). 214 | Vector2 position; // Position of the particle in 2d space. 215 | Vector2 velocity; // Velocity vector in 2d space. 216 | Vector2 externalAcceleration; // Acceleration vector in 2d space. 217 | float originAcceleration; // Accelerates velocity vector 218 | float age; // Age is measured in seconds. 219 | float ttl; // Ttl is the time to live in seconds. 220 | bool active; // Inactive particles are neither updated nor drawn. 221 | 222 | bool (*particle_Deactivator)( 223 | struct Particle *); // Pointer to a function that determines 224 | // when a particle is deactivated. 225 | }; 226 | 227 | // Particle_DeactivatorAge is the default deactivator function that 228 | // disables particles only if their age exceeds their time to live. 229 | bool Particle_DeactivatorAge(Particle *p) { return p->age > p->ttl; } 230 | 231 | // Particle_new creates a new Particle object. 232 | // The deactivator function may be omitted by passing NULL. 233 | Particle *Particle_New(bool (*deactivatorFunc)(struct Particle *)) { 234 | Particle *p = PARTIKEL_ALLOC(1, sizeof(Particle)); 235 | if (p == NULL) { 236 | return NULL; 237 | } 238 | *p = (Particle){.position = (Vector2){.x = 0, .y = 0}, 239 | .velocity = (Vector2){.x = 0, .y = 0}, 240 | .externalAcceleration = (Vector2){.x = 0, .y = 0}, 241 | .originAcceleration = 0, 242 | .age = 0, 243 | .ttl = 0, 244 | .active = false, 245 | 246 | .particle_Deactivator = Particle_DeactivatorAge}; 247 | if (deactivatorFunc != NULL) { 248 | p->particle_Deactivator = deactivatorFunc; 249 | } 250 | 251 | return p; 252 | } 253 | 254 | // Particle_free frees all memory used by the Particle. 255 | void Particle_Free(Particle *p) { PARTIKEL_FREE(p); } 256 | 257 | // Particle_Init inits a particle. It is then ready to be updated and drawn. 258 | void Particle_Init(Particle *p, EmitterConfig *cfg) { 259 | p->age = 0; 260 | p->origin = cfg->origin; 261 | 262 | // Get a random angle to find an random velocity. 263 | float randa = 264 | GetRandomFloat(cfg->directionAngle.min, cfg->directionAngle.max); 265 | 266 | // Rotate base direction with the given angle. 267 | Vector2 res = RotateV2(cfg->direction, randa); 268 | 269 | // Get a random value for velocity range (direction is normalized). 270 | float randv = GetRandomFloat(cfg->velocity.min, cfg->velocity.max); 271 | 272 | // Multiply direction with factor to set actual velocity in the Particle. 273 | p->velocity = (Vector2){.x = res.x * randv, .y = res.y * randv}; 274 | 275 | // Get a random angle to rotate the velocity vector. 276 | randa = GetRandomFloat(cfg->velocityAngle.min, cfg->velocityAngle.max); 277 | 278 | // Rotate velocity vector with given angle. 279 | p->velocity = RotateV2(p->velocity, randa); 280 | 281 | // Get a random value for origin offset and apply it to position. 282 | float rando = GetRandomFloat(cfg->offset.min, cfg->offset.max); 283 | p->position.x = cfg->origin.x + res.x * rando; 284 | p->position.y = cfg->origin.y + res.y * rando; 285 | 286 | // Get a random value for the intrinsic particle acceleration 287 | float rands = 288 | GetRandomFloat(cfg->originAcceleration.min, cfg->originAcceleration.max); 289 | p->originAcceleration = rands; 290 | p->externalAcceleration = cfg->externalAcceleration; 291 | p->ttl = GetRandomFloat(cfg->age.min, cfg->age.max); 292 | p->active = true; 293 | } 294 | 295 | // Particle_update updates all properties according to the delta time (in 296 | // seconds). Deactivates the particle if the deactivator function returns true. 297 | void Particle_Update(Particle *p, float dt) { 298 | if (!p->active) { 299 | return; 300 | } 301 | 302 | p->age += dt; 303 | 304 | if (p->particle_Deactivator(p)) { 305 | p->active = false; 306 | return; 307 | } 308 | 309 | Vector2 toOrigin = NormalizeV2((Vector2){.x = p->origin.x - p->position.x, 310 | .y = p->origin.y - p->position.y}); 311 | 312 | // Update velocity by internal acceleration. 313 | p->velocity.x += toOrigin.x * p->originAcceleration * dt; 314 | p->velocity.y += toOrigin.y * p->originAcceleration * dt; 315 | 316 | // Update velocity by external acceleration. 317 | p->velocity.x += p->externalAcceleration.x * dt; 318 | p->velocity.y += p->externalAcceleration.y * dt; 319 | 320 | // Update position by velocity. 321 | p->position.x += p->velocity.x * dt; 322 | p->position.y += p->velocity.y * dt; 323 | } 324 | 325 | // Emitter type. 326 | //---------------------------------------------------------------------------------- 327 | 328 | // Emitter is a single (point) source emitting many particles. 329 | struct Emitter { 330 | EmitterConfig config; 331 | float mustEmit; // Amount of particles to be emitted within next update call. 332 | Vector2 offset; // Offset holds half the width and height of the texture. 333 | bool isEmitting; 334 | Particle **particles; // Array of all particles (by pointer). 335 | }; 336 | 337 | // Emitter_New creates a new Emitter object. 338 | Emitter *Emitter_New(EmitterConfig cfg) { 339 | Emitter *e = PARTIKEL_ALLOC(1, sizeof(Emitter)); 340 | if (e == NULL) { 341 | return NULL; 342 | } 343 | e->config = cfg; 344 | e->offset.x = e->config.texture.width / 2; 345 | e->offset.y = e->config.texture.height / 2; 346 | e->particles = PARTIKEL_ALLOC(e->config.capacity, sizeof(Particle *)); 347 | if (e->particles == NULL) { 348 | PARTIKEL_FREE(e); 349 | return NULL; 350 | } 351 | e->mustEmit = 0; 352 | // Normalize direction for future uses. 353 | e->config.direction = NormalizeV2(e->config.direction); 354 | 355 | for (size_t i = 0; i < e->config.capacity; i++) { 356 | e->particles[i] = Particle_New(e->config.particle_Deactivator); 357 | } 358 | 359 | return e; 360 | } 361 | 362 | // Emitter_Reinit reinits the given Emitter with a new EmitterConfig. 363 | bool Emitter_Reinit(Emitter *e, EmitterConfig cfg) { 364 | if (cfg.capacity > e->config.capacity) { 365 | // Array needs to be grown to the new size. 366 | Particle **newParticles = 367 | realloc(e->particles, cfg.capacity * sizeof(Particle *)); 368 | if (newParticles == NULL) { 369 | return false; 370 | } 371 | e->particles = newParticles; 372 | 373 | // Create new Particles 374 | for (size_t i = e->config.capacity; i < cfg.capacity; i++) { 375 | e->particles[i] = Particle_New(cfg.particle_Deactivator); 376 | } 377 | } else if (cfg.capacity < e->config.capacity) { 378 | // First we free the now obsolete Particles. 379 | for (size_t i = cfg.capacity; i < e->config.capacity; i++) { 380 | Particle_Free(e->particles[i]); 381 | } 382 | 383 | // Array needs to be shrunk to the new size. 384 | Particle **newParticles = 385 | realloc(e->particles, cfg.capacity * sizeof(Particle *)); 386 | if (newParticles == NULL) { 387 | return false; 388 | } 389 | e->particles = newParticles; 390 | } 391 | 392 | // Set new config. 393 | e->config = cfg; 394 | 395 | // Set new Particle deactivator function for all Particles. 396 | for (size_t i = 0; i < e->config.capacity; i++) { 397 | e->particles[i]->particle_Deactivator = e->config.particle_Deactivator; 398 | } 399 | 400 | return true; 401 | } 402 | 403 | // Emitter_Start activates Particle emission. 404 | void Emitter_Start(Emitter *e) { e->isEmitting = true; } 405 | 406 | // Emitter_Start deactivates Particle emission. 407 | void Emitter_Stop(Emitter *e) { e->isEmitting = false; } 408 | 409 | // Emitter_Free frees all allocated resources. 410 | void Emitter_Free(Emitter *e) { 411 | for (size_t i = 0; i < e->config.capacity; i++) { 412 | Particle_Free(e->particles[i]); 413 | } 414 | free(e->particles); 415 | PARTIKEL_FREE(e); 416 | } 417 | 418 | // Emitter_Burst emits a specified amount of particles at once, 419 | // ignoring the state of e->isEmitting. Use this for singular events 420 | // instead of continuous output. 421 | void Emitter_Burst(Emitter *e) { 422 | Particle *p = NULL; 423 | size_t emitted = 0; 424 | 425 | int amount = GetRandomValue(e->config.burst.min, e->config.burst.max); 426 | 427 | for (size_t i = 0; i < e->config.capacity; i++) { 428 | p = e->particles[i]; 429 | if (!p->active) { 430 | Particle_Init(p, &e->config); 431 | p->position = e->config.origin; 432 | emitted++; 433 | } 434 | if (emitted >= amount) { 435 | return; 436 | } 437 | } 438 | } 439 | 440 | // Emitter_Update updates all particles and returns 441 | // the current amount of active particles. 442 | unsigned long Emitter_Update(Emitter *e, float dt) { 443 | size_t emitNow = 0; 444 | Particle *p = NULL; 445 | unsigned long counter = 0; 446 | 447 | if (e->isEmitting) { 448 | e->mustEmit += dt * (float)e->config.emissionRate; 449 | emitNow = (size_t)e->mustEmit; // floor 450 | } 451 | 452 | for (size_t i = 0; i < e->config.capacity; i++) { 453 | p = e->particles[i]; 454 | if (p->active) { 455 | Particle_Update(p, dt); 456 | counter++; 457 | } else if (e->isEmitting && emitNow > 0) { 458 | // emit new particles here 459 | Particle_Init(p, &e->config); 460 | Particle_Update(p, dt); 461 | emitNow--; 462 | e->mustEmit--; 463 | counter++; 464 | } 465 | } 466 | 467 | return counter; 468 | } 469 | 470 | // Emitter_Draw draws all active particles. 471 | void Emitter_Draw(Emitter *e) { 472 | BeginBlendMode(e->config.blendMode); 473 | for (size_t i = 0; i < e->config.capacity; i++) { 474 | Particle *p = e->particles[i]; 475 | if (p->active) { 476 | DrawTexture(e->config.texture, e->particles[i]->position.x - e->offset.x, 477 | e->particles[i]->position.y - e->offset.y, 478 | LinearFade(e->config.startColor, e->config.endColor, 479 | p->age / p->ttl)); 480 | } 481 | } 482 | EndBlendMode(); 483 | } 484 | 485 | // ParticleSystem type. 486 | //---------------------------------------------------------------------------------- 487 | 488 | // ParticleSystem is a set of emitters grouped logically 489 | // together to achieve a specific visual effect. 490 | // While Emitters can be used independently, ParticleSystem 491 | // offers some convenience for handling many Emitters at once. 492 | struct ParticleSystem { 493 | bool active; 494 | size_t length; 495 | size_t capacity; 496 | Vector2 origin; 497 | Emitter **emitters; 498 | }; 499 | 500 | // Particlesystem_New creates a new particle system 501 | // with the given amount of emitters. 502 | ParticleSystem *ParticleSystem_New(void) { 503 | ParticleSystem *ps = PARTIKEL_ALLOC(1, sizeof(ParticleSystem)); 504 | if (ps == NULL) { 505 | return NULL; 506 | } 507 | ps->active = false; 508 | ps->length = 0; 509 | ps->capacity = 1; 510 | ps->origin = (Vector2){.x = 0, .y = 0}; 511 | ps->emitters = PARTIKEL_ALLOC(ps->capacity, sizeof(Emitter *)); 512 | if (ps->emitters == NULL) { 513 | PARTIKEL_FREE(ps); 514 | return NULL; 515 | } 516 | return ps; 517 | } 518 | 519 | // ParticleSystem_Register registers an emitter to the system. 520 | // The emitter will be controlled by all particle system functions. 521 | // Returns true on success and false otherwise. 522 | bool ParticleSystem_Register(ParticleSystem *ps, Emitter *emitter) { 523 | // If there is no space for another emitter we have to realloc. 524 | if (ps->length >= ps->capacity) { 525 | // Double capacity. 526 | Emitter **newEmitters = 527 | realloc(ps->emitters, 2 * ps->capacity * sizeof(Emitter *)); 528 | if (newEmitters == NULL) { 529 | return false; 530 | } 531 | ps->emitters = newEmitters; 532 | ps->capacity *= 2; 533 | } 534 | 535 | // Now the new Emitter can be registered. 536 | ps->emitters[ps->length] = emitter; 537 | ps->length++; 538 | 539 | return true; 540 | } 541 | 542 | // ParticleSystem_Deregister deregisters an Emitter by its pointer. 543 | // Returns true on success and false otherwise. 544 | bool ParticleSystem_Deregister(ParticleSystem *ps, Emitter *emitter) { 545 | for (size_t i = 0; i < ps->length; i++) { 546 | if (ps->emitters[i] == emitter) { 547 | // Remove this emitter by replacing its pointer with the 548 | // last pointer, if it is not the only Emitter. 549 | if (i != ps->length - 1) { 550 | ps->emitters[i] = ps->emitters[ps->length - 1]; 551 | } 552 | // Then NULL the last emitter. It is either a duplicate or 553 | // the removed one. 554 | ps->length--; 555 | ps->emitters[ps->length] = NULL; 556 | 557 | return true; 558 | } 559 | } 560 | // Emitter not found. 561 | return false; 562 | } 563 | 564 | // ParticleSystem_SetOrigin sets the origin for all registered Emitters. 565 | void ParticleSystem_SetOrigin(ParticleSystem *ps, Vector2 origin) { 566 | ps->origin = origin; 567 | for (size_t i = 0; i < ps->length; i++) { 568 | ps->emitters[i]->config.origin = origin; 569 | } 570 | } 571 | 572 | // ParticleSystem_Start runs Emitter_Start on all registered Emitters. 573 | void ParticleSystem_Start(ParticleSystem *ps) { 574 | for (size_t i = 0; i < ps->length; i++) { 575 | Emitter_Start(ps->emitters[i]); 576 | } 577 | } 578 | 579 | // ParticleSystem_Stop runs Emitter_Stop on all registered Emitters. 580 | void ParticleSystem_Stop(ParticleSystem *ps) { 581 | for (size_t i = 0; i < ps->length; i++) { 582 | Emitter_Stop(ps->emitters[i]); 583 | } 584 | } 585 | 586 | // ParticleSystem_Burst runs Emitter_Burst on all registered Emitters. 587 | void ParticleSystem_Burst(ParticleSystem *ps) { 588 | for (size_t i = 0; i < ps->length; i++) { 589 | Emitter_Burst(ps->emitters[i]); 590 | } 591 | } 592 | 593 | // ParticleSystem_Draw runs Emitter_Draw on all registered Emitters. 594 | void ParticleSystem_Draw(ParticleSystem *ps) { 595 | for (size_t i = 0; i < ps->length; i++) { 596 | Emitter_Draw(ps->emitters[i]); 597 | } 598 | } 599 | 600 | // ParticleSystem_Update runs Emitter_Update on all registered Emitters. 601 | unsigned long ParticleSystem_Update(ParticleSystem *ps, float dt) { 602 | size_t counter = 0; 603 | for (size_t i = 0; i < ps->length; i++) { 604 | counter += Emitter_Update(ps->emitters[i], dt); 605 | } 606 | return counter; 607 | } 608 | 609 | // ParticleSystem_Free only frees its own resources. 610 | // The emitters referenced here must be freed on their own. 611 | void ParticleSystem_Free(ParticleSystem *p) { 612 | PARTIKEL_FREE(p->emitters); 613 | PARTIKEL_FREE(p); 614 | } 615 | 616 | #endif // LIBPARTIKEL_IMPLEMENTATION 617 | --------------------------------------------------------------------------------