--- /dev/null
+#include <cstdio>
+#include <cstdlib>
+#include <cstdbool>
+#include <cassert>
+#include <malloc.h>
+#include <alloca.h>
+#include <unistd.h>
+#include <math.h>
+#include <time.h>
+
+#include <GLES3/gl3.h>
+#include <GLFW/glfw3.h>
+
+#include "types.h"
+#include "container.h"
+#include "utils.h"
+#include "physics.h"
+#include "ui.h"
+#include "log.h"
+#include "settings.h"
+
+// TODO(Brendan): Maybe this can be removed because it isn't really necessary?
+internal void
+glfw_key_handler(GLFWwindow* window, i32 key, i32 scancode, i32 action, i32 mods)
+{
+ if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
+ glfwSetWindowShouldClose(window, 1);
+
+ if (key == GLFW_KEY_F && action == GLFW_PRESS)
+ {
+ persist bool is_fullscreen = false;
+ is_fullscreen = !is_fullscreen;
+
+ if (is_fullscreen)
+ {
+ GLFWmonitor* primary_mon = glfwGetPrimaryMonitor();
+ const GLFWvidmode* vidmode = glfwGetVideoMode(primary_mon);
+ glfwSetWindowMonitor(window, primary_mon, 0, 0, vidmode->width, vidmode->height, GLFW_DONT_CARE);
+ }
+ else
+ {
+ glfwSetWindowMonitor(window, NULL, 0, 0, 800, 600, GLFW_DONT_CARE);
+ }
+ }
+}
+
+internal void
+glfw_error_handler(i32 error, const char* desc)
+{
+ panic_and_die("GLFW Error (%d): %s\n", error, desc);
+}
+
+GLFWwindow* window;
+internal void
+init_glfw()
+{
+ logprint(LOG_LEVEL_INFO, "Initializing GLFW");
+
+ if (!glfwInit()) panic_and_die("Failed to initalize GLFW.");
+ glfwSetErrorCallback(glfw_error_handler);
+
+ window = glfwCreateWindow(1600, 900, "N-Body Simulation", NULL, NULL);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+ glfwMakeContextCurrent(window);
+
+ glfwSwapInterval(1);
+ glfwSetKeyCallback(window, glfw_key_handler);
+
+ // NOTE(Brendan): This may need to be changed if the screen orientation changes.
+ glEnable(GL_CULL_FACE);
+ glFrontFace(GL_CW);
+ glCullFace(GL_BACK);
+
+ glEnable(GL_TEXTURE_2D);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+internal void
+deinit_glfw()
+{
+ glfwDestroyWindow(window);
+ glfwTerminate();
+}
+
+#define CIRCLE_POINT_COUNT 12 // NOTE(Brendan): Treat a circle as a many-sided polygon.
+
+// NOTE(Brendan): Returns the VAO where the mesh data was bound.
+internal GLuint
+create_circle_mesh()
+{
+ logprint(LOG_LEVEL_INFO, "Generating circle mesh");
+
+ GLuint vao;
+ glGenVertexArrays(1, &vao);
+ glBindVertexArray(vao);
+
+ V2f circle_points[CIRCLE_POINT_COUNT] = {};
+ foreach (i, 0, CIRCLE_POINT_COUNT)
+ {
+ f32 t = (f32) i / (f32) CIRCLE_POINT_COUNT;
+ circle_points[i].x = cos(t * 2 * 3.1415926535897);
+ circle_points[i].y = sin(t * 2 * 3.1415926535897);
+ }
+
+ GLuint vertex_buffer;
+ glGenBuffers(1, &vertex_buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(circle_points), &circle_points, GL_STATIC_DRAW);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 2, GL_FLOAT, false, sizeof(V2f), (void *) offsetof(V2f, x));
+ glBindBuffer(GL_ARRAY_BUFFER, -1);
+
+ u8 circle_indicies[CIRCLE_POINT_COUNT] = {};
+ foreach(i, 0, CIRCLE_POINT_COUNT) circle_indicies[i] = i;
+
+ GLuint index_buffer;
+ glGenBuffers(1, &index_buffer);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(circle_indicies), &circle_indicies, GL_STATIC_DRAW);
+
+ glBindVertexArray(-1);
+
+ return vao;
+}
+
+struct SimState;
+struct ThreadData
+{
+ i32 id;
+ SimState* state;
+};
+
+struct SimState
+{
+ Array<Body> bodies;
+ QuadTree<Body> qt_bodies;
+ FixedArenaAllocator<QuadTree<Body>> qt_body_allocator;
+
+ Camera camera;
+};
+
+// @CLEANUP
+internal f64 TEMP_dt = 0;
+
+internal void
+update_bodies_partial(SimState *state, i32 index)
+{
+ i32 low = 0;
+ i32 high = global_settings.body_count;
+
+ foreach (i, low, high) body_accumulate_move(&state->bodies[i], &state->qt_bodies, TEMP_dt);
+ foreach (i, low, high) body_apply_move(&state->bodies[i], TEMP_dt);
+}
+
+internal void
+sim_state_init(SimState* state)
+{
+ // NOTE(Brendan): Need to initialize the array since it does not get constructed because I refuse to use the 'new' keyword. alloc<T> uses malloc under the hood and cannot initialize the result.
+ state->bodies.init(global_settings.body_count);
+
+ foreach (i, 0, global_settings.body_count)
+ {
+ Body tmp_body;
+ tmp_body.body_type = (rand() % global_settings.body_type_count);
+ tmp_body.pos = V2f{ randf(-1000, 1000), randf(-1000, 1000) };
+ tmp_body.vel = V2f{ 0.0f, 0.0f };
+
+ BodyMassRange bmr = global_settings.body_mass_range[tmp_body.body_type];
+ tmp_body.mass = randf(bmr.min, bmr.max);
+
+ state->bodies.push(tmp_body);
+ }
+
+ state->qt_body_allocator.init(global_settings.body_count);
+ state->qt_bodies.init(AABB { -2000, -2000, 4000, 4000 });
+
+ state->camera.scale = 1.0f;
+}
+
+// NOTE(Brendan): dt is expected to be in units of "per second".
+internal void
+update(SimState* state, f64 dt)
+{
+ glfwGetWindowSize(window, &state->camera.window_width, &state->camera.window_height);
+
+ persist const f32 camera_move_speed = 6;
+ if (glfwGetKey(window, GLFW_KEY_UP)) state->camera.offset.y -= camera_move_speed;
+ if (glfwGetKey(window, GLFW_KEY_DOWN)) state->camera.offset.y += camera_move_speed;
+ if (glfwGetKey(window, GLFW_KEY_LEFT)) state->camera.offset.x -= camera_move_speed;
+ if (glfwGetKey(window, GLFW_KEY_RIGHT)) state->camera.offset.x += camera_move_speed;
+ if (glfwGetKey(window, GLFW_KEY_Q)) state->camera.scale *= 1.02f;
+ if (glfwGetKey(window, GLFW_KEY_A)) state->camera.scale /= 1.02f;
+
+ state->qt_bodies.init(AABB { -2000, -2000, 4000, 4000 });
+ state->qt_body_allocator.reset();
+ For (state->bodies) state->qt_bodies.insert(&it, &state->qt_body_allocator);
+
+ TEMP_dt = dt;
+ update_bodies_partial(state, 0);
+}
+
+// NOTE CLEANUP(Brendan): Bunch of graphics state that should go elsewhere.
+internal GLuint body_buffer;
+internal GLuint circle_mesh;
+internal GLuint body_program;
+
+// TEMPORARY
+internal i32 frame_rate = 0;
+
+internal void
+draw(SimState* state)
+{
+ // NOTE(Brendan): Rebuffer all the body data to the GPU.
+ glBindBuffer(GL_ARRAY_BUFFER, body_buffer);
+ glBufferSubData(GL_ARRAY_BUFFER, 0, state->bodies.size_in_bytes(), state->bodies.data);
+ glBindBuffer(GL_ARRAY_BUFFER, -1);
+
+ glUseProgram(body_program);
+
+ mat4 ortho_mat;
+ camera_ortho_mat4(state->camera, &ortho_mat);
+ GLuint ortho_mat_loc = glGetUniformLocation(body_program, "u_proj");
+ glUniformMatrix4fv(ortho_mat_loc, 1, false, (f32 *) ortho_mat);
+ glViewport(0, 0, state->camera.window_width, state->camera.window_height);
+
+ mat4 camera_mat;
+ camera_world_mat4(state->camera, &camera_mat);
+ GLuint camera_mat_loc = glGetUniformLocation(body_program, "u_camera");
+ glUniformMatrix4fv(camera_mat_loc, 1, false, (f32 *) camera_mat);
+
+ // NOTE(Brendan): Clear the screen.
+ glClearColor(0.1, 0.1, 0.1, 1.0);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ // NOTE(Brendan): Draw the bodies.
+ glBindVertexArray(circle_mesh);
+ glDrawElementsInstanced(GL_TRIANGLE_FAN, CIRCLE_POINT_COUNT, GL_UNSIGNED_BYTE, 0, state->bodies.count);
+ glBindVertexArray(-1);
+
+ font_set_projection_matrix(ortho_mat);
+ char fps_str[64];
+ snprintf(fps_str, 63, "FPS: %d", frame_rate);
+ font_print(0, 32, fps_str);
+
+ // NOTE(Brendan): Present the changes to the screen.
+ glfwSwapBuffers(window);
+}
+
+internal void
+loop(SimState* state)
+{
+ f64 last_time = glfwGetTime();
+ f64 curr_time = last_time;
+ f64 delta = 0.0;
+
+ f64 frame_delta = 0.0;
+ i32 frame_count = 0;
+ clock_t total_clock_delta = 0;
+
+ while (!glfwWindowShouldClose(window))
+ {
+ curr_time = glfwGetTime();
+ delta = curr_time - last_time;
+ last_time = curr_time;
+
+ clock_t before_update = clock();
+ update(state, delta);
+ clock_t after_update = clock();
+ total_clock_delta += (after_update - before_update);
+
+ frame_count += 1;
+ draw(state);
+
+ frame_delta += delta;
+ if (frame_delta >= 1.0)
+ {
+ total_clock_delta = 0;
+ frame_delta -= 1.0;
+ frame_rate = frame_count;
+ frame_count = 0;
+ }
+
+ glfwPollEvents();
+ }
+}
+
+i32
+main(i32 argc, char* argv[])
+{
+ srand(time(NULL));
+
+ if (argc > 1)
+ {
+ load_settings_from_file(&global_settings, argv[1]);
+ }
+ else
+ {
+ generate_random_settings(&global_settings);
+ }
+
+ init_glfw();
+ defer { deinit_glfw(); };
+
+ circle_mesh = create_circle_mesh();
+
+ init_font("res/font/Hack-Regular.ttf");
+
+ body_program = create_program(load_shader(GL_VERTEX_SHADER, "res/shaders/planet_vert.glsl"),
+ load_shader(GL_FRAGMENT_SHADER, "res/shaders/planet_frag.glsl"));
+ glUseProgram(body_program);
+
+ GLuint planet_colors_loc = glGetUniformLocation(body_program, "u_planet_colors");
+ GLfloat* planet_colors = (GLfloat *) global_settings.body_colors.data;
+ glUniform3fv(planet_colors_loc, global_settings.body_type_count, planet_colors);
+
+ auto state = alloc<SimState>();
+ sim_state_init(state);
+
+ // NOTE(Brendan): Other OpenGL initializations that could probably happen earlier.
+ {
+ glBindVertexArray(circle_mesh);
+ defer { glBindVertexArray(-1); };
+
+ glGenBuffers(1, &body_buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, body_buffer);
+ glBufferData(GL_ARRAY_BUFFER, state->bodies.size_in_bytes(), state->bodies.data, GL_STREAM_DRAW);
+
+ foreach (i, 1, 4)
+ {
+ glEnableVertexAttribArray(i);
+ glVertexAttribDivisor(i, 1);
+ }
+ glVertexAttribPointer(1, 2, GL_FLOAT, false, sizeof(Body), (void *) offsetof(Body, pos.x));
+ glVertexAttribPointer(2, 1, GL_FLOAT, false, sizeof(Body), (void *) offsetof(Body, mass));
+ glVertexAttribPointer(3, 1, GL_BYTE, false, sizeof(Body), (void *) offsetof(Body, body_type));
+
+ glBindBuffer(GL_ARRAY_BUFFER, -1);
+ }
+
+ loop(state);
+
+ return 0;
+}