shaders_game_of_life.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. /*******************************************************************************************
  2. *
  3. * raylib [shaders] example - game of life
  4. *
  5. * Example complexity rating: [★★★☆] 3/4
  6. *
  7. * NOTE: This example requires raylib OpenGL 3.3 or ES2 versions for shaders support,
  8. * OpenGL 1.1 does not support shaders, recompile raylib to OpenGL 3.3 version
  9. *
  10. * Example originally created with raylib 5.6, last time updated with raylib 5.6
  11. *
  12. * Example contributed by Jordi Santonja (@JordSant) and reviewed by Ramon Santamaria (@raysan5)
  13. *
  14. * Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
  15. * BSD-like license that allows static linking with closed source software
  16. *
  17. * Copyright (c) 2025 Jordi Santonja (@JordSant)
  18. *
  19. ********************************************************************************************/
  20. #include "raylib.h"
  21. #define RAYGUI_IMPLEMENTATION
  22. #include "raygui.h" // Required for GUI controls
  23. #if defined(PLATFORM_DESKTOP)
  24. #define GLSL_VERSION 330
  25. #else // PLATFORM_ANDROID, PLATFORM_WEB
  26. #define GLSL_VERSION 100
  27. #endif
  28. //----------------------------------------------------------------------------------
  29. // Types and Structures Definition
  30. //----------------------------------------------------------------------------------
  31. // Interaction mode
  32. typedef enum {
  33. MODE_RUN = 0,
  34. MODE_PAUSE,
  35. MODE_DRAW,
  36. } InteractionMode;
  37. // Struct to store example preset patterns
  38. typedef struct {
  39. char *name;
  40. Vector2 position;
  41. } PresetPattern;
  42. //----------------------------------------------------------------------------------
  43. // Functions declaration
  44. //----------------------------------------------------------------------------------
  45. void FreeImageToDraw(Image **imageToDraw);
  46. //------------------------------------------------------------------------------------
  47. // Program main entry point
  48. //------------------------------------------------------------------------------------
  49. int main(void)
  50. {
  51. // Initialization
  52. //--------------------------------------------------------------------------------------
  53. const int screenWidth = 800;
  54. const int screenHeight = 450;
  55. InitWindow(screenWidth, screenHeight, "raylib [shaders] example - game of life");
  56. const int menuWidth = 100;
  57. const int windowWidth = screenWidth - menuWidth;
  58. const int windowHeight = screenHeight;
  59. const int worldWidth = 2048;
  60. const int worldHeight = 2048;
  61. const int randomTiles = 8; // Random preset: divide the world to compute random points in each tile
  62. const Rectangle worldRectSource = { 0, 0, (float)worldWidth, (float)-worldHeight };
  63. const Rectangle worldRectDest = { 0, 0, (float)worldWidth, (float)worldHeight };
  64. const Rectangle textureOnScreen = { 0, 0, (float)windowWidth, (float)windowHeight };
  65. const PresetPattern presetPatterns[] = {
  66. { "Glider", { 0.5f, 0.5f } }, { "R-pentomino", { 0.5f, 0.5f } }, { "Acorn", { 0.5f,0.5f } },
  67. { "Spaceships", { 0.1f, 0.5f } }, { "Still lifes", { 0.5f, 0.5f } }, { "Oscillators", { 0.5f, 0.5f } },
  68. { "Puffer train", { 0.1f, 0.5f } }, { "Glider Gun", { 0.2f, 0.2f } }, { "Breeder", { 0.1f, 0.5f } },
  69. { "Random", { 0.5f, 0.5f } }
  70. };
  71. const int numberOfPresets = sizeof(presetPatterns)/sizeof(presetPatterns[0]);
  72. int zoom = 1;
  73. float offsetX = (worldWidth - windowWidth)/2.0f; // Centered on window
  74. float offsetY = (worldHeight - windowHeight)/2.0f; // Centered on window
  75. int framesPerStep = 1;
  76. int frame = 0;
  77. int preset = -1; // No button pressed for preset
  78. int mode = MODE_RUN; // Starting mode: running
  79. bool buttonZoomIn = false; // Button states: false not pressed
  80. bool buttonZomOut = false;
  81. bool buttonFaster = false;
  82. bool buttonSlower = false;
  83. // Load shader
  84. Shader shdrGameOfLife = LoadShader(0, TextFormat("resources/shaders/glsl%i/game_of_life.fs", GLSL_VERSION));
  85. // Set shader uniform size of the world
  86. int resolutionLoc = GetShaderLocation(shdrGameOfLife, "resolution");
  87. const float resolution[2] = { (float)worldWidth, (float)worldHeight };
  88. SetShaderValue(shdrGameOfLife, resolutionLoc, resolution, SHADER_UNIFORM_VEC2);
  89. // Define two textures: the current world and the previous world
  90. RenderTexture2D world1 = LoadRenderTexture(worldWidth, worldHeight);
  91. RenderTexture2D world2 = LoadRenderTexture(worldWidth, worldHeight);
  92. BeginTextureMode(world2);
  93. ClearBackground(RAYWHITE);
  94. EndTextureMode();
  95. Image startPattern = LoadImage("resources/game_of_life/r_pentomino.png");
  96. UpdateTextureRec(world2.texture, (Rectangle){ worldWidth/2.0f, worldHeight/2.0f, (float)(startPattern.width), (float)(startPattern.height) }, startPattern.data);
  97. UnloadImage(startPattern);
  98. // Pointers to the two textures, to be swapped
  99. RenderTexture2D *currentWorld = &world2;
  100. RenderTexture2D *previousWorld = &world1;
  101. // Image to be used in DRAW mode, to be changed with mouse input
  102. Image *imageToDraw = NULL;
  103. SetTargetFPS(60); // Set at 60 frames-per-second
  104. //--------------------------------------------------------------------------------------
  105. // Main game loop
  106. while (!WindowShouldClose()) // Detect window close button or ESC key
  107. {
  108. // Update
  109. //----------------------------------------------------------------------------------
  110. frame++;
  111. // Change zoom: both by buttons or by mouse wheel
  112. float mouseWheelMove = GetMouseWheelMove();
  113. if (buttonZoomIn || (buttonZomOut && (zoom > 1)) || (mouseWheelMove != 0.0f))
  114. {
  115. FreeImageToDraw(&imageToDraw); // Zoom change: free the image to draw to be recreated again
  116. const float centerX = offsetX + (windowWidth/2.0f)/zoom;
  117. const float centerY = offsetY + (windowHeight/2.0f)/zoom;
  118. if (buttonZoomIn || (mouseWheelMove > 0.0f)) zoom *= 2;
  119. if ((buttonZomOut || (mouseWheelMove < 0.0f)) && (zoom > 1)) zoom /= 2;
  120. offsetX = centerX - (windowWidth/2.0f)/zoom;
  121. offsetY = centerY - (windowHeight/2.0f)/zoom;
  122. }
  123. // Change speed: number of frames per step
  124. if (buttonFaster && framesPerStep > 1) framesPerStep--;
  125. if (buttonSlower) framesPerStep++;
  126. // Mouse management
  127. if ((mode == MODE_RUN) || (mode == MODE_PAUSE))
  128. {
  129. FreeImageToDraw(&imageToDraw); // Free the image to draw: no longer needed in these modes
  130. // Pan with mouse left button
  131. static Vector2 previousMousePosition = { 0.0f, 0.0f };
  132. const Vector2 mousePosition = GetMousePosition();
  133. if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && (mousePosition.x < windowWidth))
  134. {
  135. offsetX -= (mousePosition.x - previousMousePosition.x)/zoom;
  136. offsetY -= (mousePosition.y - previousMousePosition.y)/zoom;
  137. }
  138. previousMousePosition = mousePosition;
  139. }
  140. else // MODE_DRAW
  141. {
  142. const float offsetDecimalX = offsetX - floorf(offsetX);
  143. const float offsetDecimalY = offsetY - floorf(offsetY);
  144. int sizeInWorldX = (int)(ceilf((float)(windowWidth + offsetDecimalX*zoom)/zoom));
  145. int sizeInWorldY = (int)(ceilf((float)(windowHeight + offsetDecimalY*zoom)/zoom));
  146. if (offsetX + sizeInWorldX >= worldWidth) sizeInWorldX = worldWidth - (int)floorf(offsetX);
  147. if (offsetY + sizeInWorldY >= worldHeight) sizeInWorldY = worldHeight - (int)floorf(offsetY);
  148. // Create image to draw if not created yet
  149. if (imageToDraw == NULL)
  150. {
  151. RenderTexture2D worldOnScreen = LoadRenderTexture(sizeInWorldX, sizeInWorldY);
  152. BeginTextureMode(worldOnScreen);
  153. DrawTexturePro(currentWorld->texture, (Rectangle) { floorf(offsetX), floorf(offsetY), (float)(sizeInWorldX), -(float)(sizeInWorldY) },
  154. (Rectangle) { 0, 0, (float)(sizeInWorldX), (float)(sizeInWorldY) }, (Vector2) { 0, 0 }, 0.0f, WHITE);
  155. EndTextureMode();
  156. imageToDraw = (Image*)RL_MALLOC(sizeof(Image));
  157. *imageToDraw = LoadImageFromTexture(worldOnScreen.texture);
  158. UnloadRenderTexture(worldOnScreen);
  159. }
  160. const Vector2 mousePosition = GetMousePosition();
  161. static int firstColor = -1;
  162. if (IsMouseButtonDown(MOUSE_BUTTON_LEFT) && (mousePosition.x < windowWidth))
  163. {
  164. int mouseX = (int)(mousePosition.x + offsetDecimalX*zoom)/zoom;
  165. int mouseY = (int)(mousePosition.y + offsetDecimalY*zoom)/zoom;
  166. if (mouseX >= sizeInWorldX) mouseX = sizeInWorldX - 1;
  167. if (mouseY >= sizeInWorldY) mouseY = sizeInWorldY - 1;
  168. if (firstColor == -1) firstColor = (GetImageColor(*imageToDraw, mouseX, mouseY).r < 5)? 0 : 1;
  169. const int prevColor = (GetImageColor(*imageToDraw, mouseX, mouseY).r < 5)? 0 : 1;
  170. ImageDrawPixel(imageToDraw, mouseX, mouseY, (firstColor) ? BLACK : RAYWHITE);
  171. if (prevColor != firstColor) UpdateTextureRec(currentWorld->texture, (Rectangle){ floorf(offsetX), floorf(offsetY), (float)(sizeInWorldX), (float)(sizeInWorldY) }, imageToDraw->data);
  172. }
  173. else firstColor = -1;
  174. }
  175. // Load selected preset
  176. if (preset >= 0)
  177. {
  178. Image pattern;
  179. if (preset < numberOfPresets - 1) // Preset with pattern image lo load
  180. {
  181. switch (preset)
  182. {
  183. case 0: pattern = LoadImage("resources/game_of_life/glider.png"); break;
  184. case 1: pattern = LoadImage("resources/game_of_life/r_pentomino.png"); break;
  185. case 2: pattern = LoadImage("resources/game_of_life/acorn.png"); break;
  186. case 3: pattern = LoadImage("resources/game_of_life/spaceships.png"); break;
  187. case 4: pattern = LoadImage("resources/game_of_life/still_lifes.png"); break;
  188. case 5: pattern = LoadImage("resources/game_of_life/oscillators.png"); break;
  189. case 6: pattern = LoadImage("resources/game_of_life/puffer_train.png"); break;
  190. case 7: pattern = LoadImage("resources/game_of_life/glider_gun.png"); break;
  191. case 8: pattern = LoadImage("resources/game_of_life/breeder.png"); break;
  192. }
  193. BeginTextureMode(*currentWorld);
  194. ClearBackground(RAYWHITE);
  195. EndTextureMode();
  196. UpdateTextureRec(currentWorld->texture, (Rectangle){ worldWidth*presetPatterns[preset].position.x - pattern.width/2.0f,
  197. worldHeight*presetPatterns[preset].position.y - pattern.height/2.0f,
  198. (float)(pattern.width), (float)(pattern.height) }, pattern.data);
  199. }
  200. else // Last preset: Random values
  201. {
  202. pattern = GenImageColor(worldWidth/randomTiles, worldHeight/randomTiles, RAYWHITE);
  203. for (int i = 0; i < randomTiles; i++)
  204. {
  205. for (int j = 0; j < randomTiles; j++)
  206. {
  207. ImageClearBackground(&pattern, RAYWHITE);
  208. for (int x = 0; x < pattern.width; x++)
  209. {
  210. for (int y = 0; y < pattern.height; y++)
  211. {
  212. if (GetRandomValue(0, 100) < 15) ImageDrawPixel(&pattern, x, y, BLACK);
  213. }
  214. }
  215. UpdateTextureRec(currentWorld->texture,
  216. (Rectangle){ (float)(pattern.width*i), (float)(pattern.height*j),
  217. (float)(pattern.width), (float)(pattern.height) }, pattern.data);
  218. }
  219. }
  220. }
  221. UnloadImage(pattern);
  222. mode = MODE_PAUSE;
  223. offsetX = worldWidth*presetPatterns[preset].position.x - windowWidth/zoom/2.0f;
  224. offsetY = worldHeight*presetPatterns[preset].position.y - windowHeight/zoom/2.0f;
  225. }
  226. // Check window draw inside world limits
  227. if (offsetX < 0) offsetX = 0;
  228. if (offsetY < 0) offsetY = 0;
  229. if (offsetX > worldWidth - (float)(windowWidth)/zoom) offsetX = worldWidth - (float)(windowWidth)/zoom;
  230. if (offsetY > worldHeight - (float)(windowHeight)/zoom) offsetY = worldHeight - (float)(windowHeight)/zoom;
  231. // Rectangles for drawing texture portion to screen
  232. const Rectangle textureSourceToScreen = { offsetX, offsetY, (float)windowWidth/zoom, (float)windowHeight/zoom };
  233. //----------------------------------------------------------------------------------
  234. // Draw to texture
  235. //----------------------------------------------------------------------------------
  236. if ((mode == MODE_RUN) && ((frame%framesPerStep) == 0))
  237. {
  238. // Swap worlds
  239. RenderTexture2D *tempWorld = currentWorld;
  240. currentWorld = previousWorld;
  241. previousWorld = tempWorld;
  242. // Draw to texture
  243. BeginTextureMode(*currentWorld);
  244. BeginShaderMode(shdrGameOfLife);
  245. DrawTexturePro(previousWorld->texture, worldRectSource, worldRectDest, (Vector2){ 0, 0 }, 0.0f, RAYWHITE);
  246. EndShaderMode();
  247. EndTextureMode();
  248. }
  249. //----------------------------------------------------------------------------------
  250. // Draw to screen
  251. //----------------------------------------------------------------------------------
  252. BeginDrawing();
  253. DrawTexturePro(currentWorld->texture, textureSourceToScreen, textureOnScreen, (Vector2){ 0, 0 }, 0.0f, WHITE);
  254. DrawLine(windowWidth, 0, windowWidth, screenHeight, (Color){ 218, 218, 218, 255 });
  255. DrawRectangle(windowWidth, 0, screenWidth - windowWidth, screenHeight, (Color){ 232, 232, 232, 255 });
  256. DrawText("Conway's", 704, 4, 20, DARKBLUE);
  257. DrawText(" game of", 704, 19, 20, DARKBLUE);
  258. DrawText(" life", 708, 34, 20, DARKBLUE);
  259. DrawText("in raylib", 757, 42, 6, BLACK);
  260. DrawText("Presets", 710, 58, 8, GRAY);
  261. preset = -1;
  262. for (int i = 0; i < numberOfPresets; i++)
  263. if (GuiButton((Rectangle){ 710.0f, 70.0f + 18*i, 80.0f, 16.0f }, presetPatterns[i].name)) preset = i;
  264. GuiToggleGroup((Rectangle){ 710, 258, 80, 16 }, "Run\nPause\nDraw", &mode);
  265. DrawText(TextFormat("Zoom: %ix", zoom), 710, 316, 8, GRAY);
  266. buttonZoomIn = GuiButton((Rectangle){ 710, 328, 80, 16 }, "Zoom in");
  267. buttonZomOut = GuiButton((Rectangle){ 710, 346, 80, 16 }, "Zoom out");
  268. DrawText(TextFormat("Speed: %i frame%s", framesPerStep, (framesPerStep > 1)? "s" : ""), 710, 370, 8, GRAY);
  269. buttonFaster = GuiButton((Rectangle){ 710, 382, 80, 16 }, "Faster");
  270. buttonSlower = GuiButton((Rectangle){ 710, 400, 80, 16 }, "Slower");
  271. DrawFPS(712, 426);
  272. EndDrawing();
  273. //----------------------------------------------------------------------------------
  274. }
  275. // De-Initialization
  276. //--------------------------------------------------------------------------------------
  277. UnloadShader(shdrGameOfLife);
  278. UnloadRenderTexture(world1);
  279. UnloadRenderTexture(world2);
  280. FreeImageToDraw(&imageToDraw);
  281. CloseWindow(); // Close window and OpenGL context
  282. //--------------------------------------------------------------------------------------
  283. return 0;
  284. }
  285. //----------------------------------------------------------------------------------
  286. // Functions definition
  287. //----------------------------------------------------------------------------------
  288. void FreeImageToDraw(Image **imageToDraw)
  289. {
  290. if (*imageToDraw != NULL)
  291. {
  292. UnloadImage(**imageToDraw);
  293. RL_FREE(*imageToDraw);
  294. *imageToDraw = NULL;
  295. }
  296. }