|
// 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(); |
|
} |
|
} |