#include "glad/glad.h" #include "glm/glm.hpp" #include "glm/gtc/matrix_transform.hpp" #include "glm/gtc/type_ptr.hpp" #include #include #include #include #include #include #include #include #include "camera.hpp" #include "shader.hpp" #include "sphere.hpp" #include "stb_image.h" float deltaTime = 0.0f; // Time between current frame and last frame float lastFrame = 0.0f; // Time of last frame float lastX = 600 / 2.0f; float lastY = 800 / 2.0f; Camera camera(glm::vec3(1.0f, 1.0f, 1.0f)); bool firstMouse = true; struct Sector { int id; float floor, ceil; size_t firstwall, nwalls; }; struct Wall { glm::vec2 v1, v2; // Index of the vertices int portalID; // -1 if solid, else the ID of the sector on the other side }; struct { struct { struct Sector arr[32]; size_t n; } sectors; struct { struct Wall arr[128]; size_t n; } walls; } state; struct MapVertex { glm::vec3 pos; glm::vec2 tex; }; std::vector map_mesh; static int load_sectors(const char *path) { // Sector 0 is usually reserved/null state.sectors.n = 1; state.walls.n = 0; FILE *f = fopen(path, "r"); if (!f) { printf("Could not open file: %s\n", path); return -1; } enum { SCAN_SECTOR, SCAN_WALL, SCAN_NONE } ss = SCAN_NONE; char line[1024]; while (fgets(line, sizeof(line), f)) { char *p = line; // Trim leading whitespace while (isspace(*p)) p++; // Skip comments and empty lines if (!*p || *p == '#') continue; // Check for section headers like [SECTOR] if (*p == '[') { if (strstr(p, "[SECTOR]")) ss = SCAN_SECTOR; else if (strstr(p, "[WALL]")) ss = SCAN_WALL; continue; } // Parse data based on current section if (ss == SCAN_SECTOR) { struct Sector *s = &state.sectors.arr[state.sectors.n++]; // Format: ID, FirstWallIndex, NumWalls, FloorZ, CeilZ if (sscanf(p, "%d %zu %zu %f %f", &s->id, &s->firstwall, &s->nwalls, &s->floor, &s->ceil) != 5) { fclose(f); return -5; } } else if (ss == SCAN_WALL) { struct Wall *w = &state.walls.arr[state.walls.n++]; // Format: x1, y1, x2, y2, PortalID if (sscanf(p, "%f %f %f %f %d", &w->v1.x, &w->v1.y, &w->v2.x, &w->v2.y, &w->portalID) != 5) { fclose(f); return -4; } } } fclose(f); return 0; // Success } void build_sector_flats(struct Sector *s) { // We need at least 3 walls to make a floor (a triangle) if (s->nwalls < 3) return; // 1. Pick a pivot point (the start of the first wall) struct Wall *first_w = &state.walls.arr[s->firstwall]; glm::vec3 pivot_floor = glm::vec3((float)first_w->v1.x, s->floor, (float)first_w->v1.y); glm::vec3 pivot_ceil = glm::vec3((float)first_w->v1.x, s->ceil, (float)first_w->v1.y); // 2. Loop through the rest of the walls to create a "fan" of triangles for (size_t i = 1; i < s->nwalls - 1; i++) { struct Wall *w1 = &state.walls.arr[s->firstwall + i]; struct Wall *w2 = &state.walls.arr[s->firstwall + i + 1]; glm::vec3 p1_f = glm::vec3((float)w1->v1.x, s->floor, (float)w1->v1.y); glm::vec3 p2_f = glm::vec3((float)w2->v1.x, s->floor, (float)w2->v1.y); glm::vec3 p1_c = glm::vec3((float)w1->v1.x, s->ceil, (float)w1->v1.y); glm::vec3 p2_c = glm::vec3((float)w2->v1.x, s->ceil, (float)w2->v1.y); // FLOOR TRIANGLE (Facing Up) // Winding: Pivot -> P1 -> P2 (Counter-Clockwise) map_mesh.push_back({pivot_floor, {pivot_floor.x, pivot_floor.z}}); map_mesh.push_back({p1_f, {p1_f.x, p1_f.z}}); map_mesh.push_back({p2_f, {p2_f.x, p2_f.z}}); // CEILING TRIANGLE (Facing Down) // Winding: Pivot -> P2 -> P1 (Clockwise from top, CCW from bottom) map_mesh.push_back({pivot_ceil, {pivot_ceil.x, pivot_ceil.z}}); map_mesh.push_back({p2_c, {p2_c.x, p2_c.z}}); map_mesh.push_back({p1_c, {p1_c.x, p1_c.z}}); } } void push_wall_quad(glm::vec2 a, glm::vec2 b, float y_low, float y_high) { float length = glm::distance(a, b); float height = y_high - y_low; // Vertex data: Position(x, y, z), TexCoords(u, v) // Triangle 1 map_mesh.push_back({{a.x, y_low, a.y}, {0.0f, 0.0f}}); map_mesh.push_back({{b.x, y_low, b.y}, {length, 0.0f}}); map_mesh.push_back({{b.x, y_high, b.y}, {length, height}}); // Triangle 2 map_mesh.push_back({{a.x, y_low, a.y}, {0.0f, 0.0f}}); map_mesh.push_back({{b.x, y_high, b.y}, {length, height}}); map_mesh.push_back({{a.x, y_high, a.y}, {0.0f, height}}); } void build_map_mesh() { map_mesh.clear(); // Loop through sectors starting from 1 for (size_t i = 1; i < state.sectors.n; i++) { struct Sector *s = &state.sectors.arr[i]; // Loop through the walls assigned to this sector for (size_t j = 0; j < s->nwalls; j++) { struct Wall *w = &state.walls.arr[s->firstwall + j]; // Map coordinates directly from the wall struct // We cast to float because sscanf read them as ints glm::vec2 pA = glm::vec2((float)w->v1.x, (float)w->v1.y); glm::vec2 pB = glm::vec2((float)w->v2.x, (float)w->v2.y); if (w->portalID == 0) { // It's a solid wall: draw from floor to ceiling push_wall_quad(pA, pB, s->floor, s->ceil); } else { // It's a portal: draw the upper/lower differences struct Sector *neighbor = &state.sectors.arr[w->portalID]; // Draw lower "step" if neighbor floor is higher if (neighbor->floor > s->floor) push_wall_quad(pA, pB, s->floor, neighbor->floor); // Draw upper "overhang" if neighbor ceiling is lower if (neighbor->ceil < s->ceil) push_wall_quad(pA, pB, neighbor->ceil, s->ceil); } } build_sector_flats(s); } } void scroll_callback(GLFWwindow *window, double xoffset, double yoffset) { (void)window; camera.ProcessMouseScroll(yoffset); } void framebuffer_size_callback(GLFWwindow *window, int width, int height) { (void)window; glViewport(0, 0, width, height); } void mouse_callback(GLFWwindow *window, double xpos, double ypos) { (void)window; if (firstMouse) { lastX = xpos; lastY = ypos; firstMouse = false; } float xoffset = xpos - lastX; float yoffset = lastY - ypos; lastX = xpos; lastY = ypos; camera.ProcessMouseMovement(xoffset, yoffset); } void updatePhysics(float deltaTime, float floor) { // 1. Apply gravity to velocity (Velocity = Acceleration * Time) if (!camera.isGrounded) { camera.verticalVelocity += camera.gravity * deltaTime; } // 2. Apply velocity to position (Position = Velocity * Time) camera.Position.y += camera.verticalVelocity * deltaTime; // 3. Ground Collision (The Floor) // If the floor is at y = 0 if (camera.Position.y <= floor) { camera.Position.y = floor; camera.verticalVelocity = 0.0f; camera.isGrounded = true; } } void processInput(GLFWwindow *window) { if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) { glfwSetWindowShouldClose(window, 1); } if (glfwGetKey(window, GLFW_KEY_F) == GLFW_PRESS) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } if (glfwGetKey(window, GLFW_KEY_H) == GLFW_PRESS) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.ProcessKeyboard(FORWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.ProcessKeyboard(BACKWARD, deltaTime); if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.ProcessKeyboard(LEFT, deltaTime); if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.ProcessKeyboard(RIGHT, deltaTime); if (glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS && camera.isGrounded) { camera.verticalVelocity = camera.jumpForce; camera.isGrounded = false; } } int get_sector_at(float x, float z) { // Start from 1 (skipping null sector) for (size_t i = 1; i < state.sectors.n; i++) { struct Sector *s = &state.sectors.arr[i]; int inside = 0; for (size_t j = 0; j < s->nwalls; j++) { struct Wall *w = &state.walls.arr[s->firstwall + j]; // Check if player position is between the Y-bounds of the wall if (((w->v1.y > z) != (w->v2.y > z)) && (x < (float)(w->v2.x - w->v1.x) * (z - (float)w->v1.y) / (float)(w->v2.y - w->v1.y) + (float)w->v1.x)) { inside = !inside; } } if (inside) return (int)i; } return -1; // Outside the map } int main() { float planeVertices[] = { // positions // texture coords -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, // bottom left 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, // bottom right 0.5f, 0.5f, 0.0f, 1.0f, 1.0f, // top right -0.5f, 0.5f, 0.0f, 0.0f, 1.0f // top left }; unsigned int planeIndices[] = { 0, 1, 2, // first triangle 2, 3, 0 // second triangle }; glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_FLOATING, GLFW_TRUE); // This makes the window float glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE); glfwWindowHint(GLFW_FOCUS_ON_SHOW, GLFW_TRUE); glfwWindowHintString(GLFW_WAYLAND_APP_ID, "opengl"); GLFWwindow *window = glfwCreateWindow(1000, 600, "testing open gl", NULL, NULL); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glfwSetCursorPosCallback(window, mouse_callback); glfwSetScrollCallback(window, scroll_callback); if (window == NULL) { printf("failed to create window"); return -1; } glfwMakeContextCurrent(window); if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { printf("failed to initlize glad"); return -1; } glViewport(0, 0, 800, 600); Shader ourshader("../vertexshader.glsl", "../fragmentshader.glsl"); float vertices[] = { -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // cube one 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, // cube one 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // cube one 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // cube one -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // cube one -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, // cube one -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // cube one 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // cube one 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // cube one 0.5f, 0.5f, 0.5f, 1.0f, 1.0f, // cube one -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, // cube one -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // cube one -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // cube one -0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // cube one -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // cube one -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // cube one -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // cube one -0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // cube one 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // cube one 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // cube one 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // cube one 0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // cube one 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // cube one 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // cube one -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // cube one 0.5f, -0.5f, -0.5f, 1.0f, 1.0f, // cube one 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // cube one 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, // cube one -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, // cube one -0.5f, -0.5f, -0.5f, 0.0f, 1.0f, // cube one -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, // cube one 0.5f, 0.5f, -0.5f, 1.0f, 1.0f, // cube one 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // cube one 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, // cube one -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, // cube one -0.5f, 0.5f, -0.5f, 0.0f, 1.0f // cube one }; if (load_sectors("../map.txt") != 0) { printf("loaded %zu sectors and %zu walls", state.sectors.n, state.walls.n); printf("Failed to parse map file!\n"); return -1; } build_map_mesh(); unsigned int mapVAO, mapVBO; glGenVertexArrays(1, &mapVAO); glGenBuffers(1, &mapVBO); glBindVertexArray(mapVAO); glBindBuffer(GL_ARRAY_BUFFER, mapVBO); glBufferData(GL_ARRAY_BUFFER, map_mesh.size() * sizeof(MapVertex), map_mesh.data(), GL_STATIC_DRAW); // Position attribute glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(MapVertex), (void *)0); glEnableVertexAttribArray(0); // TexCoord attribute glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(MapVertex), (void *)offsetof(MapVertex, tex)); glEnableVertexAttribArray(2); unsigned int EBO; glGenBuffers(1, &EBO); unsigned int VBO; glGenBuffers(1, &VBO); unsigned int VAO; glGenVertexArrays(1, &VAO); glBindVertexArray(VAO); glBindBuffer(GL_ARRAY_BUFFER, VBO); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(planeIndices), planeIndices, GL_STATIC_DRAW); // position attribute glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)0); glEnableVertexAttribArray(0); // texture coord attribute glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(3 * sizeof(float))); glEnableVertexAttribArray(2); unsigned int wallVAO, wallVBO, wallEBO; glGenVertexArrays(1, &wallVAO); glGenBuffers(1, &wallVBO); glGenBuffers(1, &wallEBO); glBindVertexArray(wallVAO); glBindBuffer(GL_ARRAY_BUFFER, wallVBO); glBufferData(GL_ARRAY_BUFFER, sizeof(planeVertices), planeVertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, wallEBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(planeIndices), planeIndices, GL_STATIC_DRAW); // Position attribute glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)0); glEnableVertexAttribArray(0); // Texture coord attribute glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void *)(3 * sizeof(float))); glEnableVertexAttribArray(2); // sphere unsigned int sphereVAO, sphereVBO, sphereEBO; std::vector sphereVerts; std::vector sphereIndices; float ballRadius = 0.05f; generateSphere(ballRadius, 36, 18, sphereVerts, sphereIndices); glGenVertexArrays(1, &sphereVAO); glGenBuffers(1, &sphereVBO); glGenBuffers(1, &sphereEBO); glBindVertexArray(sphereVAO); glBindBuffer(GL_ARRAY_BUFFER, sphereVBO); glBufferData(GL_ARRAY_BUFFER, sphereVerts.size() * sizeof(SphereVertex), &sphereVerts[0], GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sphereEBO); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sphereIndices.size() * sizeof(unsigned int), &sphereIndices[0], GL_STATIC_DRAW); // Position attribute glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(SphereVertex), (void *)0); glEnableVertexAttribArray(0); // Normal attribute glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(SphereVertex), (void *)offsetof(SphereVertex, normal)); glEnableVertexAttribArray(1); // TexCoord attribute glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(SphereVertex), (void *)offsetof(SphereVertex, texCoord)); glEnableVertexAttribArray(2); // loading textures unsigned int texture1, texture2; int width, height, nrChannels; stbi_set_flip_vertically_on_load(true); glGenTextures(1, &texture1); glBindTexture(GL_TEXTURE_2D, texture1); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); unsigned char *image = stbi_load("../sources/textures/w65b_2.png", &width, &height, &nrChannels, 0); if (image) { GLenum format = (nrChannels == 4) ? GL_RGBA : GL_RGB; glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, image); glGenerateMipmap(GL_TEXTURE_2D); } else { std::cout << "Failed to load texture" << std::endl; } stbi_image_free(image); glGenTextures(1, &texture2); glBindTexture(GL_TEXTURE_2D, texture2); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); image = stbi_load("../sources/textures/brks_y.png", &width, &height, &nrChannels, 0); if (image) { GLenum format = (nrChannels == 4) ? GL_RGBA : GL_RGB; glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, image); glGenerateMipmap(GL_TEXTURE_2D); } else { std::cout << "Failed to load texture" << std::endl; } stbi_image_free(image); ourshader.use(); ourshader.setInt("textureimg", 0); // defines a model matrix which will rotate the plane // ourshader.setMat4("mvp", glm::mat4(1.0f)); glEnable(GL_DEPTH_TEST); glm::vec3 ballVelocity = glm::vec3(0.0f, 1.0f, 0.0f); glm::vec3 ballPosition = glm::vec3(camera.Position.x, 5.0f, camera.Position.z); float gravity = -9.81f; while (!glfwWindowShouldClose(window)) { float currentFrame = glfwGetTime(); float ground = 1.0f; deltaTime = currentFrame - lastFrame; lastFrame = currentFrame; int current_sector_id = get_sector_at(camera.Position.x, camera.Position.z); if (current_sector_id != -1) { struct Sector *s = &state.sectors.arr[current_sector_id]; // The "target" height is floor + player height (e.g., 2.0 units) float target_y = s->floor + 1.0f; ground = target_y; // Smoothly interpolate (lerp) to the target height so you don't // "teleport" up stairs if (camera.isGrounded) { float lerpSpeed = 10.0f; // Adjust this for "snappiness" camera.Position.y = glm::mix(camera.Position.y, target_y, lerpSpeed * deltaTime); } } updatePhysics(deltaTime, ground); processInput(window); ballVelocity.y += gravity * deltaTime; ballPosition += ballVelocity * deltaTime; float groundlevel = -0.5f; if (ballPosition.y - ballRadius < groundlevel) { ballPosition.y = groundlevel + ballRadius; // Snap to floor ballVelocity.y = -ballVelocity.y * 0.75f; // Reverse and dampen (75% energy kept) // Friction: Slow down X and Z movement slightly on impact ballVelocity.x *= 0.9f; ballVelocity.z *= 0.9f; } // printf("ball: \nx:%f\ny:%f\nz:%f\n", ballPosition.x, ballPosition.y, // ballPosition.z); // printf("camera: \nx:%f\ny:%f\nz:%f\n", camera.Position.x, // camera.Position.y, // camera.Position.z); // matrix transformations, remember to do them in reverse order to what // you want glm::mat4 trans = glm::mat4(1.0f); trans = // glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f)); trans = // glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0f, // 0.0f, 1.0f)); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // glDisable(GL_CULL_FACE); // use our shader program before passing in the uniform ourshader.use(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, texture1); // model = glm::translate( // model, glm::vec3(0.0f, sin(glfwGetTime() * 2) * 10e-4f, 0.0f)); // model = glm::rotate(model, glm::radians(90.0f) * (float)deltaTime, // glm::vec3(0.0f, 1.0f, 0.0f)); glm::mat4 model = glm::mat4(1.0f); model = glm::translate(model, ballPosition); // Put it somewhere in the map glm::mat4 view; view = camera.GetViewMatrix(); float aspect = (float)width / (float)height; glm::mat4 projection = glm::mat4(1.0f); projection = glm::perspective(glm::radians(camera.Zoom), aspect, 0.1f, 100.0f); ourshader.setMat4("mvp", projection * view * model); glBindVertexArray(sphereVAO); glDrawElements(GL_TRIANGLES, sphereIndices.size(), GL_UNSIGNED_INT, 0); glBindTexture(GL_TEXTURE_2D, texture2); ourshader.setMat4("mvp", projection * view * glm::mat4(1.0f)); glBindVertexArray(mapVAO); glDrawArrays(GL_TRIANGLES, 0, map_mesh.size()); glBindVertexArray(0); glfwSwapBuffers(window); glfwPollEvents(); } glfwTerminate(); return 0; }