Created
April 9, 2026 10:58
-
-
Save aavogt/b2d3b59580ce823b4c4abd39e7bc5052 to your computer and use it in GitHub Desktop.
raylib turing pattern animation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // https://youtu.be/1A2gVRfUDPY | |
| // start the window, resize it, press enter to start recording out.mp4 | |
| #ifndef RAYLIBD | |
| #include "raygui.h" | |
| #include "raylib.h" | |
| #include "raymath.h" | |
| #include <math.h> | |
| #include <stdbool.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <time.h> | |
| #endif | |
| // initial size | |
| #define W 512 | |
| #define H 512 | |
| /// SetShaderValue / GetShaderLocation | |
| void shset(Shader sh, const char *uniformName, const void *res, | |
| int uniformType) { | |
| SetShaderValue(sh, GetShaderLocation(sh, uniformName), res, uniformType); | |
| } | |
| Texture2D fillRedRandom() { | |
| Image noise = GenImageColor(GetScreenWidth(), GetScreenHeight(), BLACK); | |
| for (int i = 0; i < W * H; i++) | |
| ((Color *)noise.data)[i] = | |
| (Color){(unsigned char)(rand() % 256), 0, 0, 255}; | |
| Texture2D result = LoadTextureFromImage(noise); | |
| UnloadImage(noise); | |
| return result; | |
| } | |
| Texture2D texA; | |
| RenderTexture2D rtA, rtB; | |
| Shader sh, shg; | |
| /// one step of the turing pattern | |
| void pingpongRT() { | |
| BeginTextureMode(rtB); | |
| BeginShaderMode(sh); | |
| DrawTexture(rtA.texture, 0, 0, WHITE); | |
| EndShaderMode(); | |
| EndTextureMode(); | |
| BeginTextureMode(rtA); | |
| BeginShaderMode(sh); | |
| DrawTexture(rtB.texture, 0, 0, WHITE); | |
| EndShaderMode(); | |
| EndTextureMode(); | |
| } | |
| void allocRT() { | |
| int w = GetScreenWidth(), h = GetScreenHeight(); | |
| rtA = LoadRenderTexture(w, h); | |
| rtB = LoadRenderTexture(w, h); | |
| } | |
| void fillRT() { | |
| UnloadTexture(texA); | |
| texA = fillRedRandom(); | |
| BeginTextureMode(rtA); | |
| DrawTexture(texA, 0, 0, WHITE); | |
| EndTextureMode(); | |
| BeginTextureMode(rtB); | |
| DrawTexture(texA, 0, 0, WHITE); | |
| EndTextureMode(); | |
| } | |
| void reallocRT() { | |
| int w = GetScreenWidth(), h = GetScreenHeight(); | |
| UnloadRenderTexture(rtA); | |
| UnloadRenderTexture(rtB); | |
| rtA = LoadRenderTexture(w, h); | |
| rtB = LoadRenderTexture(w, h); | |
| fillRT(); | |
| float res[2] = {w, h}; | |
| shset(sh, "resolution", res, SHADER_UNIFORM_VEC2); | |
| } | |
| FILE *ffmpeg_pipe; | |
| int vidw, vidh; | |
| int main(void) { | |
| SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_MSAA_4X_HINT | FLAG_VSYNC_HINT); | |
| InitWindow(W, H, "Turing Pattern"); | |
| srand(time(NULL)); | |
| sh = LoadShader(0, "turing.fs"); | |
| float res[2] = {GetScreenWidth(), GetScreenHeight()}; | |
| float actR1 = 2.0f, inhR1 = 3.0f; | |
| float actR2 = 4.0f, inhR2 = 6.0f; | |
| float step = 0.01f; | |
| int stride = 1; | |
| shset(sh, "resolution", res, SHADER_UNIFORM_VEC2); | |
| shset(sh, "actRadius1", &actR1, SHADER_UNIFORM_FLOAT); | |
| shset(sh, "inhRadius1", &inhR1, SHADER_UNIFORM_FLOAT); | |
| shset(sh, "actRadius2", &actR2, SHADER_UNIFORM_FLOAT); | |
| shset(sh, "inhRadius2", &inhR2, SHADER_UNIFORM_FLOAT); | |
| shset(sh, "stepSize", &step, SHADER_UNIFORM_FLOAT); | |
| shset(sh, "sampleStride", &stride, SHADER_UNIFORM_INT); | |
| allocRT(); | |
| fillRT(); | |
| SetTargetFPS(10); | |
| while (!WindowShouldClose()) { | |
| if (IsWindowResized() && (rtA.texture.height < GetScreenHeight() || | |
| rtA.texture.width < GetScreenWidth())) | |
| reallocRT(); | |
| if (IsKeyPressed(KEY_ENTER)) { | |
| char cmd[200]; | |
| printf("recording\n"); | |
| vidw = GetScreenWidth(); | |
| vidh = GetScreenHeight(); | |
| snprintf(cmd, 200, | |
| "ffmpeg -f rawvideo -pixel_format rgba -video_size %dx%d " | |
| "-framerate 6 -i pipe:0 -c:v libx264 out.mp4", | |
| vidw, vidh); | |
| ffmpeg_pipe = popen(cmd, "w"); | |
| } | |
| if (IsKeyPressed(KEY_SPACE)) { | |
| UnloadTexture(texA); | |
| texA = fillRedRandom(); | |
| BeginTextureMode(rtA); | |
| DrawTexture(texA, 0, 0, WHITE); | |
| EndTextureMode(); | |
| } | |
| pingpongRT(); | |
| BeginDrawing(); | |
| DrawTexture(rtA.texture, 0, 0, WHITE); | |
| EndDrawing(); | |
| if (ffmpeg_pipe) { | |
| Image frame = LoadImageFromTexture(rtA.texture); | |
| ImageFlipVertical(&frame); // raylib FBOs are flipped | |
| fwrite(frame.data, 1, vidw * vidh * 4, ffmpeg_pipe); | |
| UnloadImage(frame); | |
| } | |
| } | |
| UnloadShader(sh); | |
| UnloadTexture(texA); | |
| UnloadRenderTexture(rtA); | |
| UnloadRenderTexture(rtB); | |
| CloseWindow(); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #version 330 | |
| in vec2 fragTexCoord; | |
| out vec4 fragColor; | |
| uniform sampler2D texture0; | |
| uniform vec2 resolution; | |
| // Two scales; radii in pixels | |
| uniform float actRadius1; // e.g. 2.0 | |
| uniform float inhRadius1; // e.g. 8.0 | |
| uniform float actRadius2; // e.g. 6.0 | |
| uniform float inhRadius2; // e.g. 24.0 | |
| uniform float stepSize; // e.g. 0.02 | |
| uniform int sampleStride; // e.g. 2 (skip pixels for speed) | |
| float circleAvg(vec2 uv, float radius) { | |
| vec2 px = 1.0 / resolution; | |
| int r = int(ceil(radius)); | |
| float sum = 0.0, count = 0.0; | |
| for (int y = -r; y <= r; y += sampleStride) { | |
| for (int x = -r; x <= r; x += sampleStride) { | |
| if (float(x*x + y*y) <= radius * radius) { | |
| sum += texture(texture0, uv + vec2(x, y) * px).r; | |
| count += 1.0; | |
| } | |
| } | |
| } | |
| return sum / max(count, 1.0); | |
| } | |
| void main() { | |
| vec2 uv = fragTexCoord; | |
| float v = texture(texture0, uv).r; | |
| float a1 = circleAvg(uv, actRadius1); | |
| float i1 = circleAvg(uv, inhRadius1); | |
| float a2 = circleAvg(uv, actRadius2); | |
| float i2 = circleAvg(uv, inhRadius2); | |
| // Scale whose activator/inhibitor are closest "wins" | |
| float var1 = abs(a1 - i1); | |
| float var2 = abs(a2 - i2); | |
| float best_act, best_inh; | |
| if (var1 < var2) { best_act = a1; best_inh = i1; } | |
| else { best_act = a2; best_inh = i2; } | |
| v += (best_act > best_inh) ? stepSize : -stepSize; | |
| float min = 0; | |
| float max = 1.0; | |
| v = clamp(v, min, max); | |
| fragColor = vec4((v - (max+min)/2) / (max-min) + 0.5, // remap [-1,1] -> [0,1] for display | |
| (v - (max+min)/2) / (max-min) + 0.5, | |
| (v - (max+min)/2) / (max-min) + 0.5, | |
| 1.0); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment