Skip to content

Instantly share code, notes, and snippets.

@aavogt
Created April 9, 2026 10:58
Show Gist options
  • Select an option

  • Save aavogt/b2d3b59580ce823b4c4abd39e7bc5052 to your computer and use it in GitHub Desktop.

Select an option

Save aavogt/b2d3b59580ce823b4c4abd39e7bc5052 to your computer and use it in GitHub Desktop.
raylib turing pattern animation
// 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();
}
#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