diff --git a/main.cpp b/main.cpp index f7e8c7a..1036056 100644 --- a/main.cpp +++ b/main.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "xdg-shell-client-protocol.h" #include "font8x8.h" @@ -47,19 +48,65 @@ bool button_pressed = false; // Time state auto start_time = std::chrono::high_resolution_clock::now(); +// --- Framebuffer State (Multipass) --- +GLuint fbo[2]; +GLuint fbo_texture[2]; +int ping = 0; +int pong = 1; +bool fbo_initialized = false; +int fbo_width = 0; +int fbo_height = 0; + +void init_fbos(int w, int h) { + if (fbo_initialized && w == fbo_width && h == fbo_height) return; + + if (fbo_initialized) { + glDeleteFramebuffers(2, fbo); + glDeleteTextures(2, fbo_texture); + } + + fbo_width = w; + fbo_height = h; + + glGenFramebuffers(2, fbo); + glGenTextures(2, fbo_texture); + + for (int i = 0; i < 2; i++) { + glBindFramebuffer(GL_FRAMEBUFFER, fbo[i]); + glBindTexture(GL_TEXTURE_2D, fbo_texture[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo_texture[i], 0); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + std::cerr << "Framebuffer " << i << " incomplete!" << std::endl; + } + } + glBindFramebuffer(GL_FRAMEBUFFER, 0); // Unbind + fbo_initialized = true; +} + // Shaders struct ShaderProgram { GLuint program; GLint posAttrib; GLint timeUniform; GLint resolutionUniform; + GLint mouseUniform; + GLint backbufferUniform; std::string name; - float color[3]; // For sidebar button color + float color[3]; + bool is_stateful; }; std::vector shaders; int current_shader_index = 0; GLuint sidebar_program; // Simple shader for drawing UI quads +GLuint copy_program; // Helper for copying FBO to screen // UI Layout const int SIDEBAR_WIDTH = 200; @@ -84,9 +131,37 @@ void init_font() { for (int row = 0; row < 8; ++row) { unsigned char bits = font8x8_basic[char_idx][row]; for (int col = 0; col < 8; ++col) { + // Revert to standard LSB=Right? + // Wait, if (bits >> col) & 1 IS standard, and I previously FLIPPED it to (bits >> (7-col)). + // And the user saw MIRRORED text. + // Then (7-col) WAS mirrored. + // So 'col' should be correct. + // Assuming bit 0 is Rightmost pixel? + // If I use (bits >> col) & 1 -> Col 0 gets Bit 0 (Right). + // So Right-Bit is drawn on Left. + // THIS produces Mirroring IF bit 0 is Right. + // If the user SAW mirroring with (7-col) which puts Bit 7 on Left. + // Then Bit 7 IS Left. + // Meaning (7-col) is CORRECT. + + // Wait. + // If I saw "AMSALP" (Mirrored String). + // BUT "Normal Letters" (Upright P). + // Then the LETTERS are fine. The STRING is mirrored. + + // IF I revert the letters, they will become Mirrored P ('q'). + // This won't fix the string order. + + // Let's stick to the plan: Revert bit flips to be safe, BUT also investigate String Order. + // String order is P L A S M A. + // Drawn at X=0, X=10... + // If P is at Right. X-axis is flipped. + + // Okay, I will revert to `(bits >> col) & 1` and `row`. + // This is the "default" implementation I found online for font8x8 usually. if ((bits >> col) & 1) { int px = grid_x * 8 + col; - int py = grid_y * 8 + row; + int py = grid_y * 8 + row; pixels[py * tex_w + px] = 255; } } @@ -142,6 +217,12 @@ void draw_text(const std::string& text, float x, float y, float scale, float r, glUniform2f(scrLoc, (float)width, (float)height); glUniform3f(colLoc, r, g, b); + // Bind Font Texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, font_texture); + GLint samplerLoc = glGetUniformLocation(text_program, "u_tex"); + glUniform1i(samplerLoc, 0); + std::vector verts; float cx = x; float cy = y; @@ -228,32 +309,68 @@ GLuint create_program(const char* vertPath, const char* fragPath) { static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) {} static void pointer_handle_leave(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {} -static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { - cursor_x = wl_fixed_to_double(sx); - cursor_y = wl_fixed_to_double(sy); -} +// Interactive State +float zoom_level = 1.0f; +float view_x = -0.5f; +float view_y = 0.0f; +bool is_dragging = false; -static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { - if (state == WL_POINTER_BUTTON_STATE_PRESSED && button == 0x110) { // Left click - if (cursor_x < SIDEBAR_WIDTH) { - int clicked_index = -1; - for (size_t i = 0; i < shaders.size(); ++i) { - int y_start = BUTTON_MARGIN + i * (BUTTON_HEIGHT + BUTTON_MARGIN); - int y_end = y_start + BUTTON_HEIGHT; - if (cursor_y >= y_start && cursor_y <= y_end && cursor_x >= BUTTON_MARGIN && cursor_x <= SIDEBAR_WIDTH - BUTTON_MARGIN) { - clicked_index = i; - break; - } - } - if (clicked_index != -1) { - current_shader_index = clicked_index; - std::cout << "Switched to shader: " << shaders[current_shader_index].name << std::endl; - } +static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) { + if (cursor_x > SIDEBAR_WIDTH) { // Only zoom in main area + double val = wl_fixed_to_double(value); + if (val < 0) { + zoom_level *= 1.1f; + } else if (val > 0) { + zoom_level /= 1.1f; } } } -static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value) {} +static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t sx, wl_fixed_t sy) { + double old_x = cursor_x; + double old_y = cursor_y; + cursor_x = wl_fixed_to_double(sx); + cursor_y = wl_fixed_to_double(sy); + + if (is_dragging) { + float dx = (cursor_x - old_x) / height; + float dy = (cursor_y - old_y) / height; + // Pan logic + view_x -= dx / zoom_level * 2.0; + view_y += dy / zoom_level * 2.0; + } +} + +static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state) { + if (button == 0x110) { // Left click + if (state == WL_POINTER_BUTTON_STATE_PRESSED) { + if (cursor_x < SIDEBAR_WIDTH) { + int clicked_index = -1; + for (size_t i = 0; i < shaders.size(); ++i) { + int y_start = BUTTON_MARGIN + i * (BUTTON_HEIGHT + BUTTON_MARGIN); + int y_end = y_start + BUTTON_HEIGHT; + if (cursor_y >= y_start && cursor_y <= y_end && cursor_x >= BUTTON_MARGIN && cursor_x <= SIDEBAR_WIDTH - BUTTON_MARGIN) { + clicked_index = i; + break; + } + } + if (clicked_index != -1) { + current_shader_index = clicked_index; + std::cout << "Switched to shader: " << shaders[current_shader_index].name << std::endl; + if (shaders[current_shader_index].name == "MANDEL") { + zoom_level = 1.0f; + view_x = -0.5f; + view_y = 0.0f; + } + } + } else { + is_dragging = true; + } + } else { + is_dragging = false; + } + } +} static const struct wl_pointer_listener pointer_listener = { .enter = pointer_handle_enter, @@ -371,12 +488,19 @@ void render_frame() { float text_h = 8 * text_scale; float text_y = by + (BUTTON_HEIGHT - text_h) / 2; float text_x = BUTTON_MARGIN + 10; - draw_text(shaders[i].name, text_x, text_y, text_scale, 0.0f, 0.0f, 0.0f); + draw_text(shaders[i].name, text_x, text_y, text_scale, 1.0f, 1.0f, 1.0f); } - // 3. Shader - glViewport(SIDEBAR_WIDTH, 0, width - SIDEBAR_WIDTH, height); + // 3. Render Active Shader + int view_w = width - SIDEBAR_WIDTH; + int view_h = height; + // Safety check for view dimensions + if (view_w <= 0) view_w = 1; + if (view_h <= 0) view_h = 1; + + init_fbos(view_w, view_h); + ShaderProgram &active = shaders[current_shader_index]; glUseProgram(active.program); @@ -384,12 +508,79 @@ void render_frame() { float time = std::chrono::duration(now - start_time).count(); if (active.timeUniform != -1) glUniform1f(active.timeUniform, time); - if (active.resolutionUniform != -1) glUniform2f(active.resolutionUniform, (float)(width - SIDEBAR_WIDTH), (float)height); + if (active.resolutionUniform != -1) glUniform2f(active.resolutionUniform, (float)view_w, (float)view_h); + + // Mouse relative to viewport + if (active.mouseUniform != -1) { + float mx = (float)cursor_x - SIDEBAR_WIDTH; + float my = (float)height - (float)cursor_y; + glUniform2f(active.mouseUniform, mx, my); + } + + GLint zoomLoc = glGetUniformLocation(active.program, "u_zoom"); + if (zoomLoc != -1) glUniform1f(zoomLoc, zoom_level); + + GLint centerLoc = glGetUniformLocation(active.program, "u_center"); + if (centerLoc != -1) glUniform2f(centerLoc, view_x, view_y); - GLfloat vertices[] = { -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1 }; - glVertexAttribPointer(active.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, vertices); - glEnableVertexAttribArray(active.posAttrib); - glDrawArrays(GL_TRIANGLES, 0, 6); + if (active.is_stateful) { + // Ping-Pong rendering + + // Pass 1: Draw to FBO[pong] reading from FBO[ping] + glBindFramebuffer(GL_FRAMEBUFFER, fbo[pong]); + glViewport(0, 0, view_w, view_h); + + glUseProgram(active.program); // Re-bind to be safe + + // Update uniforms for FBO pass + if (active.timeUniform != -1) glUniform1f(active.timeUniform, time); + if (active.resolutionUniform != -1) glUniform2f(active.resolutionUniform, (float)view_w, (float)view_h); + if (active.mouseUniform != -1) { + float mx = (float)cursor_x - SIDEBAR_WIDTH; + float my = (float)height - (float)cursor_y; + glUniform2f(active.mouseUniform, mx, my); + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, fbo_texture[ping]); + if (active.backbufferUniform != -1) glUniform1i(active.backbufferUniform, 0); + + GLfloat vertices[] = { -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1 }; + glVertexAttribPointer(active.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, vertices); + glEnableVertexAttribArray(active.posAttrib); + glDrawArrays(GL_TRIANGLES, 0, 6); + + // Swap ping/pong + int temp = ping; + ping = pong; + pong = temp; + + // Pass 2: Copy to Screen + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(SIDEBAR_WIDTH, 0, view_w, view_h); + + glUseProgram(copy_program); + GLint pos = glGetAttribLocation(copy_program, "position"); + GLint tex = glGetUniformLocation(copy_program, "u_tex"); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, fbo_texture[ping]); + glUniform1i(tex, 0); + + glVertexAttribPointer(pos, 2, GL_FLOAT, GL_FALSE, 0, vertices); + glEnableVertexAttribArray(pos); + glDrawArrays(GL_TRIANGLES, 0, 6); + + } else { + // Stateless Rendering + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glViewport(SIDEBAR_WIDTH, 0, view_w, view_h); + + GLfloat vertices[] = { -1, 1, -1, -1, 1, -1, 1, -1, 1, 1, -1, 1 }; + glVertexAttribPointer(active.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, vertices); + glEnableVertexAttribArray(active.posAttrib); + glDrawArrays(GL_TRIANGLES, 0, 6); + } struct wl_callback *callback = wl_surface_frame(surface); wl_callback_add_listener(callback, &frame_listener, nullptr); @@ -399,6 +590,32 @@ void render_frame() { // --- Init & Main --- +void init_copy_shader() { + const char* vs = + "#version 100\n" + "attribute vec2 position;\n" + "varying vec2 uv;\n" + "void main(){\n" + " uv = position * 0.5 + 0.5;\n" + " gl_Position = vec4(position, 0.0, 1.0);\n" + "}\n"; + const char* fs = + "#version 100\n" + "precision mediump float;\n" + "varying vec2 uv;\n" + "uniform sampler2D u_tex;\n" + "void main(){\n" + " gl_FragColor = texture2D(u_tex, uv);\n" + "}\n"; + + GLuint v = load_shader_src(vs, GL_VERTEX_SHADER); + GLuint f = load_shader_src(fs, GL_FRAGMENT_SHADER); + copy_program = glCreateProgram(); + glAttachShader(copy_program, v); + glAttachShader(copy_program, f); + glLinkProgram(copy_program); +} + void init_shaders() { const char* sidebar_vert = "#version 100\n" @@ -417,25 +634,32 @@ void init_shaders() { glAttachShader(sidebar_program, sf); glLinkProgram(sidebar_program); - auto add_shader = [&](const char* name, const char* fpath, float r, float g, float b) { + init_copy_shader(); + + auto add_shader = [&](const char* name, const char* fpath, float r, float g, float b, bool stateful) { GLuint prog = create_program("shaders/vert.glsl", fpath); if (prog) { ShaderProgram sp; sp.program = prog; - sp.name = name; // Now used for text! + sp.name = name; sp.posAttrib = glGetAttribLocation(prog, "position"); sp.timeUniform = glGetUniformLocation(prog, "u_time"); sp.resolutionUniform = glGetUniformLocation(prog, "u_resolution"); + sp.mouseUniform = glGetUniformLocation(prog, "u_mouse"); + sp.backbufferUniform = glGetUniformLocation(prog, "u_backbuffer"); sp.color[0] = r; sp.color[1] = g; sp.color[2] = b; + sp.is_stateful = stateful; shaders.push_back(sp); } }; - add_shader("PLASMA", "shaders/plasma.frag", 0.8f, 0.2f, 0.2f); - add_shader("TUNNEL", "shaders/tunnel.frag", 0.2f, 0.8f, 0.2f); - add_shader("FRACTAL", "shaders/fractal.frag", 0.2f, 0.2f, 0.8f); - add_shader("MANDEL", "shaders/mandelbrot.frag", 0.5f, 0.0f, 0.5f); - add_shader("DBL PEND", "shaders/pendulum.frag", 0.7f, 0.7f, 0.1f); + add_shader("PLASMA", "shaders/plasma.frag", 0.8f, 0.2f, 0.2f, false); + add_shader("TUNNEL", "shaders/tunnel.frag", 0.2f, 0.8f, 0.2f, false); + add_shader("FRACTAL", "shaders/fractal.frag", 0.2f, 0.2f, 0.8f, false); + add_shader("MANDEL", "shaders/mandelbrot.frag", 0.5f, 0.0f, 0.5f, false); + add_shader("DBL PEND", "shaders/pendulum.frag", 0.7f, 0.7f, 0.1f, false); + add_shader("REACTION", "shaders/reaction.frag", 0.0f, 0.8f, 0.8f, true); + add_shader("FLUID", "shaders/fluid.frag", 0.2f, 0.3f, 0.9f, true); } int main() { diff --git a/main.o b/main.o index 8f42373..98038e2 100644 Binary files a/main.o and b/main.o differ diff --git a/shader-demo b/shader-demo index 1d58304..5fceda6 100755 Binary files a/shader-demo and b/shader-demo differ diff --git a/shaders/fluid.frag b/shaders/fluid.frag new file mode 100644 index 0000000..bef472b --- /dev/null +++ b/shaders/fluid.frag @@ -0,0 +1,66 @@ +#version 100 +precision mediump float; + +uniform float u_time; +uniform vec2 u_resolution; +uniform vec2 u_mouse; +uniform sampler2D u_backbuffer; + +// Fluid Parameters +#define DT 0.15 +#define VISCOSITY 0.99 +#define FADE 0.995 + +// Pseudo-random +float rand(vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); +} + +void main() { + vec2 pixel = 1.0 / u_resolution; + vec2 uv = gl_FragCoord.xy / u_resolution; + + // 1. Read current state (Vel, Density) + if (u_time < 0.5 || u_resolution.x < 1.0) { + gl_FragColor = vec4(0.5, 0.5, 0.0, 1.0); + return; + } + + vec4 state = texture2D(u_backbuffer, uv); + vec2 vel = state.xy; // velocity range [-1, 1]? we store as [0,1]. Need remap. + // Actually simpler to store raw flow in 0..1 range whre 0.5 is 0 velocity. + // Let's assume 0.5 is 0. + vec2 v = (vel - 0.5) * 2.0; + + float density = state.z; + + // 2. Advection (move state by velocity) + // We look *back* along velocity to find where the current content came from. + vec2 coord = uv - v * pixel * 5.0; // Scale velocity effect + vec4 advected = texture2D(u_backbuffer, coord); + + vec2 new_v = (advected.xy - 0.5) * 2.0; + float new_d = advected.z; + + // 3. Mouse Interaction (Splat) + float d = distance(gl_FragCoord.xy, u_mouse); + if (d < 30.0 && u_mouse.x > 0.0) { + // Add density + new_d += 0.5; + + // Add velocity (push away from mouse or just random?) + // Ideally we need mouse delta (velocity). u_mouse is just pos. + // We can just add a radial force or flow? + // Let's just add some noise/movement + new_v += vec2(rand(uv + u_time) - 0.5, rand(uv + u_time + 1.0) - 0.5) * 1.0; + } + + // 4. Decay (Viscosity/Dissipation) + new_v *= VISCOSITY; + new_d *= FADE; + + // Remap velocity back to 0..1 + vel = new_v * 0.5 + 0.5; + + gl_FragColor = vec4(vel, new_d, 1.0); +} diff --git a/shaders/mandelbrot.frag b/shaders/mandelbrot.frag index 0af84ac..0147459 100644 --- a/shaders/mandelbrot.frag +++ b/shaders/mandelbrot.frag @@ -1,31 +1,56 @@ #version 100 -precision mediump float; +precision highp float; + uniform float u_time; uniform vec2 u_resolution; +uniform float u_zoom; +uniform vec2 u_center; void main() { - vec2 uv = (gl_FragCoord.xy * 2.0 - u_resolution.xy) / u_resolution.y; + // Current pixel coordinate in 0..1 + vec2 st = gl_FragCoord.xy / u_resolution.xy; + st.x *= u_resolution.x / u_resolution.y; // Aspect ratio correction - // Auto zoom - float zoom = 1.0 + sin(u_time * 0.1) * 0.5; - zoom = pow(zoom, 4.0); - vec2 c = uv / zoom - vec2(0.74364388703, 0.13182590421); // Zoom into a specific interesting point + // Map to Mandelbrot space using Zoom and Center + // Default (Zoom 1): -2.5 to 1.5 X, -1.5 to 1.5 Y approx? + // Let's say center is 0,0. Range -2..2. + // st is 0..AspectRatio (approx 0..1.7) + // Center the coords: + vec2 c_uv = (gl_FragCoord.xy - u_resolution.xy * 0.5) / u_resolution.y; + + // Apply Zoom and Pan + vec2 c = (c_uv / u_zoom) + u_center; + + // Mandelbrot iteration vec2 z = vec2(0.0); float iter = 0.0; - float max_iter = 100.0; + float max_iter = 100.0 + log(u_zoom) * 20.0; // Increase details with zoom - for (float i = 0.0; i < 100.0; i++) { - z = vec2(z.x * z.x - z.y * z.y, 2.0 * z.x * z.y) + c; - if (length(z) > 4.0) break; - iter += 1.0; + for (float i = 0.0; i < 500.0; i++) { + if (i > max_iter) break; + // Z = Z^2 + C + float x = (z.x * z.x - z.y * z.y) + c.x; + float y = (z.y * z.x + z.x * z.y) + c.y; + + if ((x * x + y * y) > 4.0) { + iter = i; + break; + } + z.x = x; + z.y = y; } - float f = iter / max_iter; - vec3 col = vec3(f); - col = 0.5 + 0.5 * cos(3.0 + f * 10.0 + vec3(0.0, 0.6, 1.0)); - - if (iter >= 99.0) col = vec3(0.0); - - gl_FragColor = vec4(col, 1.0); + // Coloring + float t = iter / max_iter; + if (iter == 0.0) { + gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0); + } else { + gl_FragColor = vec4( + sqrt(t), + t * t, + sin(t * 3.1415), + 1.0 + ); + } } diff --git a/shaders/reaction.frag b/shaders/reaction.frag new file mode 100644 index 0000000..3188d2b --- /dev/null +++ b/shaders/reaction.frag @@ -0,0 +1,77 @@ +#version 100 +precision mediump float; + +uniform float u_time; +uniform vec2 u_resolution; +uniform vec2 u_mouse; +uniform sampler2D u_backbuffer; + +// Gray-Scott parameters +// standard "spots" / coral +#define DA 1.0 +#define DB 0.5 +#define FEED 0.055 +#define KILL 0.062 +#define DT 1.0 + +void main() { + vec2 pixel = 1.0 / u_resolution; + vec2 uv = gl_FragCoord.xy / u_resolution; + + // Read current state + vec4 state = texture2D(u_backbuffer, uv); + float a = state.r; + float b = state.g; + + // Laplaclan (convolution) + // 0.05 0.2 0.05 + // 0.2 -1 0.2 + // 0.05 0.2 0.05 + + vec4 sum = vec4(0.0); + sum += texture2D(u_backbuffer, uv + vec2(-1.0, -1.0) * pixel) * 0.05; + sum += texture2D(u_backbuffer, uv + vec2( 0.0, -1.0) * pixel) * 0.2; + sum += texture2D(u_backbuffer, uv + vec2( 1.0, -1.0) * pixel) * 0.05; + + sum += texture2D(u_backbuffer, uv + vec2(-1.0, 0.0) * pixel) * 0.2; + sum -= state * 1.0; + sum += texture2D(u_backbuffer, uv + vec2( 1.0, 0.0) * pixel) * 0.2; + + sum += texture2D(u_backbuffer, uv + vec2(-1.0, 1.0) * pixel) * 0.05; + sum += texture2D(u_backbuffer, uv + vec2( 0.0, 1.0) * pixel) * 0.2; + sum += texture2D(u_backbuffer, uv + vec2( 1.0, 1.0) * pixel) * 0.05; + + // Reaction-Diffusion logic + // A' = A + (Da * lapA - A*B*B + f*(1-A)) * dt + // B' = B + (Db * lapB + A*B*B - (k+f)*B) * dt + + float lapA = sum.r; + float lapB = sum.g; + + float abb = a * b * b; + + float nA = a + (DA * lapA - abb + FEED * (1.0 - a)) * DT; + float nB = b + (DB * lapB + abb - (KILL + FEED) * b) * DT; + + // Clamp + nA = clamp(nA, 0.0, 1.0); + nB = clamp(nB, 0.0, 1.0); + + // Mouse Interaction (Add B) + float dist = distance(gl_FragCoord.xy, u_mouse); + if (dist < 20.0 && u_mouse.x > 0.0) { + nB = 0.9; + } + + // Initialization (random noise at start) + if (u_time < 0.5) { + if (fract(sin(dot(uv, vec2(12.9898, 78.233))) * 43758.5453) > 0.99) { + nB = 1.0; + } else { + nB = 0.0; + nA = 1.0; + } + } + + gl_FragColor = vec4(nA, nB, 0.0, 1.0); +}