diff --git a/include/particle.h b/include/particle.h new file mode 100644 index 0000000..634cfab --- /dev/null +++ b/include/particle.h @@ -0,0 +1,69 @@ +#ifndef PARTICLE_H +#define PARTICLE_H + +#include + +#define G_CONSTANT 9.807f + +typedef struct { + float x, y, z; +} Vec3; + +typedef struct { + float r, g, b, a; +} Color; + +typedef struct { + Vec3 position; + Vec3 velocity; + Color color; + + float life; +} Particle; + +// INFO: This adds adder to out, basicaly out + adder +void vec_add(Vec3 &out, Vec3 adder) { + out.x += adder.x; + out.y += adder.y; + out.z += adder.z; +} + +Particle particle_init(Vec3 position, Color color) { + Particle p; + p.position = position; + p.color = color; + p.velocity = {0.0f, 0.0f, 0.0f}; + + p.life = 10; + return p; +} + +int particle_update(Particle *particles, unsigned long long size, float time) { + int alive_count = 0; + 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.position.x += p.velocity.x * time; + p.position.y += p.velocity.y * time; + p.position.z += p.velocity.z * time; + + vec_add(p.position, (p.velocity)); + + if (p.life > 0) p.life -= time; + + particles[i] = p; + alive_count++; + } + + return alive_count; +} + +#endif // PARTICLE_H diff --git a/source/main.cpp b/source/main.cpp index 60b8d09..8e9281a 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -1,20 +1,24 @@ #include "main.h" #include "io.h" +#include "particle.h" // ImGui includes #include "imgui/imgui.h" #include "imgui/imgui_impl_glfw.h" #include "imgui/imgui_impl_opengl3.h" +#include const char FRAGMENT_SHADER_SOURCE[] = "./source/shader/fragment.glsl"; const char VERTEX_SHADER_SOURCE[] = "./source/shader/vertex.glsl"; -const char PROGRAM_NAME[] = "OpenGL Template"; +const char PROGRAM_NAME[] = "Particle Sim"; const unsigned int SCR_WIDTH = 1920; const unsigned int SCR_HEIGHT = 1080; const char *glsl_version; +const unsigned int PARTICLE_COUNT = 1000; + GLFWwindow *window; unsigned int VBO, VAO; unsigned int shader; @@ -56,7 +60,20 @@ __attribute__((constructor)) static void init_glfw(void) { glBindBuffer(GL_ARRAY_BUFFER, VBO); glEnableVertexAttribArray(0); - glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *)0); + + // Position: 3 floats, starts at 0 + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 11 * 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))); + 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))); + glEnableVertexAttribArray(2); + + glBufferData(GL_ARRAY_BUFFER, sizeof(Particle) * PARTICLE_COUNT, NULL, GL_DYNAMIC_DRAW); char *vertex_shader_source = read_file(VERTEX_SHADER_SOURCE); char *fragment_shader_source = read_file(FRAGMENT_SHADER_SOURCE); @@ -84,31 +101,51 @@ __attribute__((constructor)) static void init_glfw(void) { __attribute__((destructor)) static void shutdown_glfw(void) { glDeleteVertexArrays(1, &VAO); glDeleteBuffers(1, &VBO); - glfwDestroyWindow(window); + glfwDestroyWindow(window); glfwTerminate(); } static inline void init_imgui() { - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGui::StyleColorsDark(); - ImGui_ImplGlfw_InitForOpenGL(window, true); - ImGui_ImplOpenGL3_Init(glsl_version); + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGui::StyleColorsDark(); + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init(glsl_version); } int main() { #ifndef NDEBUG - init_imgui(); + init_imgui(); #endif + Particle *particles = (Particle *)malloc(sizeof(Particle) * PARTICLE_COUNT); + + char errLog[128]; + if (particles == NULL) { + snprintf(errLog, 128, "There was a issue allocating memory for the particle list\n"); + } else { + snprintf(errLog, 128, "Successfully allocated memory for the particle list\n"); + + 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 current_time, last_time, delta_time; + int alive_count = 0; while (!glfwWindowShouldClose(window)) { + current_time = static_cast(glfwGetTime()); + delta_time = current_time - last_time; + last_time = current_time; + // Poll events glfwPollEvents(); #ifndef NDEBUG - if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { - glfwSetWindowShouldClose(window, true); - } + if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { + glfwSetWindowShouldClose(window, true); + } // Start ImGui frame ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); @@ -117,8 +154,21 @@ int main() { // Create ImGui window ImGui::Begin("Debug"); ImGui::Text("FPS: %.1f", static_cast(ImGui::GetIO().Framerate)); + ImGui::Text("Particle %d: X=%3.2f Y=%3.2f Z=%3.2f",0, particles[0].position.x, particles[0].position.y, particles[0].position.z); + ImGui::Text("Thread Status: "); + ImGui::TextColored({0.0, 0.7f ,0.0, 1.0}, "READY\n"); + ImGui::Text("%s", errLog); ImGui::End(); #endif + alive_count = particle_update(particles, PARTICLE_COUNT, delta_time); + + glUseProgram(shader); + + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(Particle) * PARTICLE_COUNT, particles); + + glBindVertexArray(VAO); + glDrawArrays(GL_POINTS, 0, alive_count); // Render glClearColor(0.03f, 0.03f, 0.03f, 1.0f); @@ -134,6 +184,6 @@ int main() { glfwSwapBuffers(window); } + free(particles); return 0; } - diff --git a/source/shader/vertex.glsl b/source/shader/vertex.glsl index 1b0623f..b249c40 100644 --- a/source/shader/vertex.glsl +++ b/source/shader/vertex.glsl @@ -1,9 +1,18 @@ #version 330 core -layout (location = 0) in vec2 aPos; +layout(location = 0) in vec3 aPos; // Position +layout(location = 1) in vec4 aColor; // Color (RGBA) +layout(location = 2) in float aLife; // Life -out vec3 vColor; +out vec4 vColor; void main() { - gl_Position = vec4(aPos, 0.0, 1.0); + 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 + } + vColor = vec4(aColor.rgb, aColor.a * alpha); + gl_Position = vec4(aPos, 1.0); + gl_PointSize = 10.0; // Make them big enough to see! } -