
C/OpenGL ES experiments compiled to WebAssembly. Click or tap a canvas to launch it; it pauses automatically when scrolled off screen.
Rotating Triangle
Classic GL triangle with a rotation and a brightening color pulse.
Source: src/tri.c
#include <GLES3/gl3.h>
#include <math.h>
#include <stdint.h>
#include <stddef.h>
#ifdef DEBUG
#include <stdio.h>
#endif
#include "demo_app.h"
static GLuint g_program = 0;
static GLuint g_vao = 0;
static GLuint g_vbo = 0;
static GLint g_time_loc = -1;
static GLint g_aspect_loc = -1;
static int g_width = 0;
static int g_height = 0;
static int g_active = 0;
static const char *VERT_SRC =
"#version 300 es\n"
"layout(location=0) in vec2 a_pos;\n"
"layout(location=1) in vec3 a_color;\n"
"out vec3 v_color;\n"
"uniform float u_time;\n"
"uniform float u_aspect;\n"
"void main(){\n"
" float angle = u_time * 0.5;\n"
" mat2 rot = mat2(cos(angle), -sin(angle), sin(angle), cos(angle));\n"
" vec2 p = rot * a_pos;\n"
" p.x *= u_aspect;\n"
" gl_Position = vec4(p, 0.0, 1.0);\n"
" v_color = a_color;\n"
"}\n";
static const char *FRAG_SRC =
"#version 300 es\n"
"precision highp float;\n"
"in vec3 v_color;\n"
"uniform float u_time;\n"
"out vec4 fragColor;\n"
"void main(){\n"
" float glow = 0.5 + 0.5 * sin(u_time * 3.14159);\n"
" vec3 neon = mix(v_color, vec3(1.0, 0.3, 1.0), glow);\n"
" vec3 bright = clamp(neon * (1.15 + 0.65 * glow), 0.0, 1.0);\n"
" fragColor = vec4(bright, 1.0);\n"
"}\n";
static GLuint compile_shader(GLenum type, const char *src) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
GLint ok = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[512];
glGetShaderInfoLog(shader, sizeof log, NULL, log);
#ifdef DEBUG
printf("shader compile error: %s\n", log);
#endif
glDeleteShader(shader);
return 0;
}
return shader;
}
static GLuint link_program(GLuint vs, GLuint fs) {
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
GLint ok = 0;
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if (!ok) {
char log[512];
glGetProgramInfoLog(prog, sizeof log, NULL, log);
#ifdef DEBUG
printf("program link error: %s\n", log);
#endif
glDeleteProgram(prog);
prog = 0;
}
glDetachShader(prog, vs);
glDetachShader(prog, fs);
glDeleteShader(vs);
glDeleteShader(fs);
return prog;
}
void demo_app_init(int width, int height) {
g_width = width;
g_height = height;
g_active = 0;
GLuint vs = compile_shader(GL_VERTEX_SHADER, VERT_SRC);
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, FRAG_SRC);
g_program = link_program(vs, fs);
g_time_loc = glGetUniformLocation(g_program, "u_time");
g_aspect_loc = glGetUniformLocation(g_program, "u_aspect");
const GLfloat verts[] = {
0.0f, 0.6f, 1.0f, 0.4f, 0.4f,
-0.6f, -0.4f, 0.4f, 0.8f, 0.4f,
0.6f, -0.4f, 0.4f, 0.4f, 1.0f,
};
glGenVertexArrays(1, &g_vao);
glBindVertexArray(g_vao);
glGenBuffers(1, &g_vbo);
glBindBuffer(GL_ARRAY_BUFFER, g_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof verts, verts, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void *)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (void *)(2 * sizeof(GLfloat)));
demo_app_resize(width, height);
}
void demo_app_resize(int width, int height) {
g_width = width;
g_height = height;
glViewport(0, 0, g_width, g_height);
}
void demo_app_frame(double time_sec, double dt_sec) {
(void)dt_sec;
if (!g_active) return;
float t = (float)time_sec;
float aspect = (g_height > 0) ? ((float)g_height / (float)g_width) : 1.0f;
glDisable(GL_DEPTH_TEST);
glClearColor(0.05f, 0.08f, 0.12f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(g_program);
if (g_time_loc >= 0) {
glUniform1f(g_time_loc, t);
}
if (g_aspect_loc >= 0) {
glUniform1f(g_aspect_loc, aspect);
}
glBindVertexArray(g_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
void demo_app_set_active(int active) {
g_active = active ? 1 : 0;
}
void demo_app_shutdown(void) {
if (g_vbo) {
glDeleteBuffers(1, &g_vbo);
g_vbo = 0;
}
if (g_vao) {
glDeleteVertexArrays(1, &g_vao);
g_vao = 0;
}
if (g_program) {
glDeleteProgram(g_program);
g_program = 0;
}
}
void demo_app_handle_key(int key, int pressed) {
(void)key;
(void)pressed;
}
void demo_app_update_mouse(float x, float y, int present) {
(void)x; (void)y; (void)present;
}
Plasma Shader
Full-screen plasma driven by layered sine waves and a little swirl math.
Source: src/plasma.c
#include <GLES3/gl3.h>
#include <math.h>
#include <stddef.h>
#ifdef DEBUG
#include <stdio.h>
#endif
#include "demo_app.h"
static GLuint g_program = 0;
static GLuint g_vao = 0;
static GLuint g_vbo = 0;
static GLint g_time_loc = -1;
static GLint g_aspect_loc = -1;
static int g_width = 0;
static int g_height = 0;
static int g_active = 0;
static const char *VERT_SRC =
"#version 300 es\n"
"layout(location=0) in vec2 a_pos;\n"
"out vec2 v_uv;\n"
"void main(){\n"
" v_uv = a_pos * 0.5 + 0.5;\n"
" gl_Position = vec4(a_pos, 0.0, 1.0);\n"
"}\n";
static const char *FRAG_SRC =
"#version 300 es\n"
"precision highp float;\n"
"in vec2 v_uv;\n"
"uniform float u_time;\n"
"uniform float u_aspect;\n"
"out vec4 fragColor;\n"
"void main(){\n"
" vec2 uv = v_uv * 2.0 - 1.0;\n"
" uv.x *= u_aspect;\n"
" float t = u_time * 0.4;\n"
" mat2 rot = mat2(cos(t * 0.7), -sin(t * 0.7), sin(t * 0.7), cos(t * 0.7));\n"
" vec2 p = rot * uv;\n"
" float waves = sin(p.x * 3.5 + t * 1.2) + sin(p.y * 4.5 - t * 1.7);\n"
" vec2 swirlBase = uv + 0.35 * vec2(sin(t * 0.9 + uv.y * 6.0), cos(t * 0.6 + uv.x * 6.0));\n"
" float swirl = sin(swirlBase.x * swirlBase.y * 8.0 + t * 2.0);\n"
" float rings = sin(length(uv * 3.2 + vec2(sin(t), cos(t * 0.8))) - t * 1.3);\n"
" float v = waves * 0.35 + swirl * 0.4 + rings * 0.25;\n"
" vec3 col = 0.5 + 0.5 * cos(vec3(0.0, 2.0, 4.0) + v * 3.4 + t * 0.7);\n"
" fragColor = vec4(col, 1.0);\n"
"}\n";
static GLuint compile_shader(GLenum type, const char *src) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
GLint ok = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (!ok) {
char log[512];
glGetShaderInfoLog(shader, sizeof log, NULL, log);
#ifdef DEBUG
printf("shader compile error: %s\n", log);
#endif
glDeleteShader(shader);
return 0;
}
return shader;
}
static GLuint link_program(GLuint vs, GLuint fs) {
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
GLint ok = 0;
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if (!ok) {
char log[512];
glGetProgramInfoLog(prog, sizeof log, NULL, log);
#ifdef DEBUG
printf("program link error: %s\n", log);
#endif
glDeleteProgram(prog);
prog = 0;
}
glDetachShader(prog, vs);
glDetachShader(prog, fs);
glDeleteShader(vs);
glDeleteShader(fs);
return prog;
}
void demo_app_init(int width, int height) {
g_width = width;
g_height = height;
g_active = 0;
GLuint vs = compile_shader(GL_VERTEX_SHADER, VERT_SRC);
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, FRAG_SRC);
g_program = link_program(vs, fs);
g_time_loc = glGetUniformLocation(g_program, "u_time");
g_aspect_loc = glGetUniformLocation(g_program, "u_aspect");
const GLfloat verts[] = {
-1.0f, -1.0f,
3.0f, -1.0f,
-1.0f, 3.0f,
};
glGenVertexArrays(1, &g_vao);
glBindVertexArray(g_vao);
glGenBuffers(1, &g_vbo);
glBindBuffer(GL_ARRAY_BUFFER, g_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof verts, verts, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (void *)0);
demo_app_resize(width, height);
}
void demo_app_resize(int width, int height) {
g_width = width;
g_height = height;
glViewport(0, 0, g_width, g_height);
}
void demo_app_frame(double time_sec, double dt_sec) {
(void)dt_sec;
if (!g_active) return;
float t = (float)time_sec;
float aspect = (g_height > 0) ? ((float)g_width / (float)g_height) : 1.0f;
glDisable(GL_DEPTH_TEST);
glClearColor(0.02f, 0.03f, 0.05f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(g_program);
glUniform1f(g_time_loc, t);
glUniform1f(g_aspect_loc, aspect);
glBindVertexArray(g_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
void demo_app_set_active(int active) {
g_active = active ? 1 : 0;
}
void demo_app_shutdown(void) {
if (g_vbo) {
glDeleteBuffers(1, &g_vbo);
g_vbo = 0;
}
if (g_vao) {
glDeleteVertexArrays(1, &g_vao);
g_vao = 0;
}
if (g_program) {
glDeleteProgram(g_program);
g_program = 0;
}
}
void demo_app_handle_key(int key, int pressed) {
(void)key;
(void)pressed;
}
void demo_app_update_mouse(float x, float y, int present) {
(void)x; (void)y; (void)present;
}
Mandelbrot Explorer
Keyboard-driven Mandelbrot (arrows to pan, Z/X to zoom) rendered in plain GLSL.
Source: src/mandelbrot.c
#include <GLES3/gl3.h>
#include <math.h>
#include <stddef.h>
#include <string.h>
#ifdef DEBUG
#include <stdio.h>
#endif
#include "demo_app.h"
static GLuint g_program = 0;
static GLuint g_vao = 0;
static GLuint g_vbo = 0;
static GLint g_time_loc = -1;
static GLint g_aspect_loc = -1;
static GLint g_center_loc = -1;
static GLint g_scale_loc = -1;
static int g_width = 0;
static int g_height = 0;
static float g_center_x = -0.5f;
static float g_center_y = 0.0f;
static float g_scale = 1.8f;
static int g_active = 0;
static int g_key_left = 0, g_key_right = 0, g_key_up = 0, g_key_down = 0;
static int g_key_zoom_in = 0, g_key_zoom_out = 0;
static const char *VERT_SRC =
"#version 300 es\n"
"layout(location=0) in vec2 a_pos;\n"
"out vec2 v_pos;\n"
"void main(){\n"
" v_pos = a_pos;\n"
" gl_Position = vec4(a_pos, 0.0, 1.0);\n"
"}\n";
static const char *FRAG_SRC =
"#version 300 es\n"
"precision highp float;\n"
"in vec2 v_pos;\n"
"uniform float u_time;\n"
"uniform float u_aspect;\n"
"uniform vec2 u_center;\n"
"uniform float u_scale;\n"
"out vec4 fragColor;\n"
"vec3 palette(float t){\n"
" return vec3(0.5 + 0.5 * cos(6.2831 * (t + vec3(0.0, 0.33, 0.67))));\n"
"}\n"
"void main(){\n"
" vec2 uv = v_pos;\n"
" uv.x *= u_aspect;\n"
" vec2 c = u_center + uv * u_scale;\n"
" vec2 z = vec2(0.0);\n"
" float m = 0.0;\n"
" const int ITER = 150;\n"
" for (int i = 0; i < ITER; ++i){\n"
" z = vec2(z.x*z.x - z.y*z.y, 2.0*z.x*z.y) + c;\n"
" if (dot(z,z) > 4.0){\n"
" float nu = float(i) - log2(log2(dot(z,z))) + 4.0;\n"
" m = clamp(nu / float(ITER), 0.0, 1.0);\n"
" break;\n"
" }\n"
" }\n"
" float hue = fract(m + 0.15 * sin(u_time * 0.3));\n"
" vec3 col = (m == 0.0) ? vec3(0.05, 0.06, 0.08) : palette(hue);\n"
" fragColor = vec4(col, 1.0);\n"
"}\n";
static GLuint compile_shader(GLenum type, const char *src) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
GLint ok = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (!ok) {
#ifdef DEBUG
char log[512];
glGetShaderInfoLog(shader, sizeof log, NULL, log);
printf("shader compile error: %s\n", log);
#endif
glDeleteShader(shader);
return 0;
}
return shader;
}
static GLuint link_program(GLuint vs, GLuint fs) {
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
GLint ok = 0;
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if (!ok) {
#ifdef DEBUG
char log[512];
glGetProgramInfoLog(prog, sizeof log, NULL, log);
printf("program link error: %s\n", log);
#endif
glDeleteProgram(prog);
prog = 0;
}
glDetachShader(prog, vs);
glDetachShader(prog, fs);
glDeleteShader(vs);
glDeleteShader(fs);
return prog;
}
void demo_app_init(int width, int height) {
g_width = width;
g_height = height;
g_active = 0;
GLuint vs = compile_shader(GL_VERTEX_SHADER, VERT_SRC);
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, FRAG_SRC);
g_program = link_program(vs, fs);
g_time_loc = glGetUniformLocation(g_program, "u_time");
g_aspect_loc = glGetUniformLocation(g_program, "u_aspect");
g_center_loc = glGetUniformLocation(g_program, "u_center");
g_scale_loc = glGetUniformLocation(g_program, "u_scale");
const GLfloat verts[] = {
-1.0f, -1.0f,
3.0f, -1.0f,
-1.0f, 3.0f,
};
glGenVertexArrays(1, &g_vao);
glBindVertexArray(g_vao);
glGenBuffers(1, &g_vbo);
glBindBuffer(GL_ARRAY_BUFFER, g_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof verts, verts, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), (void *)0);
demo_app_resize(width, height);
}
void demo_app_resize(int width, int height) {
g_width = width;
g_height = height;
glViewport(0, 0, g_width, g_height);
}
void demo_app_frame(double time_sec, double dt_sec) {
if (!g_active) return;
float aspect = (g_height > 0) ? ((float)g_width / (float)g_height) : 1.0f;
float pan_speed = g_scale * 0.6f;
if (g_key_left) g_center_x -= pan_speed * (float)dt_sec;
if (g_key_right) g_center_x += pan_speed * (float)dt_sec;
if (g_key_up) g_center_y += pan_speed * (float)dt_sec;
if (g_key_down) g_center_y -= pan_speed * (float)dt_sec;
float zoom_rate = 1.6f;
if (g_key_zoom_in) g_scale *= expf(-zoom_rate * (float)dt_sec);
if (g_key_zoom_out) g_scale *= expf(zoom_rate * (float)dt_sec);
if (g_scale < 0.0002f) g_scale = 0.0002f;
if (g_scale > 4.0f) g_scale = 4.0f;
glDisable(GL_DEPTH_TEST);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(g_program);
if (g_aspect_loc >= 0) glUniform1f(g_aspect_loc, aspect);
if (g_time_loc >= 0) glUniform1f(g_time_loc, (float)time_sec);
if (g_center_loc >= 0) glUniform2f(g_center_loc, g_center_x, g_center_y);
if (g_scale_loc >= 0) glUniform1f(g_scale_loc, g_scale);
glBindVertexArray(g_vao);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
void demo_app_set_active(int active) {
g_active = active ? 1 : 0;
if (!g_active) {
g_key_left = g_key_right = g_key_up = g_key_down = 0;
g_key_zoom_in = g_key_zoom_out = 0;
}
}
void demo_app_shutdown(void) {
if (g_vbo) {
glDeleteBuffers(1, &g_vbo);
g_vbo = 0;
}
if (g_vao) {
glDeleteVertexArrays(1, &g_vao);
g_vao = 0;
}
if (g_program) {
glDeleteProgram(g_program);
g_program = 0;
}
}
void demo_app_handle_key(int key, int pressed) {
switch (key) {
case 0: g_key_left = pressed; break;
case 1: g_key_right = pressed; break;
case 2: g_key_up = pressed; break;
case 3: g_key_down = pressed; break;
case 4: g_key_zoom_in = pressed; break;
case 5: g_key_zoom_out = pressed; break;
default: break;
}
}
void demo_app_update_mouse(float x, float y, int present) {
(void)x; (void)y; (void)present;
}
Boids
Flocking simulation that follows your pointer; once it leaves the canvas a velocity damping term settles the flock.
Source: src/boids.c
#include <GLES3/gl3.h>
#include <math.h>
#include <stdint.h>
#include <stddef.h>
#include "demo_app.h"
#define MAX_BOIDS 160
#define NEIGHBOR_RADIUS 80.0f
#define SEPARATION_RADIUS 70.0f
#define MAX_SPEED 400.0f
#define MAX_FORCE 200.0f
#define VELOCITY_DAMP_ACTIVE 0.985f
#define VELOCITY_DAMP_IDLE 0.9f
#define EDGE_THRESHOLD 60.0f
#define EDGE_FORCE 800.0f
static int g_width = 0;
static int g_height = 0;
static int g_active = 0;
static GLuint g_program = 0;
static GLuint g_vao = 0;
static GLuint g_vbo = 0;
static GLint g_time_loc = -1;
static GLint g_resolution_loc = -1;
static float g_positions[MAX_BOIDS][2];
static float g_velocities[MAX_BOIDS][2];
static float g_mouse_x = 0.0f;
static float g_mouse_y = 0.0f;
static int g_mouse_present = 0;
static uint32_t g_rng = 1u;
static float frand(void) {
g_rng = g_rng * 1664525u + 1013904223u;
return (float)((g_rng >> 8) & 0xFFFFFFu) / (float)0x1000000u;
}
static float wrap_distance(float delta, float extent) {
if (extent <= 0.0f) return delta;
float half = extent * 0.5f;
while (delta > half) delta -= extent;
while (delta < -half) delta += extent;
return delta;
}
static float wrap_mod(float value, float extent) {
if (extent <= 0.0f) return value;
float wrapped = fmodf(value, extent);
if (wrapped < 0.0f) wrapped += extent;
return wrapped;
}
static const char *VERT_SRC =
"#version 300 es\n"
"layout(location=0) in vec2 a_clip;\n"
"uniform vec2 u_resolution;\n"
"void main(){\n"
" gl_Position = vec4(a_clip, 0.0, 1.0);\n"
" gl_PointSize = 6.0;\n"
"}\n";
static const char *FRAG_SRC =
"#version 300 es\n"
"precision highp float;\n"
"uniform float u_time;\n"
"out vec4 fragColor;\n"
"void main(){\n"
" float r = 0.6 + 0.4 * sin(u_time * 1.7 + gl_FragCoord.x * 0.02);\n"
" float g = 0.6 + 0.4 * sin(u_time * 1.3 + gl_FragCoord.y * 0.02 + 1.7);\n"
" float b = 0.7 + 0.3 * sin(u_time * 1.1 + 3.1);\n"
" vec2 uv = (gl_PointCoord - 0.5) * 2.0;\n"
" float alpha = smoothstep(1.0, 0.2, dot(uv, uv));\n"
" fragColor = vec4(r, g, b, alpha);\n"
"}\n";
static GLuint compile_shader(GLenum type, const char *src) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
GLint ok = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (!ok) {
#ifdef DEBUG
char log[512];
glGetShaderInfoLog(shader, sizeof log, NULL, log);
printf("boids shader error: %s\n", log);
#endif
glDeleteShader(shader);
return 0;
}
return shader;
}
static GLuint link_program(GLuint vs, GLuint fs) {
GLuint prog = glCreateProgram();
glAttachShader(prog, vs);
glAttachShader(prog, fs);
glLinkProgram(prog);
GLint ok = 0;
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
if (!ok) {
#ifdef DEBUG
char log[512];
glGetProgramInfoLog(prog, sizeof log, NULL, log);
printf("boids link error: %s\n", log);
#endif
glDeleteProgram(prog);
prog = 0;
}
glDetachShader(prog, vs);
glDetachShader(prog, fs);
glDeleteShader(vs);
glDeleteShader(fs);
return prog;
}
static void reset_boids(void) {
for (int i = 0; i < MAX_BOIDS; ++i) {
g_positions[i][0] = frand() * g_width;
g_positions[i][1] = frand() * g_height;
float angle = frand() * 6.2831853f;
float speed = 60.0f + frand() * 40.0f;
g_velocities[i][0] = cosf(angle) * speed;
g_velocities[i][1] = sinf(angle) * speed;
}
}
void demo_app_init(int width, int height) {
g_width = width;
g_height = height;
g_active = 0;
g_rng = 0x1234ABCDu ^ (uint32_t)(width * 131u + height);
GLuint vs = compile_shader(GL_VERTEX_SHADER, VERT_SRC);
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, FRAG_SRC);
g_program = link_program(vs, fs);
g_time_loc = glGetUniformLocation(g_program, "u_time");
g_resolution_loc = glGetUniformLocation(g_program, "u_resolution");
glGenVertexArrays(1, &g_vao);
glBindVertexArray(g_vao);
glGenBuffers(1, &g_vbo);
glBindBuffer(GL_ARRAY_BUFFER, g_vbo);
glBufferData(GL_ARRAY_BUFFER, MAX_BOIDS * 2 * sizeof(float), NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0);
reset_boids();
demo_app_resize(width, height);
}
void demo_app_resize(int width, int height) {
g_width = width;
g_height = height;
glViewport(0, 0, g_width, g_height);
}
void demo_app_frame(double time_sec, double dt_sec) {
if (!g_active) return;
float dt = (float)dt_sec;
if (dt > 0.05f) dt = 0.05f;
for (int i = 0; i < MAX_BOIDS; ++i) {
float px = g_positions[i][0];
float py = g_positions[i][1];
float px_screen = wrap_mod(px, (float)g_width);
float py_screen = wrap_mod(py, (float)g_height);
float vx = g_velocities[i][0];
float vy = g_velocities[i][1];
float align_x = 0.f, align_y = 0.f;
float cohesion_x = 0.f, cohesion_y = 0.f;
float separation_x = 0.f, separation_y = 0.f;
int neighbors = 0;
for (int j = 0; j < MAX_BOIDS; ++j) {
if (i == j) continue;
float dx = wrap_distance(g_positions[j][0] - px, (float)g_width);
float dy = wrap_distance(g_positions[j][1] - py, (float)g_height);
float dist2 = dx * dx + dy * dy;
if (dist2 < NEIGHBOR_RADIUS * NEIGHBOR_RADIUS) {
align_x += g_velocities[j][0];
align_y += g_velocities[j][1];
cohesion_x += px + dx;
cohesion_y += py + dy;
if (dist2 < SEPARATION_RADIUS * SEPARATION_RADIUS && dist2 > 0.0001f) {
separation_x -= dx / dist2;
separation_y -= dy / dist2;
}
neighbors++;
}
}
float accel_x = 0.f;
float accel_y = 0.f;
if (neighbors > 0) {
float inv = 1.0f / neighbors;
align_x = (align_x * inv - vx) * 1.2f;
align_y = (align_y * inv - vy) * 1.2f;
cohesion_x = ((cohesion_x * inv) - px) * 0.008f;
cohesion_y = ((cohesion_y * inv) - py) * 0.008f;
separation_x *= 10.0f;
separation_y *= 10.0f;
accel_x += align_x + cohesion_x + separation_x;
accel_y += align_y + cohesion_y + separation_y;
}
if (g_mouse_present) {
float dxm = g_mouse_x - px_screen;
float dym = g_mouse_y - py_screen;
float distm2 = dxm * dxm + dym * dym;
if (distm2 > 25.0f) {
float inv = 1.0f / sqrtf(distm2);
accel_x += dxm * inv * 160.0f;
accel_y += dym * inv * 160.0f;
}
}
if (g_width > 0) {
float left_dist = px_screen;
if (left_dist < EDGE_THRESHOLD) {
float t = (EDGE_THRESHOLD - left_dist) * (1.0f / EDGE_THRESHOLD);
accel_x += EDGE_FORCE * t;
}
float right_dist = (float)g_width - px_screen;
if (right_dist < EDGE_THRESHOLD) {
float t = (EDGE_THRESHOLD - right_dist) * (1.0f / EDGE_THRESHOLD);
accel_x -= EDGE_FORCE * t;
}
}
if (g_height > 0) {
float top_dist = py_screen;
if (top_dist < EDGE_THRESHOLD) {
float t = (EDGE_THRESHOLD - top_dist) * (1.0f / EDGE_THRESHOLD);
accel_y += EDGE_FORCE * t;
}
float bottom_dist = (float)g_height - py_screen;
if (bottom_dist < EDGE_THRESHOLD) {
float t = (EDGE_THRESHOLD - bottom_dist) * (1.0f / EDGE_THRESHOLD);
accel_y -= EDGE_FORCE * t;
}
}
float speed = sqrtf(vx * vx + vy * vy);
if (speed > 0.0001f) {
accel_x += (vx / speed) * 6.0f;
accel_y += (vy / speed) * 6.0f;
}
float acc_mag = sqrtf(accel_x * accel_x + accel_y * accel_y);
if (acc_mag > MAX_FORCE) {
float scale = MAX_FORCE / acc_mag;
accel_x *= scale;
accel_y *= scale;
}
vx += accel_x * dt;
vy += accel_y * dt;
float new_speed = sqrtf(vx * vx + vy * vy);
if (new_speed > MAX_SPEED) {
float scale = MAX_SPEED / new_speed;
vx *= scale;
vy *= scale;
}
float damp = g_mouse_present ? VELOCITY_DAMP_ACTIVE : VELOCITY_DAMP_IDLE;
vx *= damp;
vy *= damp;
if (!g_mouse_present) {
float cruise = 80.0f;
float speed_after = sqrtf(vx * vx + vy * vy);
if (speed_after < cruise) {
if (speed_after > 0.0001f) {
float scale = cruise / speed_after;
vx *= scale;
vy *= scale;
} else {
// Re-inject a tiny random push to keep idle motion alive.
float angle = frand() * 6.2831853f;
vx = cosf(angle) * cruise;
vy = sinf(angle) * cruise;
}
}
}
px += vx * dt;
py += vy * dt;
g_positions[i][0] = px;
g_positions[i][1] = py;
g_velocities[i][0] = vx;
g_velocities[i][1] = vy;
}
float verts[MAX_BOIDS * 2];
float inv_w = g_width > 0 ? 1.0f / g_width : 0.0f;
float inv_h = g_height > 0 ? 1.0f / g_height : 0.0f;
for (int i = 0; i < MAX_BOIDS; ++i) {
float screen_x = wrap_mod(g_positions[i][0], (float)g_width);
float screen_y = wrap_mod(g_positions[i][1], (float)g_height);
float x = screen_x * inv_w * 2.0f - 1.0f;
float y = 1.0f - screen_y * inv_h * 2.0f;
verts[i * 2 + 0] = x;
verts[i * 2 + 1] = y;
}
glDisable(GL_DEPTH_TEST);
glUseProgram(g_program);
glUniform1f(g_time_loc, (float)time_sec);
if (g_resolution_loc >= 0) {
glUniform2f(g_resolution_loc, (float)g_width, (float)g_height);
}
glBindVertexArray(g_vao);
glBindBuffer(GL_ARRAY_BUFFER, g_vbo);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(verts), verts);
glDrawArrays(GL_POINTS, 0, MAX_BOIDS);
}
void demo_app_set_active(int active) {
g_active = active ? 1 : 0;
}
void demo_app_update_mouse(float x, float y, int present) {
g_mouse_x = x;
g_mouse_y = y;
g_mouse_present = present;
}
void demo_app_shutdown(void) {
if (g_vbo) {
glDeleteBuffers(1, &g_vbo);
g_vbo = 0;
}
if (g_vao) {
glDeleteVertexArrays(1, &g_vao);
g_vao = 0;
}
if (g_program) {
glDeleteProgram(g_program);
g_program = 0;
}
}
void demo_app_handle_key(int key, int pressed) {
switch (key) {
case 4: if (pressed) reset_boids(); break; // Z
default: (void)pressed; break;
}
}