Skip to content

Instantly share code, notes, and snippets.

@vivekgalatage
Created December 5, 2024 07:10
Show Gist options
  • Save vivekgalatage/9af1326b879153698046623ae9fb5ad7 to your computer and use it in GitHub Desktop.
Save vivekgalatage/9af1326b879153698046623ae9fb5ad7 to your computer and use it in GitHub Desktop.

Build instructions

NOTE: Only built and tested for macOS

on macOS

brew install glew
brew install glfw

The versions may be different on your system when you try this

In order to get the include and lib directory for both glew and glfw, please use

brew list glew
brew list glfw

build:

gcc -o particles main.c \
    -L/opt/homebrew/Cellar/glew/2.2.0_1/lib/ -lGLEW \
    -L/opt/homebrew/Cellar/glfw/3.4/lib/ -lGLFW \
    -framework OpenGL \
    -I/opt/homebrew/Cellar/glew/2.2.0_1/include/ \
    -I/opt/homebrew/Cellar/glfw/3.4/include/
// particle_system.c
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef __APPLE__
#define GLFW_INCLUDE_NONE
#define GL_SILENCE_DEPRECATION
#endif
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define NUM_PARTICLES 15000
// Function to get GLSL version string based on platform
const char *getGLSLVersionString() {
#ifdef __APPLE__
return "#version 410\n"; // macOS supports up to 4.1
#else
return "#version 430\n"; // Windows/Linux can use newer versions
#endif
}
// Vertex Shader for updating particles
const char *getUpdateVertexShaderSource() {
static char shader[4096];
snprintf(
shader, sizeof(shader),
"%s"
"layout(location = 0) in vec2 position;\n"
"layout(location = 1) in vec4 color;\n"
"layout(location = 2) in vec2 velocity;\n"
"\n"
"out vec2 updatePosition;\n"
"out vec4 updateColor;\n"
"out vec2 updateVelocity;\n"
"\n"
"uniform vec2 mousePos;\n"
"uniform float deltaTime;\n"
"\n"
"float rand(vec2 co) {\n"
" return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);\n"
"}\n"
"\n"
"void main() {\n"
" vec2 pos = position;\n"
" vec2 vel = velocity;\n"
"\n"
" vec2 toMouse = pos - mousePos;\n"
" float dist = length(toMouse);\n"
" float repulsionStrength = 0.5;\n"
" float repulsionRadius = 0.3;\n"
"\n"
" if (dist < repulsionRadius) {\n"
" vec2 repulsion = normalize(toMouse) * repulsionStrength * (1.0 "
"- dist/repulsionRadius);\n"
" vel += repulsion;\n"
" }\n"
"\n"
" pos += vel * deltaTime;\n"
"\n"
" if (abs(pos.x) > 1.0) {\n"
" vel.x = -vel.x * 0.8;\n"
" pos.x = sign(pos.x) * 1.0;\n"
" }\n"
" if (abs(pos.y) > 1.0) {\n"
" vel.y = -vel.y * 0.8;\n"
" pos.y = sign(pos.y) * 1.0;\n"
" }\n"
"\n"
" vel += vec2(\n"
" rand(pos + vec2(deltaTime, 0.0)) * 0.2 - 0.1,\n"
" rand(pos + vec2(0.0, deltaTime)) * 0.2 - 0.1\n"
" ) * deltaTime;\n"
"\n"
" vel *= 0.99;\n"
"\n"
" float speed = length(vel);\n"
" vec4 newColor = vec4(\n"
" 0.5 + speed * 2.0,\n"
" 0.3 + speed * 1.0,\n"
" 1.0,\n"
" 1.0\n"
" );\n"
"\n"
" updatePosition = pos;\n"
" updateVelocity = vel;\n"
" updateColor = newColor;\n"
"}\n",
getGLSLVersionString());
return shader;
}
// Vertex Shader for rendering
const char *getRenderVertexShaderSource() {
static char shader[1024];
snprintf(shader, sizeof(shader),
"%s"
"layout(location = 0) in vec2 position;\n"
"layout(location = 1) in vec4 color;\n"
"layout(location = 2) in vec2 velocity;\n"
"\n"
"out vec4 fragColor;\n"
"\n"
"void main() {\n"
" gl_Position = vec4(position, 0.0, 1.0);\n"
" fragColor = color;\n"
" float speed = length(velocity);\n"
" gl_PointSize = 10.0 + speed * 10.0;\n"
"}\n",
getGLSLVersionString());
return shader;
}
// Fragment Shader
const char *getFragmentShaderSource() {
static char shader[1024];
snprintf(shader, sizeof(shader),
"%s"
"in vec4 fragColor;\n"
"out vec4 outColor;\n"
"\n"
"void main() {\n"
" vec2 circCoord = 2.0 * gl_PointCoord - 1.0;\n"
" float alpha = 1.0 - length(circCoord);\n"
" outColor = fragColor;\n"
" outColor.a *= alpha;\n"
"}\n",
getGLSLVersionString());
return shader;
}
typedef struct {
float position[2];
float color[4];
float velocity[2];
} Particle;
typedef struct {
GLFWwindow *window;
GLuint updateProgram;
GLuint renderProgram;
GLuint particleBuffers[2];
GLuint vaos[2];
int currentBuffer;
int majorVersion;
int minorVersion;
} RenderState;
void errorCallback(int error, const char *description) {
fprintf(stderr, "GLFW Error %d: %s\n", error, description);
}
GLuint createShader(const char *source, GLenum type) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, NULL);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(shader, sizeof(infoLog), NULL, infoLog);
fprintf(stderr, "Shader compilation failed: %s\n", infoLog);
}
return shader;
}
GLuint createUpdateProgram() {
GLuint vertexShader =
createShader(getUpdateVertexShaderSource(), GL_VERTEX_SHADER);
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
const char *varyings[] = {"updatePosition", "updateColor", "updateVelocity"};
glTransformFeedbackVaryings(program, 3, varyings, GL_INTERLEAVED_ATTRIBS);
glLinkProgram(program);
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
char infoLog[512];
glGetProgramInfoLog(program, sizeof(infoLog), NULL, infoLog);
fprintf(stderr, "Program linking failed: %s\n", infoLog);
}
glDeleteShader(vertexShader);
return program;
}
GLuint createRenderProgram() {
GLuint vertexShader =
createShader(getRenderVertexShaderSource(), GL_VERTEX_SHADER);
GLuint fragmentShader =
createShader(getFragmentShaderSource(), GL_FRAGMENT_SHADER);
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glLinkProgram(program);
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if (!success) {
char infoLog[512];
glGetProgramInfoLog(program, sizeof(infoLog), NULL, infoLog);
fprintf(stderr, "Program linking failed: %s\n", infoLog);
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
return program;
}
void initParticles(RenderState *state) {
Particle *particles = malloc(NUM_PARTICLES * sizeof(Particle));
srand(time(NULL));
for (int i = 0; i < NUM_PARTICLES; i++) {
particles[i].position[0] = (float)rand() / RAND_MAX * 2.0f - 1.0f;
particles[i].position[1] = (float)rand() / RAND_MAX * 2.0f - 1.0f;
particles[i].velocity[0] = ((float)rand() / RAND_MAX * 2.0f - 1.0f) * 0.1f;
particles[i].velocity[1] = ((float)rand() / RAND_MAX * 2.0f - 1.0f) * 0.1f;
particles[i].color[0] = 0.5f;
particles[i].color[1] = 0.3f;
particles[i].color[2] = 1.0f;
particles[i].color[3] = 1.0f;
}
glGenVertexArrays(2, state->vaos);
glGenBuffers(2, state->particleBuffers);
for (int i = 0; i < 2; i++) {
glBindBuffer(GL_ARRAY_BUFFER, state->particleBuffers[i]);
glBufferData(GL_ARRAY_BUFFER, NUM_PARTICLES * sizeof(Particle), particles,
GL_DYNAMIC_DRAW);
glBindVertexArray(state->vaos[i]);
glBindBuffer(GL_ARRAY_BUFFER, state->particleBuffers[i]);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Particle),
(void *)0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(Particle),
(void *)(2 * sizeof(float)));
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Particle),
(void *)(6 * sizeof(float)));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
}
free(particles);
}
void cleanup(RenderState *state) {
glDeleteProgram(state->updateProgram);
glDeleteProgram(state->renderProgram);
glDeleteBuffers(2, state->particleBuffers);
glDeleteVertexArrays(2, state->vaos);
glfwDestroyWindow(state->window);
glfwTerminate();
}
RenderState *initOpenGL() {
RenderState *state = malloc(sizeof(RenderState));
state->currentBuffer = 0;
if (!glfwInit()) {
fprintf(stderr, "Failed to initialize GLFW\n");
return NULL;
}
glfwSetErrorCallback(errorCallback);
// Determine OpenGL version based on platform
#ifdef __APPLE__
state->majorVersion = 4;
state->minorVersion = 1;
#else
state->majorVersion = 4;
state->minorVersion = 3;
#endif
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, state->majorVersion);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, state->minorVersion);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#ifdef __APPLE__
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif
state->window = glfwCreateWindow(800, 600, "Particle System", NULL, NULL);
if (!state->window) {
fprintf(stderr, "Failed to create window\n");
glfwTerminate();
free(state);
return NULL;
}
glfwMakeContextCurrent(state->window);
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEW\n");
glfwDestroyWindow(state->window);
glfwTerminate();
free(state);
return NULL;
}
printf("OpenGL Version: %s\n", glGetString(GL_VERSION));
printf("GLSL Version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
state->updateProgram = createUpdateProgram();
state->renderProgram = createRenderProgram();
initParticles(state);
return state;
}
int main() {
RenderState *state = initOpenGL();
if (!state) {
return -1;
}
double lastTime = glfwGetTime();
while (!glfwWindowShouldClose(state->window)) {
double currentTime = glfwGetTime();
float deltaTime = (float)(currentTime - lastTime);
lastTime = currentTime;
double mouseX, mouseY;
glfwGetCursorPos(state->window, &mouseX, &mouseY);
int width, height;
glfwGetWindowSize(state->window, &width, &height);
float normalizedX = (2.0f * mouseX) / width - 1.0f;
float normalizedY = 1.0f - (2.0f * mouseY) / height;
glUseProgram(state->updateProgram);
glUniform2f(glGetUniformLocation(state->updateProgram, "mousePos"),
normalizedX, normalizedY);
glUniform1f(glGetUniformLocation(state->updateProgram, "deltaTime"),
deltaTime);
glBindVertexArray(state->vaos[state->currentBuffer]);
glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
state->particleBuffers[1 - state->currentBuffer]);
glEnable(GL_RASTERIZER_DISCARD);
glBeginTransformFeedback(GL_POINTS);
glDrawArrays(GL_POINTS, 0, NUM_PARTICLES);
glEndTransformFeedback();
glDisable(GL_RASTERIZER_DISCARD);
glEnable(GL_PROGRAM_POINT_SIZE);
glEnable(GL_POINT_SMOOTH);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(state->renderProgram);
glBindVertexArray(state->vaos[1 - state->currentBuffer]);
glDrawArrays(GL_POINTS, 0, NUM_PARTICLES);
state->currentBuffer = 1 - state->currentBuffer;
glfwSwapBuffers(state->window);
glfwPollEvents();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment