diff --git a/include/particle.h b/include/particle.h index 3114940..83c5bc4 100644 --- a/include/particle.h +++ b/include/particle.h @@ -1,7 +1,10 @@ #ifndef PARTICLE_H #define PARTICLE_H +#include #include +#include +#include #define G_CONSTANT 0.207f @@ -18,9 +21,25 @@ typedef struct { Vec3 velocity; Color color; + float dampening; float life; + float size; } Particle; +typedef struct { + Particle *particle; + unsigned long long thread_id; + unsigned long long interval_start; + unsigned long long interval_end; + int alive_count; + float time; + + pthread_mutex_t *mutex; + pthread_cond_t *cond; + int *barrier; + atomic_int exit; +} Physics_params; + // INFO: This adds adder to out, basicaly out + adder void vec_add(Vec3 &out, Vec3 adder) { out.x += adder.x; @@ -35,6 +54,7 @@ Particle particle_init(Vec3 position, Color color) { p.velocity = {0.0f, 0.0f, 0.0f}; p.life = 10; + p.dampening = 0.97f; return p; } @@ -43,19 +63,24 @@ int particle_update(Particle *particles, unsigned long long size, float time) { for (unsigned long long i = 0; i < size; i++) { Particle p = particles[i]; if (p.life == 0) { - particles[i].position = {0.0f, 0.0f, 0.0f}; // Start at center - particles[i].velocity = {(rand() % 100 - 50) / 50.0f, 5.0f, 0.0f}; // Random spray up - particles[i].life = 2.0f; // Give it 2 seconds of life continue; } - p.velocity.y -= (G_CONSTANT * time); + // p.velocity.y -= (G_CONSTANT * time); p.position.x += p.velocity.x * time; p.position.y += p.velocity.y * time; p.position.z += p.velocity.z * time; - if (p.life > 0) p.life -= time; + if (p.position.y <= 0) { + p.position.y = 0; + p.velocity.y *= (-1 * p.dampening); + } + + if (p.life > 0) + p.life -= time; + if (p.life < 0 && p.life >= -1.0f) + p.life = 0; particles[i] = p; alive_count++; @@ -64,4 +89,59 @@ int particle_update(Particle *particles, unsigned long long size, float time) { return alive_count; } +void *t_update(void *args) { + Physics_params *params = (Physics_params *)args; + while (params->exit != 1) { + pthread_mutex_lock(params->mutex); + if (params->(missing) == 0) { + sleep(100); + } + pthread_mutex_unlock(params->mutex); + + params->alive_count = 0; + for (unsigned long long i = params->interval_start; i < params->interval_end; i++) { + Particle p = params->particle[i]; + + if (p.life == 0) { + continue; + } + + p.velocity.y -= (G_CONSTANT * params->time); + + p.position.x += p.velocity.x * params->time; + p.position.y += p.velocity.y * params->time; + p.position.z += p.velocity.z * params->time; + + if (p.position.y <= 0) { + p.position.y = 0; + p.velocity.y *= (-1 * p.dampening); + } + + if (p.life > 0) + p.life -= params->time; + if (p.life < 0 && p.life >= -1.0f) + p.life = 0; + + // DEBUG + if (params->thread_id == 0) { + p.color = {1.0f, 1.0f, 1.0f, 1.0f}; + } else if (params->thread_id == 1) { + p.color = {1.0f, 0.0f, 0.0f, 1.0f}; + } else if (params->thread_id == 2) { + p.color = {0.0f, 1.0f, 0.0f, 1.0f}; + } else if (params->thread_id == 3) { + p.color = {0.0f, 0.0f, 1.0f, 1.0f}; + } else { + p.color = {0.7f, 0.7f, 0.7f, 1.0f}; + ; + } + + params->particle[i] = p; + params->alive_count++; + } + } + + return NULL; +} + #endif // PARTICLE_H diff --git a/psystem b/psystem new file mode 100755 index 0000000..4d84863 Binary files /dev/null and b/psystem differ diff --git a/source/main.cpp b/source/main.cpp index a7cfc78..0a46a60 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -9,18 +9,24 @@ #include "imgui/imgui.h" #include "imgui/imgui_impl_glfw.h" #include "imgui/imgui_impl_opengl3.h" + +#include #include +#include +#include +#include const char FRAGMENT_SHADER_SOURCE[] = "./source/shader/fragment.glsl"; const char VERTEX_SHADER_SOURCE[] = "./source/shader/vertex.glsl"; const char PROGRAM_NAME[] = "Particle Sim"; +const unsigned int THREAD_COUNT = 4; const unsigned int SCR_WIDTH = 1920; const unsigned int SCR_HEIGHT = 1080; const char *glsl_version; -const unsigned int PARTICLE_COUNT = 1000; +const unsigned int PARTICLE_COUNT = 100000; GLFWwindow *window; unsigned int VBO, VAO; @@ -31,6 +37,10 @@ Camera *g_camera = nullptr; AxisRenderer g_axis; GridRenderer g_grid; +pthread_mutex_t mutex; +pthread_cond_t cond; +pthread_barrier_t barrier; + static void cursor_callback(GLFWwindow *w, double xpos, double ypos) { #ifndef NDEBUG // Let ImGui eat mouse when cursor is free @@ -57,6 +67,8 @@ static void mouse_button_callback(GLFWwindow *w, int button, int action, int mod } __attribute__((constructor)) static void init_glfw(void) { + srand(time(NULL)); + if (!glfwInit()) { fprintf(stderr, "GLFW initialization failed\n"); abort(); // program cannot continue @@ -97,17 +109,25 @@ __attribute__((constructor)) static void init_glfw(void) { glEnableVertexAttribArray(0); // Position: 3 floats, starts at 0 - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void *)0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 13 * sizeof(float), (void *)0); glEnableVertexAttribArray(0); // Color: 4 floats, starts after 6 floats (Position + Velocity) - glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void *)(6 * sizeof(float))); + glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 13 * sizeof(float), (void *)(6 * sizeof(float))); glEnableVertexAttribArray(1); // Life: 1 float, starts after 10 floats (Position + Velocity + Color) - glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 11 * sizeof(float), (void *)(10 * sizeof(float))); + glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, 13 * sizeof(float), (void *)(10 * sizeof(float))); glEnableVertexAttribArray(2); + // Dampening: 1 float, starts after 11 floars (Positon + Velocity + Color + Life) + glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, 13 * sizeof(float), (void *)(11 * sizeof(float))); + glEnableVertexAttribArray(3); + + // Size: 1 float, starts after 11 floars (Positon + Velocity + Color + Life + Dampening) + glVertexAttribPointer(4, 1, GL_FLOAT, GL_FALSE, 13 * sizeof(float), (void *)(12 * sizeof(float))); + glEnableVertexAttribArray(4); + glBufferData(GL_ARRAY_BUFFER, sizeof(Particle) * PARTICLE_COUNT, NULL, GL_DYNAMIC_DRAW); char *vertex_shader_source = read_file(VERTEX_SHADER_SOURCE); @@ -179,6 +199,9 @@ int main() { init_imgui(); #endif + pthread_t *threads = (pthread_t *)malloc(sizeof(pthread_t) * THREAD_COUNT); + Physics_params *thread_params = (Physics_params *)malloc(sizeof(Physics_params) * THREAD_COUNT); + Camera camera = camera_init(glm::vec3(2.0f, 2.0f, 5.0f), SCR_WIDTH, SCR_HEIGHT); g_camera = &camera; @@ -197,8 +220,16 @@ int main() { snprintf(errLog, 128, "There was a issue allocating memory for the particle list\n"); } else { for (unsigned long long i = 0; i < PARTICLE_COUNT; i++) { - particles[i] = particle_init({0.0, 0.0, 0.0}, {1.0, 1.0, 1.0, 1.0}); - particles[i].life = -1.0f; // Start with imortal particles + + float x, y, z; + + x = (float)(rand() % 200) / 100; // [0.0 , 2.0] + y = (float)(rand() % 200) / 100; // [0.0 , 2.0] + z = (float)(rand() % 200) / 100; // [0.0 , 2.0] + + particles[i] = particle_init({x, y, z}, {x, y, z, 1.0}); + particles[i].life = -2.0f; // Start with imortal particles + particles[i].size = 20; } } @@ -207,6 +238,24 @@ int main() { float delta_time; int alive_count = 0; + pthread_mutex_init(&mutex, NULL); + pthread_cond_init(&cond, NULL); + pthread_barrier_init(&barrier, NULL, THREAD_COUNT + 1); + + for (int i = 0; i < THREAD_COUNT; i++) { + thread_params[i].particle = particles; + thread_params[i].interval_start = (PARTICLE_COUNT / THREAD_COUNT) * i; + thread_params[i].interval_end = (PARTICLE_COUNT / THREAD_COUNT) * (i + 1); + + thread_params[i].time = 0; + thread_params[i].exit = 0; + thread_params[i].alive_count = 0; + + thread_params[i].thread_id = i; + thread_params[i].mutex = global_mutex; + pthread_create(&threads[i], NULL, t_update, &thread_params[i]); + } + while (!glfwWindowShouldClose(window)) { current_time = static_cast(glfwGetTime()); delta_time = current_time - last_time; @@ -235,7 +284,11 @@ int main() { glm::mat4 projection = camera_projection(camera, aspect); glm::mat4 mvp = projection * view; // model is identity - alive_count = particle_update(particles, PARTICLE_COUNT, delta_time); + alive_count = 0; + for (int i = 0; i < THREAD_COUNT; i++) { + thread_params->time = delta_time; + thread_params->run = 1; + } // Render glClearColor(0.03f, 0.03f, 0.03f, 1.0f); @@ -264,7 +317,8 @@ int main() { ImGui::Text("Camera: %.2f %.2f %.2f", (double)camera.position.x, (double)camera.position.y, (double)camera.position.z); ImGui::Text("Alive particles: %d", alive_count); if (particles) - ImGui::Text("P[0]: %.2f %.2f %.2f", (double)particles[0].position.x, (double)particles[0].position.y, (double)particles[0].position.z); + ImGui::Text("P[0]: %3.2f %3.2f %3.2f, Life: %0.2f", (double)particles[0].position.x, (double)particles[0].position.y, (double)particles[0].position.z, + (double)particles[0].life); ImGui::Separator(); ImGui::Text("Left-click to capture mouse"); ImGui::Text("ESC = release mouse / quit"); diff --git a/source/shader/fragment.glsl b/source/shader/fragment.glsl index 2387eed..df91e55 100644 --- a/source/shader/fragment.glsl +++ b/source/shader/fragment.glsl @@ -4,7 +4,10 @@ in vec4 vColor; out vec4 FragColor; void main() { + // Soft circular point — discard corners to make round dots + vec2 coord = gl_PointCoord - vec2(0.5); + if (dot(coord, coord) > 0.25) + discard; + FragColor = vColor; } - - diff --git a/source/shader/vertex.glsl b/source/shader/vertex.glsl index b249c40..63e50aa 100644 --- a/source/shader/vertex.glsl +++ b/source/shader/vertex.glsl @@ -1,18 +1,26 @@ #version 330 core -layout(location = 0) in vec3 aPos; // Position -layout(location = 1) in vec4 aColor; // Color (RGBA) -layout(location = 2) in float aLife; // Life +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec4 aColor; +layout(location = 2) in float aLife; +layout(location = 4) in float size; out vec4 vColor; +uniform mat4 uMVP; +uniform float uScreenHeight; + void main() { - float alpha = aLife; - if (aLife > 0.0) { - alpha = aLife / 10.0; // Fade mortal particles - } else if (aLife < 0.0) { - alpha = 1.0; // Eternal particles are fully opaque + float alpha; + if (aLife < 0.0) { + alpha = 1.0; // immortal — fully opaque + } else { + alpha = clamp(aLife / 2.0, 0.0, 1.0); // fade over last 2 seconds of life } + vColor = vec4(aColor.rgb, aColor.a * alpha); - gl_Position = vec4(aPos, 1.0); - gl_PointSize = 10.0; // Make them big enough to see! + vec4 clip_pos = uMVP * vec4(aPos, 1.0); + gl_Position = clip_pos; + + float base_size = 0.2 + size; + gl_PointSize = max(1.0, base_size / clip_pos.w); }