#define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #define _USE_MATH_DEFINES #include #include #include #include #include #include "IMGUI/imgui.h" #include "IMGUI/imgui_impl_glfw.h" #include "IMGUI/imgui_impl_opengl3.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "logger.hpp" #include "utils.hpp" #include "compiler.hpp" #include "crash-logger.hpp" using namespace std; using namespace glm; enum class State { MAIN_MENU, GAME, }; enum class Event { GO_TO_MAIN_MENU, GO_TO_GAME, QUIT, }; /*** START OF REFACTORED CODE ***/ enum class ObjectType { SHIP, ASTEROID, LASER, EXPLOSION, }; enum class AttribType { UNIFORM, OBJECT_VARYING, POINT_VARYING, }; // Add more types as I need them enum UniformType { UNIFORM_NONE, UNIFORM_MATRIX_4F, UNIFORM_1F, UNIFORM_3F, }; /*** END OF REFACTORED CODE ***/ enum UIValueType { UIVALUE_INT, UIVALUE_DOUBLE, }; /*** START OF REFACTORED CODE ***/ struct SceneObject { unsigned int id; ObjectType type; bool deleted; // Currently, model_transform should only have translate, and rotation and scale need to be done in model_base since // they need to be done when the object is at the origin. I should change this to have separate scale, rotate, and translate // matrices for each object that can be updated independently and then applied to the object in that order. // TODO: Actually, to make this as generic as possible, each object should have a matrix stack to support, // for instance, applying a rotate, then a translate, then another rotate. Think about and implement the best approach. mat4 model_mat, model_base, model_transform; mat4 translate_mat; // beginning of doing what's mentioned above unsigned int num_points; GLuint vertex_vbo_offset; GLuint ubo_offset; vector points; vector colors; vector texcoords; vector normals; vec3 bounding_center; GLfloat bounding_radius; }; struct Asteroid : SceneObject { float hp; }; struct Laser : SceneObject { Asteroid* targetAsteroid; }; struct ParticleEffect : SceneObject { vector particleVelocities; vector particleTimes; GLfloat startTime; GLfloat duration; }; struct EffectOverTime { float& effectedValue; float startValue; double startTime; float changePerSecond; bool deleted; SceneObject* effectedObject; // TODO: Why not just use an initializer list for all the instance variables // TODO: Maybe pass in startTime instead of calling glfwGetTime() here EffectOverTime(float& effectedValue, float changePerSecond, SceneObject* object) : effectedValue(effectedValue), changePerSecond(changePerSecond), effectedObject(object) { startValue = effectedValue; startTime = glfwGetTime(); deleted = false; } }; struct BufferInfo { unsigned int ubo_base; unsigned int ubo_offset; unsigned int ubo_capacity; }; struct AttribInfo { AttribType attribType; GLuint index; GLint size; GLenum type; UniformType uniType; GLuint buffer; // For uniforms, this is the uniform location size_t fieldOffset; GLfloat* data; // pointer to data source for uniform attributes }; struct ShaderModelGroup { GLuint shaderProgram; GLuint vao; map attribs; unsigned int numPoints; unsigned int vboCapacity; }; /*** END OF REFACTORED CODE ***/ struct UIValue { UIValueType type; string label; void* value; UIValue(UIValueType _type, string _label, void* _value) : type(_type), label(_label), value(_value) {} }; /*** START OF REFACTORED CODE ***/ void glfw_error_callback(int error, const char* description); void mouse_button_callback(GLFWwindow* window, int button, int action, int mods); void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods); void framebuffer_size_callback(GLFWwindow* window, int width, int height); void APIENTRY debugGlCallback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam ); /*** END OF REFACTORED CODE ***/ bool faceClicked(array points, SceneObject* obj, vec4 world_ray, vec4 cam, vec4& click_point); bool insideTriangle(vec3 p, array triangle_points); GLuint loadShader(GLenum type, string file); GLuint loadShaderProgram(string vertexShaderPath, string fragmentShaderPath); unsigned char* loadImage(string file_name, int* x, int* y); /*** START OF REFACTORED CODE ***/ void initObject(SceneObject* obj); void addObjectToScene(SceneObject* obj, map& shaderBufferInfo, map& modelGroups, GLuint ubo); void removeObjectFromScene(SceneObject& obj, GLuint ubo); ShaderModelGroup createModelGroup(GLuint shaderProgram); void removeModelFromGroup(ShaderModelGroup& modelGroup, SceneObject& model); void addModelToGroup(ShaderModelGroup& modelGroup, SceneObject& model); void defineModelGroupAttrib(ShaderModelGroup& modelGroup, string name, AttribType attribType, GLint size, GLenum type, size_t fieldOffset); void defineModelGroupUniform(ShaderModelGroup& modelGroup, string name, AttribType attribType, GLint size, UniformType type, GLfloat* data); void initModelGroupAttribs(ShaderModelGroup& modelGroup); void bindUniformData(AttribInfo& attrib); void bindUniformData(AttribInfo& attrib, GLfloat* data); size_t GLsizeof(GLenum); GLvoid* getVectorAttribPtr(SceneObject& obj, size_t attribOffset); GLvoid* getScalarAttribPtr(SceneObject& obj, size_t attribOffset); void calculateObjectBoundingBox(SceneObject* obj); void populateBuffers(vector& objects, map& shaderBufferInfo, map& modelGroups, GLuint ubo); void copyObjectDataToBuffers(SceneObject& obj, map& shaderBufferInfo, map& modelGroups, GLuint ubo); void transformObject(SceneObject& obj, const mat4& transform, GLuint ubo); // TODO: instead of using these methods, create constructors for these SceneObject* createShip(); Asteroid* createAsteroid(vec3 pos); Laser* createLaser(vec3 start, vec3 end, vec3 color, GLfloat width); ParticleEffect* createExplosion(mat4 model_mat); void translateLaser(Laser* laser, const vec3& translation, GLuint ubo); void updateLaserTarget(Laser* laser, vector& objects, ShaderModelGroup& laserSmg, GLuint asteroid_sp); bool getLaserAndAsteroidIntersection(vec3& start, vec3& end, SceneObject& asteroid, vec3& intersection); /*** END OF REFACTORED CODE ***/ void renderMainMenu(); void renderMainMenuGui(); void renderScene(map& modelGroups, GLuint ubo); void renderSceneGui(map> valueLists); void initGuiValueLists(map> valueLists); void renderGuiValueList(vector& values); #define NUM_KEYS (512) #define TARGET_FPS 60.0f const int KEY_STATE_UNCHANGED = -1; /*** START OF REFACTORED CODE ***/ const bool FULLSCREEN = false; const int EXPLOSION_PARTICLE_COUNT = 300; unsigned int MAX_UNIFORMS = 0; // Requires OpenGL constants only available at runtime, so it can't be const /*** END OF REFACTORED CODE ***/ int key_state[NUM_KEYS]; bool key_down[NUM_KEYS]; /*** START OF REFACTORED CODE ***/ unsigned int windowWidth = 640; unsigned int windowHeight = 480; vec3 cam_pos; mat4 view_mat; mat4 proj_mat; vector objects; vector effects; /*** END OF REFACTORED CODE ***/ queue events; SceneObject* clickedObject = NULL; SceneObject* selectedObject = NULL; /*** START OF REFACTORED CODE ***/ float NEAR_CLIP = 0.1f; float FAR_CLIP = 100.0f; // TODO: Should really have some array or struct of UI-related variables bool isRunning = true; Laser* leftLaser = NULL; EffectOverTime* leftLaserEffect = NULL; Laser* rightLaser = NULL; EffectOverTime* rightLaserEffect = NULL; /*** END OF REFACTORED CODE ***/ map> valueLists; /*** START OF REFACTORED CODE ***/ /* * TODO: Asteroid and ship movement currently depend on framerate, fix this in a generic/reusable way * Disabling vsync is a great way to test this */ // Helps to test logging during crashes void badFunc() { int* test = NULL; *test = 1; } int __main(int argc, char* argv[]); int main(int argc, char* argv[]) { CrashLogger logger(__main, argc, argv); exit(0); } int __main(int argc, char* argv[]) { cout << "New OpenGL Game" << endl; restart_gl_log(); gl_log("starting GLFW\n%s", glfwGetVersionString()); open_log(); get_log() << "starting GLFW" << endl; get_log() << glfwGetVersionString() << endl; glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) { gl_log_err("ERROR: could not start GLFW3"); cerr << "ERROR: could not start GLFW3" << endl; get_log() << "ERROR: could not start GLFW3" << endl; return 1; } // Currently, this is just for IMGUI string glsl_version = "#version 410"; #ifdef MAC glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); #else glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); #endif GLFWmonitor* mon = NULL; if (FULLSCREEN) { mon = glfwGetPrimaryMonitor(); const GLFWvidmode* vmode = glfwGetVideoMode(mon); windowWidth = vmode->width; windowHeight = vmode->height; cout << "Fullscreen resolution " << vmode->width << "x" << vmode->height << endl; } glfwWindowHint(GLFW_SAMPLES, 16); glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, true); GLFWwindow* window = glfwCreateWindow(windowWidth, windowHeight, "New OpenGL Game", mon, NULL); if (window == NULL) { gl_log_err("ERROR: could not open window with GLFW3"); cerr << "ERROR: could not open window with GLFW3" << endl; get_log() << "ERROR: could not open window with GLFW3" << endl; glfwTerminate(); return 1; } glfwMakeContextCurrent(window); glewExperimental = GL_TRUE; glewInit(); if (GLEW_KHR_debug) { cout << "FOUND GLEW debug extension" << endl; glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); glDebugMessageCallback((GLDEBUGPROC)debugGlCallback, NULL); cout << "Bound debug callback" << endl; } else { cout << "OpenGL debug message callback is not supported" << endl; } glfwSetMouseButtonCallback(window, mouse_button_callback); glfwSetKeyCallback(window, key_callback); glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); srand(time(0)); /* * RENDERING ALGORITHM NOTES: * * Basically, I need to split my objects into groups, so that each group fits into * GL_MAX_UNIFORM_BLOCK_SIZE. I need to have an offset and a size for each group. * Getting the offset is straitforward. The size may as well be GL_MAX_UNIFORM_BLOCK_SIZE * for each group, since it seems that smaller sizes just round up to the nearest GL_MAX_UNIFORM_BLOCK_SIZE * * I'll need to have a loop inside my render loop that calls glBindBufferRange(GL_UNIFORM_BUFFER, ... * for every 1024 objects and then draws all those objects with one glDraw call. * * Since I currently have very few objects, I'll wait to implement this until I have * a reasonable number of objects always using the same shader. */ GLint UNIFORM_BUFFER_OFFSET_ALIGNMENT, MAX_UNIFORM_BLOCK_SIZE; glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &UNIFORM_BUFFER_OFFSET_ALIGNMENT); glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &MAX_UNIFORM_BLOCK_SIZE); MAX_UNIFORMS = MAX_UNIFORM_BLOCK_SIZE / sizeof(mat4); cout << "UNIFORM_BUFFER_OFFSET_ALIGNMENT: " << UNIFORM_BUFFER_OFFSET_ALIGNMENT << endl; cout << "MAX_UNIFORMS: " << MAX_UNIFORMS << endl; // Setup Dear ImGui binding IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; //io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls //io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls // Setup Platform/Renderer backends ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init(glsl_version.c_str()); // Setup Dear ImGui style ImGui::StyleColorsDark(); //ImGui::StyleColorsClassic(); /*** END OF REFACTORED CODE ***/ const GLubyte* renderer = glGetString(GL_RENDERER); const GLubyte* version = glGetString(GL_VERSION); cout << "Renderer: " << renderer << endl; cout << "Supported OpenGL version: " << version << endl; gl_log("Renderer: %s", renderer); gl_log("Supported OpenGL version: %s", version); get_log() << "Renderer: " << renderer << endl; get_log() << "Supported OpenGL version: " << version << endl; glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); glEnable(GL_CULL_FACE); // glCullFace(GL_BACK); // glFrontFace(GL_CW); int x, y; unsigned char* texImage = loadImage("laser.png", &x, &y); if (texImage) { cout << "Laser texture loaded successfully!" << endl; cout << x << ", " << y << endl; cout << "first 4 bytes are: " << texImage[0] << " " << texImage[1] << " " << texImage[2] << " " << texImage[3] << endl; } GLuint laserTex = 0; glGenTextures(1, &laserTex); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, laserTex); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, x, y, 0, GL_RGBA, GL_UNSIGNED_BYTE, texImage); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); /* RENDERING ALGORITHM * * Create a separate vbo for each of the following things: * - points * - colors * - texture coordinates * - selected colors * - normals * - indices into a ubo that stores a model matrix for each object * * Also, make a model matrix ubo, the entirety of which will be passed to the vertex shader. * The vbo containing the correct index into the ubo (mentioned above) will be used to select * the right model matrix for each point. The index in the vbo will be the saem for all points * of any given object. * * Right now, the currently selected object is drawn using one color (specified in the selected * colors vbo) regardless of whether it is normally rendering using colors or a texture. The selected * object is rendering by binding the selected colors vbo in place of the colors vbo and using the colors * shader. Then, the selected object is redrawn along with all other objects, but the depth buffer test * prevents the unselected version of the object from appearing on the screen. This lets me render all the * objects that use a particular shader using one glDrawArrays() call. */ /*** START OF REFACTORED CODE ***/ GLfloat laserColor[3] = {0.2f, 1.0f, 0.2f}; GLfloat curTime, prevTime, elapsedTime; GLuint ubo = 0; glGenBuffers(1, &ubo); map shaderBufferInfo; map modelGroups; modelGroups[ObjectType::SHIP] = createModelGroup( loadShaderProgram("gl-shaders/ship.vert", "gl-shaders/ship.frag")); shaderBufferInfo[modelGroups[ObjectType::SHIP].shaderProgram] = BufferInfo(); // temporary defineModelGroupAttrib(modelGroups[ObjectType::SHIP], "vertex_position", AttribType::POINT_VARYING, 3, GL_FLOAT, offset_of(&SceneObject::points)); defineModelGroupAttrib(modelGroups[ObjectType::SHIP], "vertex_color", AttribType::POINT_VARYING, 3, GL_FLOAT, offset_of(&SceneObject::colors)); defineModelGroupAttrib(modelGroups[ObjectType::SHIP], "vertex_normal", AttribType::POINT_VARYING, 3, GL_FLOAT, offset_of(&SceneObject::normals)); defineModelGroupAttrib(modelGroups[ObjectType::SHIP], "ubo_index", AttribType::OBJECT_VARYING, 1, GL_UNSIGNED_INT, offset_of(&SceneObject::ubo_offset)); defineModelGroupUniform(modelGroups[ObjectType::SHIP], "view", AttribType::UNIFORM, 1, UNIFORM_MATRIX_4F, value_ptr(view_mat)); defineModelGroupUniform(modelGroups[ObjectType::SHIP], "proj", AttribType::UNIFORM, 1, UNIFORM_MATRIX_4F, value_ptr(proj_mat)); initModelGroupAttribs(modelGroups[ObjectType::SHIP]); modelGroups[ObjectType::ASTEROID] = createModelGroup( loadShaderProgram("gl-shaders/asteroid.vert", "gl-shaders/asteroid.frag")); shaderBufferInfo[modelGroups[ObjectType::ASTEROID].shaderProgram] = BufferInfo(); // temporary defineModelGroupAttrib(modelGroups[ObjectType::ASTEROID], "vertex_position", AttribType::POINT_VARYING, 3, GL_FLOAT, offset_of(&SceneObject::points)); defineModelGroupAttrib(modelGroups[ObjectType::ASTEROID], "vertex_color", AttribType::POINT_VARYING, 3, GL_FLOAT, offset_of(&SceneObject::colors)); defineModelGroupAttrib(modelGroups[ObjectType::ASTEROID], "vertex_normal", AttribType::POINT_VARYING, 3, GL_FLOAT, offset_of(&SceneObject::normals)); defineModelGroupAttrib(modelGroups[ObjectType::ASTEROID], "ubo_index", AttribType::OBJECT_VARYING, 1, GL_UNSIGNED_INT, offset_of(&SceneObject::ubo_offset)); defineModelGroupUniform(modelGroups[ObjectType::ASTEROID], "view", AttribType::UNIFORM, 1, UNIFORM_MATRIX_4F, value_ptr(view_mat)); defineModelGroupUniform(modelGroups[ObjectType::ASTEROID], "proj", AttribType::UNIFORM, 1, UNIFORM_MATRIX_4F, value_ptr(proj_mat)); initModelGroupAttribs(modelGroups[ObjectType::ASTEROID]); modelGroups[ObjectType::LASER] = createModelGroup( loadShaderProgram("gl-shaders/laser.vert", "gl-shaders/laser.frag")); shaderBufferInfo[modelGroups[ObjectType::LASER].shaderProgram] = BufferInfo(); // temporary defineModelGroupAttrib(modelGroups[ObjectType::LASER], "vertex_position", AttribType::POINT_VARYING, 3, GL_FLOAT, offset_of(&SceneObject::points)); defineModelGroupAttrib(modelGroups[ObjectType::LASER], "vt", AttribType::POINT_VARYING, 2, GL_FLOAT, offset_of(&SceneObject::texcoords)); defineModelGroupAttrib(modelGroups[ObjectType::LASER], "ubo_index", AttribType::OBJECT_VARYING, 1, GL_UNSIGNED_INT, offset_of(&SceneObject::ubo_offset)); defineModelGroupUniform(modelGroups[ObjectType::LASER], "view", AttribType::UNIFORM, 1, UNIFORM_MATRIX_4F, value_ptr(view_mat)); defineModelGroupUniform(modelGroups[ObjectType::LASER], "proj", AttribType::UNIFORM, 1, UNIFORM_MATRIX_4F, value_ptr(proj_mat)); defineModelGroupUniform(modelGroups[ObjectType::LASER], "laser_color", AttribType::UNIFORM, 1, UNIFORM_3F, laserColor); initModelGroupAttribs(modelGroups[ObjectType::LASER]); modelGroups[ObjectType::EXPLOSION] = createModelGroup( loadShaderProgram("gl-shaders/explosion.vert", "gl-shaders/explosion.frag")); shaderBufferInfo[modelGroups[ObjectType::EXPLOSION].shaderProgram] = BufferInfo(); // temporary defineModelGroupAttrib(modelGroups[ObjectType::EXPLOSION], "v_i", AttribType::POINT_VARYING, 3, GL_FLOAT, offset_of(&ParticleEffect::particleVelocities)); defineModelGroupAttrib(modelGroups[ObjectType::EXPLOSION], "start_time", AttribType::POINT_VARYING, 1, GL_FLOAT, offset_of(&ParticleEffect::particleTimes)); defineModelGroupAttrib(modelGroups[ObjectType::EXPLOSION], "ubo_index", AttribType::OBJECT_VARYING, 1, GL_UNSIGNED_INT, offset_of(&SceneObject::ubo_offset)); defineModelGroupUniform(modelGroups[ObjectType::EXPLOSION], "cur_time", AttribType::UNIFORM, 1, UNIFORM_1F, &curTime); defineModelGroupUniform(modelGroups[ObjectType::EXPLOSION], "view", AttribType::UNIFORM, 1, UNIFORM_MATRIX_4F, value_ptr(view_mat)); defineModelGroupUniform(modelGroups[ObjectType::EXPLOSION], "proj", AttribType::UNIFORM, 1, UNIFORM_MATRIX_4F, value_ptr(proj_mat)); initModelGroupAttribs(modelGroups[ObjectType::EXPLOSION]); cam_pos = vec3(0.0f, 0.0f, 2.0f); float cam_yaw = 0.0f * 2.0f * 3.14159f / 360.0f; float cam_pitch = -50.0f * 2.0f * 3.14159f / 360.0f; // player ship objects.push_back(createShip()); populateBuffers(objects, shaderBufferInfo, modelGroups, ubo); float cam_speed = 1.0f; float cam_yaw_speed = radians(60.0f); float cam_pitch_speed = radians(60.0f); // glm::lookAt can create the view matrix // glm::perspective can create the projection matrix mat4 T = translate(mat4(1.0f), vec3(-cam_pos.x, -cam_pos.y, -cam_pos.z)); mat4 yaw_mat = rotate(mat4(1.0f), -cam_yaw, vec3(0.0f, 1.0f, 0.0f)); mat4 pitch_mat = rotate(mat4(1.0f), -cam_pitch, vec3(1.0f, 0.0f, 0.0f)); mat4 R = pitch_mat * yaw_mat; view_mat = R*T; // TODO: Create a function to construct the projection matrix // (Maybe I should just use glm::perspective, after making sure it matches what I have now) float fov = radians(67.0f); float aspect = (float)windowWidth / (float)windowHeight; float range = tan(fov * 0.5f) * NEAR_CLIP; float Sx = NEAR_CLIP / (range * aspect); float Sy = NEAR_CLIP / range; float Sz = -(FAR_CLIP + NEAR_CLIP) / (FAR_CLIP - NEAR_CLIP); float Pz = -(2.0f * FAR_CLIP * NEAR_CLIP) / (FAR_CLIP - NEAR_CLIP); float proj_arr[] = { Sx, 0.0f, 0.0f, 0.0f, 0.0f, Sy, 0.0f, 0.0f, 0.0f, 0.0f, Sz, -1.0f, 0.0f, 0.0f, Pz, 0.0f, }; proj_mat = make_mat4(proj_arr); /* TODO: Fix the UBO binding code based on the following forum post (in order to support multiple ubos): (Also, I bookmarked a great explanation of this under ) CHECK MY OpenGL BOOKMARK CALLED "Learn OpenGL: Advanced GLSL" No, you're misunderstanding how this works. UBO binding works exactly like texture object binding. The OpenGL context has a number of slots for binding UBOs. There are GL_MAX_UNIFORM_BUFFER_BINDINGS number of slots for UBO binding. Uniform Blocks in a program can be set to use one of the slots in the context. You do this by first querying the block index using the block name (glGetUniformBlockIndex). This is similar to how you need to use glGetUniformLocation in order to set a uniform's value with glUniform. Block indices, like uniform locations, are specific to a program. Once you have the block index, you use glUniformBlockBinding to set that specific program to use a particular uniform buffer slot in the context. Let's say you have a global UBO that you want to use for every program. To make using it easier, you want to bind it just once. So first, you pick a uniform buffer slot in the context, one that always will refer to this UBO. Let's say you pick slot 8. When you build a program object that may use this global uniform buffer, what you do is quite simple. First, after linking the program, call glGetUniformBlockIndex(program, "NameOfGlobalUniformBlock"). If you get back GL_INVALID_INDEX, then you know that the global uniform block isn't used in that program. Otherwise you get back a block index. If you got a valid block index, then you call glUniformBlockBinding(program, uniformBlockIndex, 8). Remember that 8 is the uniform buffer context slot that we selected earlier. This causes this particular program to use uniform buffer slot #8 to find the buffer for "NameOfGlobalUniformBlock". Finally, to set the actual buffer in the context, call glBindBufferRange(GL_UNIFORM_BUFFER, 8, bufferObjectName, offset, size); */ GLuint ub_binding_point = 0; GLuint ship_sp_models_ub_index = glGetUniformBlockIndex(modelGroups[ObjectType::SHIP].shaderProgram, "models"); GLuint asteroid_sp_models_ub_index = glGetUniformBlockIndex(modelGroups[ObjectType::ASTEROID].shaderProgram, "models"); GLuint laser_sp_models_ub_index = glGetUniformBlockIndex(modelGroups[ObjectType::LASER].shaderProgram, "models"); GLuint explosion_sp_models_ub_index = glGetUniformBlockIndex(modelGroups[ObjectType::EXPLOSION].shaderProgram, "models"); glUseProgram(modelGroups[ObjectType::SHIP].shaderProgram); bindUniformData(modelGroups[ObjectType::SHIP].attribs["view"]); bindUniformData(modelGroups[ObjectType::SHIP].attribs["proj"]); glUniformBlockBinding(modelGroups[ObjectType::SHIP].shaderProgram, ship_sp_models_ub_index, ub_binding_point); glBindBufferRange(GL_UNIFORM_BUFFER, ub_binding_point, ubo, 0, GL_MAX_UNIFORM_BLOCK_SIZE); glUseProgram(modelGroups[ObjectType::ASTEROID].shaderProgram); bindUniformData(modelGroups[ObjectType::ASTEROID].attribs["view"]); bindUniformData(modelGroups[ObjectType::ASTEROID].attribs["proj"]); glUniformBlockBinding(modelGroups[ObjectType::ASTEROID].shaderProgram, asteroid_sp_models_ub_index, ub_binding_point); glBindBufferRange(GL_UNIFORM_BUFFER, ub_binding_point, ubo, 0, GL_MAX_UNIFORM_BLOCK_SIZE); // may want to do initialization for basic_texture uniform here too // Right now, I think I'm getting away without getting that uniform location because I'm only // using one texture, so setting it to GL_TEXTURE0 once works glUseProgram(modelGroups[ObjectType::LASER].shaderProgram); bindUniformData(modelGroups[ObjectType::LASER].attribs["view"]); bindUniformData(modelGroups[ObjectType::LASER].attribs["proj"]); bindUniformData(modelGroups[ObjectType::LASER].attribs["laser_color"]); glUniformBlockBinding(modelGroups[ObjectType::LASER].shaderProgram, laser_sp_models_ub_index, ub_binding_point); glBindBufferRange(GL_UNIFORM_BUFFER, ub_binding_point, ubo, 0, GL_MAX_UNIFORM_BLOCK_SIZE); glUseProgram(modelGroups[ObjectType::EXPLOSION].shaderProgram); bindUniformData(modelGroups[ObjectType::EXPLOSION].attribs["view"]); bindUniformData(modelGroups[ObjectType::EXPLOSION].attribs["proj"]); glUniformBlockBinding(modelGroups[ObjectType::EXPLOSION].shaderProgram, explosion_sp_models_ub_index, ub_binding_point); glBindBufferRange(GL_UNIFORM_BUFFER, ub_binding_point, ubo, 0, GL_MAX_UNIFORM_BLOCK_SIZE); /*** END OF REFACTORED CODE ***/ double fps; unsigned int score = 0; bool cam_moved = false; int frame_count = 0; double elapsed_seconds_fps = 0.0f; double elapsed_seconds_spawn = 0.0f; prevTime = glfwGetTime(); // This draws wireframes. Useful for seeing separate faces and occluded objects. //glPolygonMode(GL_FRONT, GL_LINE); // disable vsync to see real framerate //glfwSwapInterval(0); State curState = State::MAIN_MENU; initGuiValueLists(valueLists); valueLists["stats value list"].push_back(UIValue(UIVALUE_INT, "Score", &score)); valueLists["stats value list"].push_back(UIValue(UIVALUE_DOUBLE, "FPS", &fps)); while (!glfwWindowShouldClose(window) && isRunning) { curTime = glfwGetTime(); elapsedTime = curTime - prevTime; // temporary code to get around vsync issue in OSX Sierra if (elapsedTime < (1.0f / TARGET_FPS)) { continue; } prevTime = curTime; elapsed_seconds_fps += elapsedTime; if (elapsed_seconds_fps > 0.25f) { fps = (double)frame_count / elapsed_seconds_fps; frame_count = 0; elapsed_seconds_fps = 0.0f; } frame_count++; // Handle events clickedObject = NULL; // reset the all key states to KEY_STATE_UNCHANGED (something the GLFW key callback can never return) // so that GLFW_PRESS and GLFW_RELEASE are only detected once // TODO: Change this if we ever need to act on GLFW_REPEAT (which is when a key is held down // continuously for a period of time) fill(key_state, key_state + NUM_KEYS, KEY_STATE_UNCHANGED); glfwPollEvents(); while (!events.empty()) { switch (events.front()) { case Event::GO_TO_MAIN_MENU: curState = State::MAIN_MENU; break; case Event::GO_TO_GAME: curState = State::GAME; break; case Event::QUIT: isRunning = false; break; } events.pop(); } if (curState == State::GAME) { /*** START OF REFACTORED CODE ***/ elapsed_seconds_spawn += elapsedTime; if (elapsed_seconds_spawn > 0.5f) { SceneObject* obj = createAsteroid(vec3(getRandomNum(-1.3f, 1.3f), -1.2f, getRandomNum(-5.5f, -4.5f))); addObjectToScene(obj, shaderBufferInfo, modelGroups, ubo); elapsed_seconds_spawn -= 0.5f; } /*** END OF REFACTORED CODE ***/ /* if (clickedObject == &objects[0]) { selectedObject = &objects[0]; } if (clickedObject == &objects[1]) { selectedObject = &objects[1]; } */ /*** START OF REFACTORED CODE ***/ /* if (key_state[GLFW_KEY_SPACE] == GLFW_PRESS) { transformObject(objects[1], translate(mat4(1.0f), vec3(0.3f, 0.0f, 0.0f)), ubo); } if (key_down[GLFW_KEY_RIGHT]) { transformObject(objects[2], translate(mat4(1.0f), vec3(0.01f, 0.0f, 0.0f)), ubo); } if (key_down[GLFW_KEY_LEFT]) { transformObject(objects[2], translate(mat4(1.0f), vec3(-0.01f, 0.0f, 0.0f)), ubo); } */ if (key_down[GLFW_KEY_RIGHT]) { transformObject(*objects[0], translate(mat4(1.0f), vec3(0.01f, 0.0f, 0.0f)), ubo); if (leftLaser != NULL && !leftLaser->deleted) { translateLaser(leftLaser, vec3(0.01f, 0.0f, 0.0f), ubo); } if (rightLaser != NULL && !rightLaser->deleted) { translateLaser(rightLaser, vec3(0.01f, 0.0f, 0.0f), ubo); } } if (key_down[GLFW_KEY_LEFT]) { transformObject(*objects[0], translate(mat4(1.0f), vec3(-0.01f, 0.0f, 0.0f)), ubo); if (leftLaser != NULL && !leftLaser->deleted) { translateLaser(leftLaser, vec3(-0.01f, 0.0f, 0.0f), ubo); } if (rightLaser != NULL && !rightLaser->deleted) { translateLaser(rightLaser, vec3(-0.01f, 0.0f, 0.0f), ubo); } } if (key_state[GLFW_KEY_Z] == GLFW_PRESS) { vec3 offset(objects[0]->model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f)); leftLaser = createLaser( vec3(-0.21f, -1.19f, 1.76f) + offset, vec3(-0.21f, -1.19f, -3.0f) + offset, vec3(0.0f, 1.0f, 0.0f), 0.03f); addObjectToScene(leftLaser, shaderBufferInfo, modelGroups, ubo); } else if (key_state[GLFW_KEY_Z] == GLFW_RELEASE) { removeObjectFromScene(*leftLaser, ubo); } if (key_state[GLFW_KEY_X] == GLFW_PRESS) { vec3 offset(objects[0]->model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f)); rightLaser = createLaser( vec3(0.21f, -1.19f, 1.76f) + offset, vec3(0.21f, -1.19f, -3.0f) + offset, vec3(0.0f, 1.0f, 0.0f), 0.03f); addObjectToScene(rightLaser, shaderBufferInfo, modelGroups, ubo); } else if (key_state[GLFW_KEY_X] == GLFW_RELEASE) { removeObjectFromScene(*rightLaser, ubo); } // this code moves the asteroids for (unsigned int i = 0; i < objects.size(); i++) { if (!objects[i]->deleted) { if (objects[i]->type == ObjectType::ASTEROID) { transformObject(*objects[i], translate(mat4(1.0f), vec3(0.0f, 0.0f, 0.04f)), ubo); vec3 obj_center = vec3(view_mat * vec4(objects[i]->bounding_center, 1.0f)); if ((obj_center.z - objects[i]->bounding_radius) > -NEAR_CLIP) { removeObjectFromScene(*objects[i], ubo); } if (((Asteroid*)objects[i])->hp <= 0) { // TODO: Optimize this so I don't recalculate the camera rotation every time float cam_pitch = -50.0f * 2.0f * 3.14159f / 360.0f; mat4 pitch_mat = rotate(mat4(1.0f), cam_pitch, vec3(1.0f, 0.0f, 0.0f)); mat4 model_mat = translate(mat4(1.0f), objects[i]->bounding_center) * pitch_mat; removeObjectFromScene(*objects[i], ubo); /*** END OF REFACTORED CODE ***/ score++; /*** START OF REFACTORED CODE ***/ addObjectToScene(createExplosion(model_mat), shaderBufferInfo, modelGroups, ubo); } } else if (objects[i]->type == ObjectType::EXPLOSION) { ParticleEffect* explosion = (ParticleEffect*)objects[i]; if (glfwGetTime() >= explosion->startTime + explosion->duration) { removeObjectFromScene(*objects[i], ubo); } } } } if (leftLaser != NULL && !leftLaser->deleted) { updateLaserTarget(leftLaser, objects, modelGroups[ObjectType::LASER], modelGroups[ObjectType::ASTEROID].shaderProgram); } if (rightLaser != NULL && !rightLaser->deleted) { updateLaserTarget(rightLaser, objects, modelGroups[ObjectType::LASER], modelGroups[ObjectType::ASTEROID].shaderProgram); } } for (vector::iterator it = effects.begin(); it != effects.end(); ) { if ((*it)->deleted || (*it)->effectedObject->deleted) { delete *it; it = effects.erase(it); } else { EffectOverTime* eot = *it; eot->effectedValue = eot->startValue + (curTime - eot->startTime) * eot->changePerSecond; it++; } } if (key_state[GLFW_KEY_ESCAPE] == GLFW_PRESS) { glfwSetWindowShouldClose(window, 1); } float dist = cam_speed * elapsedTime; if (key_down[GLFW_KEY_A]) { vec3 dir = vec3(inverse(R) * vec4(-1.0f, 0.0f, 0.0f, 1.0f)); cam_pos += dir * dist; cam_moved = true; } if (key_down[GLFW_KEY_D]) { vec3 dir = vec3(inverse(R) * vec4(1.0f, 0.0f, 0.0f, 1.0f)); cam_pos += dir * dist; cam_moved = true; } if (key_down[GLFW_KEY_W]) { vec3 dir = vec3(inverse(R) * vec4(0.0f, 0.0f, -1.0f, 1.0f)); cam_pos += dir * dist; cam_moved = true; } if (key_down[GLFW_KEY_S]) { vec3 dir = vec3(inverse(R) * vec4(0.0f, 0.0f, 1.0f, 1.0f)); cam_pos += dir * dist; cam_moved = true; } /* if (key_down[GLFW_KEY_LEFT]) { cam_yaw += cam_yaw_speed * elapsedTime; cam_moved = true; } if (key_down[GLFW_KEY_RIGHT]) { cam_yaw -= cam_yaw_speed * elapsedTime; cam_moved = true; } if (key_down[GLFW_KEY_UP]) { cam_pitch += cam_pitch_speed * elapsedTime; cam_moved = true; } if (key_down[GLFW_KEY_DOWN]) { cam_pitch -= cam_pitch_speed * elapsedTime; cam_moved = true; } */ if (cam_moved && false) { // disable camera movement T = translate(mat4(1.0f), vec3(-cam_pos.x, -cam_pos.y, -cam_pos.z)); mat4 yaw_mat = rotate(mat4(1.0f), -cam_yaw, vec3(0.0f, 1.0f, 0.0f)); mat4 pitch_mat = rotate(mat4(1.0f), -cam_pitch, vec3(1.0f, 0.0f, 0.0f)); R = pitch_mat * yaw_mat; view_mat = R * T; glUseProgram(modelGroups[ObjectType::SHIP].shaderProgram); bindUniformData(modelGroups[ObjectType::SHIP].attribs["view"]); glUseProgram(modelGroups[ObjectType::LASER].shaderProgram); bindUniformData(modelGroups[ObjectType::LASER].attribs["view"]); cam_moved = false; } glUseProgram(modelGroups[ObjectType::EXPLOSION].shaderProgram); bindUniformData(modelGroups[ObjectType::EXPLOSION].attribs["cur_time"]); // Render scene glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /*** END OF REFACTORED CODE ***/ switch (curState) { case State::MAIN_MENU: renderMainMenu(); renderMainMenuGui(); break; case State::GAME: renderScene(modelGroups, ubo); renderSceneGui(valueLists); break; } glfwSwapBuffers(window); } for (vector::iterator it = objects.begin(); it != objects.end(); it++) { delete* it; } ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); glfwDestroyWindow(window); glfwTerminate(); close_log(); return 0; } void glfw_error_callback(int error, const char* description) { gl_log_err("GLFW ERROR: code %i msg: %s", error, description); cerr << "GLFW ERROR: code " << error << " msg: " << description << endl; get_log() << "GLFW ERROR: code " << error << " msg: " << description << endl; } void mouse_button_callback(GLFWwindow* window, int button, int action, int mods) { double mouse_x, mouse_y; glfwGetCursorPos(window, &mouse_x, &mouse_y); if (button == GLFW_MOUSE_BUTTON_LEFT && action == GLFW_PRESS) { cout << "Mouse clicked (" << mouse_x << "," << mouse_y << ")" << endl; selectedObject = NULL; float x = (2.0f*mouse_x) / windowWidth - 1.0f; float y = 1.0f - (2.0f*mouse_y) / windowHeight; cout << "x: " << x << ", y: " << y << endl; vec4 ray_clip = vec4(x, y, -1.0f, 1.0f); vec4 ray_eye = inverse(proj_mat) * ray_clip; ray_eye = vec4(vec2(ray_eye), -1.0f, 1.0f); vec4 ray_world = inverse(view_mat) * ray_eye; vec4 click_point; vec3 closest_point = vec3(0.0f, 0.0f, -FAR_CLIP); // Any valid point will be closer than the far clipping plane, so initial value to that SceneObject* closest_object = NULL; for (vector::iterator it = objects.begin(); it != objects.end(); it++) { if ((*it)->type == ObjectType::LASER) continue; for (unsigned int p_idx = 0; p_idx < (*it)->points.size(); p_idx += 9) { if (faceClicked( { vec3((*it)->points[p_idx], (*it)->points[p_idx + 1], (*it)->points[p_idx + 2]), vec3((*it)->points[p_idx + 3], (*it)->points[p_idx + 4], (*it)->points[p_idx + 5]), vec3((*it)->points[p_idx + 6], (*it)->points[p_idx + 7], (*it)->points[p_idx + 8]), }, *it, ray_world, vec4(cam_pos, 1.0f), click_point )) { click_point = view_mat * click_point; if (-NEAR_CLIP >= click_point.z && click_point.z > -FAR_CLIP && click_point.z > closest_point.z) { closest_point = vec3(click_point); closest_object = *it; } } } } if (closest_object == NULL) { cout << "No object was clicked" << endl; } else { clickedObject = closest_object; cout << "Clicked object: " << clickedObject->id << endl; } } } void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { key_state[key] = action; // should be true for GLFW_PRESS and GLFW_REPEAT key_down[key] = (action != GLFW_RELEASE); } /*** START OF REFACTORED CODE ***/ void framebuffer_size_callback(GLFWwindow* window, int width, int height) { cout << "Window resized to (" << width << ", " << height << ")" << endl; windowWidth = width; windowHeight = height; // make sure the viewport matches the new window dimensions; note that width and // height will be significantly larger than specified on retina displays. glViewport(0, 0, width, height); // TODO: Ideally, remove the window title bar when the window is maximized // Check https://github.com/glfw/glfw/issues/778 // This requires glfw3.3. I think I have to upgrade // Doesn't seem to be needed in OSX and also causes a segfault there //glfwSetWindowAttrib(window, GLFW_DECORATED, GLFW_FALSE); } /*** END OF REFACTORED CODE ***/ void APIENTRY debugGlCallback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam ) { string strMessage(message); // TODO: Use C++ strings directly char source_str[2048]; char type_str[2048]; char severity_str[2048]; switch (source) { case 0x8246: strcpy(source_str, "API"); break; case 0x8247: strcpy(source_str, "WINDOW_SYSTEM"); break; case 0x8248: strcpy(source_str, "SHADER_COMPILER"); break; case 0x8249: strcpy(source_str, "THIRD_PARTY"); break; case 0x824A: strcpy(source_str, "APPLICATION"); break; case 0x824B: strcpy(source_str, "OTHER"); break; default: strcpy(source_str, "undefined"); break; } switch (type) { case 0x824C: strcpy(type_str, "ERROR"); break; case 0x824D: strcpy(type_str, "DEPRECATED_BEHAVIOR"); break; case 0x824E: strcpy(type_str, "UNDEFINED_BEHAVIOR"); break; case 0x824F: strcpy(type_str, "PORTABILITY"); break; case 0x8250: strcpy(type_str, "PERFORMANCE"); break; case 0x8251: strcpy(type_str, "OTHER"); break; case 0x8268: strcpy(type_str, "MARKER"); break; case 0x8269: strcpy(type_str, "PUSH_GROUP"); break; case 0x826A: strcpy(type_str, "POP_GROUP"); break; default: strcpy(type_str, "undefined"); break; } switch (severity) { case 0x9146: strcpy(severity_str, "HIGH"); break; case 0x9147: strcpy(severity_str, "MEDIUM"); break; case 0x9148: strcpy(severity_str, "LOW"); break; case 0x826B: strcpy(severity_str, "NOTIFICATION"); break; default: strcpy(severity_str, "undefined"); break; } if (string(severity_str) != "NOTIFICATION") { cout << "OpenGL Error!!!" << endl; cout << "Source: " << string(source_str) << endl; cout << "Type: " << string(type_str) << endl; cout << "Severity: " << string(severity_str) << endl; cout << strMessage << endl; } } /*** START OF REFACTORED CODE ***/ GLuint loadShader(GLenum type, string file) { cout << "Loading shader from file " << file << endl; ifstream shaderFile(file); GLuint shaderId = 0; if (shaderFile.is_open()) { string line, shaderString; while(getline(shaderFile, line)) { shaderString += line + "\n"; } shaderFile.close(); const char* shaderCString = shaderString.c_str(); shaderId = glCreateShader(type); glShaderSource(shaderId, 1, &shaderCString, NULL); glCompileShader(shaderId); cout << "Loaded successfully" << endl; } else { cout << "Failed to load the file" << endl; } return shaderId; } GLuint loadShaderProgram(string vertexShaderPath, string fragmentShaderPath) { GLuint vs = loadShader(GL_VERTEX_SHADER, vertexShaderPath); GLuint fs = loadShader(GL_FRAGMENT_SHADER, fragmentShaderPath); GLuint shader_program = glCreateProgram(); glAttachShader(shader_program, vs); glAttachShader(shader_program, fs); glLinkProgram(shader_program); return shader_program; } unsigned char* loadImage(string file_name, int* x, int* y) { int n; int force_channels = 4; // This forces RGBA (4 bytes per pixel) unsigned char* image_data = stbi_load(file_name.c_str(), x, y, &n, force_channels); int width_in_bytes = *x * 4; unsigned char *top = NULL; unsigned char *bottom = NULL; unsigned char temp = 0; int half_height = *y / 2; // TODO: I should be able to use the line below instead of doing the flip // stbi_set_flip_vertically_on_load(true); // flip image upside-down to account for OpenGL treating lower-left as (0, 0) for (int row = 0; row < half_height; row++) { top = image_data + row * width_in_bytes; bottom = image_data + (*y - row - 1) * width_in_bytes; for (int col = 0; col < width_in_bytes; col++) { temp = *top; *top = *bottom; *bottom = temp; top++; bottom++; } } if (!image_data) { gl_log_err("ERROR: could not load %s", file_name.c_str()); cerr << "ERROR: could not load " << file_name << endl; get_log() << "ERROR: could not load " << file_name << endl; } // Not Power-of-2 check if ((*x & (*x - 1)) != 0 || (*y & (*y - 1)) != 0) { gl_log_err("WARNING: texture %s is not power-of-2 dimensions", file_name.c_str()); cerr << "WARNING: texture " << file_name << " is not power-of-2 dimensions" << endl; get_log() << "WARNING: texture " << file_name << " is not power-of-2 dimensions" << endl; } return image_data; } /*** END OF REFACTORED CODE ***/ bool faceClicked(array points, SceneObject* obj, vec4 world_ray, vec4 cam, vec4& click_point) { // LINE EQUATION: P = O + Dt // O = cam // D = ray_world // PLANE EQUATION: P dot n + d = 0 // n is the normal vector // d is the offset from the origin // Take the cross-product of two vectors on the plane to get the normal vec3 v1 = points[1] - points[0]; vec3 v2 = points[2] - points[0]; vec3 normal = vec3(v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x); vec3 local_ray = vec3(inverse(obj->model_mat) * world_ray); vec3 local_cam = vec3(inverse(obj->model_mat) * cam); local_ray = local_ray - local_cam; float d = -glm::dot(points[0], normal); float t = -(glm::dot(local_cam, normal) + d) / glm::dot(local_ray, normal); vec3 intersection = local_cam + t*local_ray; if (insideTriangle(intersection, points)) { click_point = obj->model_mat * vec4(intersection, 1.0f); return true; } else { return false; } } bool insideTriangle(vec3 p, array triangle_points) { vec3 v21 = triangle_points[1] - triangle_points[0]; vec3 v31 = triangle_points[2] - triangle_points[0]; vec3 pv1 = p - triangle_points[0]; float y = (pv1.y*v21.x - pv1.x*v21.y) / (v31.y*v21.x - v31.x*v21.y); float x = (pv1.x-y*v31.x) / v21.x; return x > 0.0f && y > 0.0f && x+y < 1.0f; } /*** START OF REFACTORED CODE ***/ // TODO: Pass a reference, not a pointer void initObject(SceneObject* obj) { obj->id = objects.size(); // currently unused obj->num_points = obj->points.size() / 3; obj->model_transform = mat4(1.0f); obj->deleted = false; obj->normals.reserve(obj->points.size()); for (unsigned int i = 0; i < obj->points.size(); i += 9) { vec3 point1 = vec3(obj->points[i], obj->points[i + 1], obj->points[i + 2]); vec3 point2 = vec3(obj->points[i + 3], obj->points[i + 4], obj->points[i + 5]); vec3 point3 = vec3(obj->points[i + 6], obj->points[i + 7], obj->points[i + 8]); vec3 normal = normalize(cross(point2 - point1, point3 - point1)); // Add the same normal for all 3 points for (int j = 0; j < 3; j++) { obj->normals.push_back(normal.x); obj->normals.push_back(normal.y); obj->normals.push_back(normal.z); } } if (obj->type == ObjectType::SHIP || obj->type == ObjectType::ASTEROID) { calculateObjectBoundingBox(obj); obj->bounding_center = vec3(obj->translate_mat * vec4(obj->bounding_center, 1.0f)); } } // TODO: Check if I can pass in a reference to obj instead (do this for all other functions as well) void addObjectToScene(SceneObject* obj, map& shaderBufferInfo, map& modelGroups, GLuint ubo) { objects.push_back(obj); BufferInfo* bufferInfo = &shaderBufferInfo[modelGroups[obj->type].shaderProgram]; // Check if the buffers aren't large enough to fit the new object and, if so, call // populateBuffers() to resize and repopupulate them if ((modelGroups[obj->type].vboCapacity < (modelGroups[obj->type].numPoints + obj->num_points) || bufferInfo->ubo_capacity <= bufferInfo->ubo_offset)) { if (leftLaser != NULL && leftLaser->deleted) { leftLaser = NULL; } if (rightLaser != NULL && rightLaser->deleted) { rightLaser = NULL; } populateBuffers(objects, shaderBufferInfo, modelGroups, ubo); } else { copyObjectDataToBuffers(*objects.back(), shaderBufferInfo, modelGroups, ubo); } } void removeObjectFromScene(SceneObject& obj, GLuint ubo) { if (!obj.deleted) { // Move the object outside the render bounds of the scene so it doesn't get rendered // TODO: Find a better way of hiding the object until the next time buffers are repopulated transformObject(obj, translate(mat4(1.0f), vec3(0.0f, 0.0f, FAR_CLIP * 1000.0f)), ubo); obj.deleted = true; } } // TODO: Pass a reference, not a pointer void calculateObjectBoundingBox(SceneObject* obj) { GLfloat min_x = obj->points[0]; GLfloat max_x = obj->points[0]; GLfloat min_y = obj->points[1]; GLfloat max_y = obj->points[1]; GLfloat min_z = obj->points[2]; GLfloat max_z = obj->points[2]; // start from the second point for (unsigned int i = 3; i < obj->points.size(); i += 3) { if (min_x > obj->points[i]) { min_x = obj->points[i]; } else if (max_x < obj->points[i]) { max_x = obj->points[i]; } if (min_y > obj->points[i + 1]) { min_y = obj->points[i + 1]; } else if (max_y < obj->points[i + 1]) { max_y = obj->points[i + 1]; } if (min_z > obj->points[i + 2]) { min_z = obj->points[i + 2]; } else if (max_z < obj->points[i + 2]) { max_z = obj->points[i + 2]; } } obj->bounding_center = vec3((min_x + max_x) / 2.0f, (min_y + max_y) / 2.0f, (min_z + max_z) / 2.0f); GLfloat radius_x = max_x - obj->bounding_center.x; GLfloat radius_y = max_y - obj->bounding_center.y; GLfloat radius_z = max_z - obj->bounding_center.z; // TODO: This actually underestimates the radius. Might need to be fixed at some point. // TODO: Does not take into account any scaling in the model matrix obj->bounding_radius = radius_x; if (obj->bounding_radius < radius_y) obj->bounding_radius = radius_y; if (obj->bounding_radius < radius_z) obj->bounding_radius = radius_z; for (unsigned int i = 0; i < obj->points.size(); i += 3) { obj->points[i] -= obj->bounding_center.x; obj->points[i + 1] -= obj->bounding_center.y; obj->points[i + 2] -= obj->bounding_center.z; } obj->bounding_center = vec3(0.0f, 0.0f, 0.0f); } SceneObject* createShip() { SceneObject* ship = new SceneObject(); ship->type = ObjectType::SHIP; ship->points = { //back -0.5f, 0.3f, 0.0f, -0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, -0.5f, 0.3f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f, 0.3f, 0.0f, // left back -0.5f, 0.3f, -2.0f, -0.5f, 0.0f, -2.0f, -0.5f, 0.0f, 0.0f, -0.5f, 0.3f, -2.0f, -0.5f, 0.0f, 0.0f, -0.5f, 0.3f, 0.0f, // right back 0.5f, 0.3f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, -2.0f, 0.5f, 0.3f, 0.0f, 0.5f, 0.0f, -2.0f, 0.5f, 0.3f, -2.0f, // left mid -0.25f, 0.3f, -3.0f, -0.25f, 0.0f, -3.0f, -0.5f, 0.0f, -2.0f, -0.25f, 0.3f, -3.0f, -0.5f, 0.0f, -2.0f, -0.5f, 0.3f, -2.0f, // right mid 0.5f, 0.3f, -2.0f, 0.5f, 0.0f, -2.0f, 0.25f, 0.0f, -3.0f, 0.5f, 0.3f, -2.0f, 0.25f, 0.0f, -3.0f, 0.25f, 0.3f, -3.0f, // left front 0.0f, 0.0f, -3.5f, -0.25f, 0.0f, -3.0f, -0.25f, 0.3f, -3.0f, // right front 0.25f, 0.3f, -3.0f, 0.25f, 0.0f, -3.0f, 0.0f, 0.0f, -3.5f, // top back -0.5f, 0.3f, -2.0f, -0.5f, 0.3f, 0.0f, 0.5f, 0.3f, 0.0f, -0.5f, 0.3f, -2.0f, 0.5f, 0.3f, 0.0f, 0.5f, 0.3f, -2.0f, // bottom back -0.5f, 0.0f, 0.0f, -0.5f, 0.0f, -2.0f, 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, -0.5f, 0.0f, -2.0f, 0.5f, 0.0f, -2.0f, // top mid -0.25f, 0.3f, -3.0f, -0.5f, 0.3f, -2.0f, 0.5f, 0.3f, -2.0f, -0.25f, 0.3f, -3.0f, 0.5f, 0.3f, -2.0f, 0.25f, 0.3f, -3.0f, // bottom mid -0.5f, 0.0f, -2.0f, -0.25f, 0.0f, -3.0f, 0.5f, 0.0f, -2.0f, 0.5f, 0.0f, -2.0f, -0.25f, 0.0f, -3.0f, 0.25f, 0.0f, -3.0f, // top front -0.25f, 0.3f, -3.0f, 0.25f, 0.3f, -3.0f, 0.0f, 0.0f, -3.5f, // bottom front 0.25f, 0.0f, -3.0f, -0.25f, 0.0f, -3.0f, 0.0f, 0.0f, -3.5f, // left wing start back -1.5f, 0.3f, 0.0f, -1.5f, 0.0f, 0.0f, -0.5f, 0.0f, 0.0f, -1.5f, 0.3f, 0.0f, -0.5f, 0.0f, 0.0f, -0.5f, 0.3f, 0.0f, // left wing start top -0.5f, 0.3f, -0.3f, -1.3f, 0.3f, -0.3f, -1.5f, 0.3f, 0.0f, -0.5f, 0.3f, -0.3f, -1.5f, 0.3f, 0.0f, -0.5f, 0.3f, 0.0f, // left wing start front -0.5f, 0.3f, -0.3f, -0.5f, 0.0f, -0.3f, -1.3f, 0.0f, -0.3f, -0.5f, 0.3f, -0.3f, -1.3f, 0.0f, -0.3f, -1.3f, 0.3f, -0.3f, // left wing start bottom -0.5f, 0.0f, 0.0f, -1.5f, 0.0f, 0.0f, -1.3f, 0.0f, -0.3f, -0.5f, 0.0f, 0.0f, -1.3f, 0.0f, -0.3f, -0.5f, 0.0f, -0.3f, // left wing end outside -1.5f, 0.3f, 0.0f, -2.2f, 0.15f, -0.8f, -1.5f, 0.0f, 0.0f, // left wing end top -1.3f, 0.3f, -0.3f, -2.2f, 0.15f, -0.8f, -1.5f, 0.3f, 0.0f, // left wing end front -1.3f, 0.0f, -0.3f, -2.2f, 0.15f, -0.8f, -1.3f, 0.3f, -0.3f, // left wing end bottom -1.5f, 0.0f, 0.0f, -2.2f, 0.15f, -0.8f, -1.3f, 0.0f, -0.3f, // right wing start back 1.5f, 0.0f, 0.0f, 1.5f, 0.3f, 0.0f, 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 1.5f, 0.3f, 0.0f, 0.5f, 0.3f, 0.0f, // right wing start top 1.3f, 0.3f, -0.3f, 0.5f, 0.3f, -0.3f, 1.5f, 0.3f, 0.0f, 1.5f, 0.3f, 0.0f, 0.5f, 0.3f, -0.3f, 0.5f, 0.3f, 0.0f, // right wing start front 0.5f, 0.0f, -0.3f, 0.5f, 0.3f, -0.3f, 1.3f, 0.0f, -0.3f, 1.3f, 0.0f, -0.3f, 0.5f, 0.3f, -0.3f, 1.3f, 0.3f, -0.3f, // right wing start bottom 1.5f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 1.3f, 0.0f, -0.3f, 1.3f, 0.0f, -0.3f, 0.5f, 0.0f, 0.0f, 0.5f, 0.0f, -0.3f, // right wing end outside 2.2f, 0.15f, -0.8f, 1.5f, 0.3f, 0.0f, 1.5f, 0.0f, 0.0f, // right wing end top 2.2f, 0.15f, -0.8f, 1.3f, 0.3f, -0.3f, 1.5f, 0.3f, 0.0f, // right wing end front 2.2f, 0.15f, -0.8f, 1.3f, 0.0f, -0.3f, 1.3f, 0.3f, -0.3f, // right wing end bottom 2.2f, 0.15f, -0.8f, 1.5f, 0.0f, 0.0f, 1.3f, 0.0f, -0.3f, }; ship->colors = { 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, 0.0f, 0.0f, 0.3f, }; ship->texcoords = { 0.0f }; mat4 T_model = translate(mat4(1.0f), vec3(0.0f, -1.2f, 1.65f)); mat4 R_model(1.0f); ship->model_base = T_model * R_model * scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f)); ship->translate_mat = T_model; initObject(ship); return ship; } /* LASER RENDERING/POSITIONING ALGORITHM * -Draw a thin rectangle for the laser beam, using the specified width and endpoints * -Texture the beam with a grayscale partially transparent image * -In the shader, blend the image with a color to support lasers of different colors * * The flat part of the textured rectangle needs to always face the camera, so the laser's width is constant * This is done as follows: * -Determine the length of the laser based on the start and end points * -Draw a rectangle along the z-axis and rotated upwards along the y-axis, with the correct final length and width * -Rotate the beam around the z-axis by the correct angle, sot that in its final position, the flat part faces the camera * -Rotate the beam along the x-axis and then along the y-axis and then translate it to put it into its final position */ // TODO: Make the color parameter have an effect Laser* createLaser(vec3 start, vec3 end, vec3 color, GLfloat width) { Laser* obj = new Laser(); obj->type = ObjectType::LASER; obj->targetAsteroid = NULL; vec3 ray = end - start; float length = glm::length(ray); obj->points = { width / 2, 0.0f, -width / 2, -width / 2, 0.0f, -width / 2, -width / 2, 0.0f, 0.0f, width / 2, 0.0f, -width / 2, -width / 2, 0.0f, 0.0f, width / 2, 0.0f, 0.0f, width / 2, 0.0f, -length + width / 2, -width / 2, 0.0f, -length + width / 2, -width / 2, 0.0f, -width / 2, width / 2, 0.0f, -length + width / 2, -width / 2, 0.0f, -width / 2, width / 2, 0.0f, -width / 2, width / 2, 0.0f, -length, -width / 2, 0.0f, -length, -width / 2, 0.0f, -length + width / 2, width / 2, 0.0f, -length, -width / 2, 0.0f, -length + width / 2, width / 2, 0.0f, -length + width / 2, }; obj->texcoords = { 1.0f, 0.5f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.51f, 0.0f, 0.51f, 0.0f, 0.49f, 1.0f, 0.51f, 0.0f, 0.49f, 1.0f, 0.49f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.5f, 1.0f, 1.0f, 0.0f, 0.5f, 1.0f, 0.5f, }; float xAxisRotation = asin(ray.y / length); float yAxisRotation = atan2(-ray.x, -ray.z); vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) * rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) * vec4(0.0f, 1.0f, 0.0f, 1.0f)); // To project point P onto line AB: // projection = A + dot(AP,AB) / dot(AB,AB) * AB vec3 projOnLaser = start + glm::dot(cam_pos - start, ray) / (length * length) * ray; vec3 laserToCam = cam_pos - projOnLaser; float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam)); obj->model_base = rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f)); initObject(obj); obj->model_transform = rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) * obj->model_transform; obj->model_transform = rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) * obj->model_transform; obj->model_transform = translate(mat4(1.0f), start) * obj->model_transform; return obj; } ShaderModelGroup createModelGroup(GLuint shaderProgram) { ShaderModelGroup smg; smg.shaderProgram = shaderProgram; glGenVertexArrays(1, &smg.vao); smg.numPoints = 0; return smg; } // TODO: Add the code to resize the buffers here // addObjectToScene and removeObjectFromScene pretty much already do this. // However, when addObjectToScene resizes the buffers, it resizes them for all object types // It would be more efficient to only resize them for the object type in question void removeModelFromGroup(ShaderModelGroup& modelGroup, SceneObject& model) { // TODO: Implement } void addModelToGroup(ShaderModelGroup& modelGroup, SceneObject& model) { // TODO: Implement } void defineModelGroupAttrib(ShaderModelGroup& modelGroup, string name, AttribType attribType, GLint size, GLenum type, size_t fieldOffset) { if (type != GL_FLOAT && type != GL_UNSIGNED_INT) { cout << "Unknown shader program attribute type: " << type << endl; return; } AttribInfo attribInfo; attribInfo.attribType = attribType; attribInfo.index = modelGroup.attribs.size(); attribInfo.size = size; attribInfo.type = type; attribInfo.fieldOffset = fieldOffset; modelGroup.attribs[name] = attribInfo; } void defineModelGroupUniform(ShaderModelGroup& modelGroup, string name, AttribType attribType, GLint size, UniformType type, GLfloat* data) { AttribInfo attribInfo; attribInfo.attribType = attribType; attribInfo.size = size; attribInfo.uniType = type; attribInfo.data = data; modelGroup.attribs[name] = attribInfo; } void initModelGroupAttribs(ShaderModelGroup& modelGroup) { glBindVertexArray(modelGroup.vao); map::iterator it; for (it = modelGroup.attribs.begin(); it != modelGroup.attribs.end(); it++) { if (it->second.attribType == AttribType::UNIFORM) { it->second.buffer = glGetUniformLocation(modelGroup.shaderProgram, it->first.c_str()); } else { glEnableVertexAttribArray(it->second.index); glGenBuffers(1, &it->second.buffer); glBindBuffer(GL_ARRAY_BUFFER, it->second.buffer); switch (it->second.type) { case GL_FLOAT: { glVertexAttribPointer(it->second.index, it->second.size, it->second.type, GL_FALSE, 0, NULL); break; } case GL_UNSIGNED_INT: { glVertexAttribIPointer(it->second.index, it->second.size, it->second.type, 0, NULL); break; } } } } } void bindUniformData(AttribInfo& attrib) { switch(attrib.uniType) { case UNIFORM_MATRIX_4F: glUniformMatrix4fv(attrib.buffer, attrib.size, GL_FALSE, attrib.data); break; case UNIFORM_1F: glUniform1fv(attrib.buffer, attrib.size, attrib.data); break; case UNIFORM_3F: glUniform3fv(attrib.buffer, attrib.size, attrib.data); break; case UNIFORM_NONE: break; } } void bindUniformData(AttribInfo& attrib, GLfloat *data) { switch(attrib.uniType) { case UNIFORM_MATRIX_4F: glUniformMatrix4fv(attrib.buffer, attrib.size, GL_FALSE, data); break; case UNIFORM_1F: glUniform1fv(attrib.buffer, attrib.size, data); break; case UNIFORM_3F: glUniform3fv(attrib.buffer, attrib.size, data); break; case UNIFORM_NONE: break; } } /* The purpose of this function is to replace the use of sizeof() when calling * function like glBufferSubData and using AttribInfo to get offsets and types * I need instead of hardcoding them. I can't save a type like GLfloat, but I cam * save GL_FLOAT and use this function to return sizeof(GLfloat) when GL_FLOAT is * passed. */ size_t GLsizeof(GLenum type) { switch (type) { case GL_FLOAT: return sizeof(GLfloat); case GL_UNSIGNED_INT: return sizeof(GLuint); default: cout << "Uknown GL type passed to GLsizeof: " << type << endl; return 0; } } /* This function returns a reference to the first element of a given vector * attribute in obj. The vector is assumed to hold GLfloats. If the same thing * needs to be done later for vectors of other types, we could pass in a GLenum, * and do something similar to GLsizeof */ GLvoid* getVectorAttribPtr(SceneObject& obj, size_t attribOffset) { return (GLvoid*)(&(*(vector*)((size_t)&obj + attribOffset))[0]); } GLvoid* getScalarAttribPtr(SceneObject& obj, size_t attribOffset) { return (GLvoid*)((size_t)&obj + attribOffset); } void populateBuffers(vector& objects, map& shaderBufferInfo, map& modelGroups, GLuint ubo) { GLsizeiptr num_points = 0; GLsizeiptr num_objects = 0; map shaderCounts; map shaderUboCounts; map::iterator shaderIt; for (shaderIt = shaderBufferInfo.begin(); shaderIt != shaderBufferInfo.end(); shaderIt++) { shaderCounts[shaderIt->first] = 0; shaderUboCounts[shaderIt->first] = 0; } vector::iterator it; /* Find all shaders that need to be used and the number of objects and * number of points for each shader. Construct a map from shader id to count * of points being drawn using that shader (for thw model matrix ubo, we * need object counts instead). These will be used to get offsets into the * vertex buffer for each shader. */ for (it = objects.begin(); it != objects.end(); ) { if ((*it)->deleted) { delete *it; it = objects.erase(it); } else { num_points += (*it)->num_points; num_objects++; shaderCounts[modelGroups[(*it)->type].shaderProgram] += (*it)->num_points; shaderUboCounts[modelGroups[(*it)->type].shaderProgram]++; it++; } } // double the buffer sizes to leave room for new objects num_points *= 2; num_objects *= 2; map::iterator shaderCountIt; unsigned int lastShaderCount = 0; unsigned int lastShaderUboCount = 0; /* * The counts calculated above can be used to get the starting offset of * each shader in the vertex buffer. Create a map of base offsets to mark * where the data for the first object using a given shader begins. Also, * create a map of current offsets to mark where to copy data for the next * object being added. */ for (shaderCountIt = shaderCounts.begin(); shaderCountIt != shaderCounts.end(); shaderCountIt++) { // When populating the buffers, leave as much empty space as space taken up by existing objects // to allow new objects to be added without immediately having to resize the buffers shaderBufferInfo[shaderCountIt->first].ubo_base = lastShaderUboCount * 2; shaderBufferInfo[shaderCountIt->first].ubo_offset = 0; shaderBufferInfo[shaderCountIt->first].ubo_capacity = shaderUboCounts[shaderCountIt->first] * 2; lastShaderCount += shaderCounts[shaderCountIt->first]; lastShaderUboCount += shaderUboCounts[shaderCountIt->first]; } map::iterator modelGroupIt; ShaderModelGroup* smg; for (modelGroupIt = modelGroups.begin(); modelGroupIt != modelGroups.end(); modelGroupIt++) { smg = &modelGroups[modelGroupIt->first]; smg->numPoints = 0; smg->vboCapacity = shaderCounts[smg->shaderProgram] * 2; map::iterator attrIt; for (attrIt = smg->attribs.begin(); attrIt != smg->attribs.end(); attrIt++) { if (attrIt->second.attribType != AttribType::UNIFORM) { glBindBuffer(GL_ARRAY_BUFFER, attrIt->second.buffer); glBufferData(GL_ARRAY_BUFFER, smg->vboCapacity * GLsizeof(attrIt->second.type) * attrIt->second.size, NULL, GL_DYNAMIC_DRAW); } } } // Allocate the ubo using the counts calculated above glBindBuffer(GL_UNIFORM_BUFFER, ubo); glBufferData(GL_UNIFORM_BUFFER, num_objects * sizeof(mat4), NULL, GL_DYNAMIC_DRAW); for (it = objects.begin(); it != objects.end(); it++) { copyObjectDataToBuffers(**it, shaderBufferInfo, modelGroups, ubo); } } void copyObjectDataToBuffers(SceneObject& obj, map& shaderBufferInfo, map& modelGroups, GLuint ubo) { BufferInfo* bufferInfo = &shaderBufferInfo[modelGroups[obj.type].shaderProgram]; obj.vertex_vbo_offset = modelGroups[obj.type].numPoints; obj.ubo_offset = bufferInfo->ubo_base + bufferInfo->ubo_offset; ShaderModelGroup& smg = modelGroups[obj.type]; for (map::iterator it = smg.attribs.begin(); it != smg.attribs.end(); it++) { switch (it->second.attribType) { case AttribType::POINT_VARYING: glBindBuffer(GL_ARRAY_BUFFER, it->second.buffer); glBufferSubData(GL_ARRAY_BUFFER, obj.vertex_vbo_offset * GLsizeof(it->second.type) * it->second.size, obj.num_points * GLsizeof(it->second.type) * it->second.size, getVectorAttribPtr(obj, it->second.fieldOffset)); break; case AttribType::OBJECT_VARYING: glBindBuffer(GL_ARRAY_BUFFER, it->second.buffer); for (unsigned int i = 0; i < obj.num_points; i++) { glBufferSubData(GL_ARRAY_BUFFER, (obj.vertex_vbo_offset + i) * GLsizeof(it->second.type) * it->second.size, GLsizeof(it->second.type) * it->second.size, getScalarAttribPtr(obj, it->second.fieldOffset)); } break; case AttribType::UNIFORM: break; } } obj.model_mat = obj.model_transform * obj.model_base; glBindBuffer(GL_UNIFORM_BUFFER, ubo); glBufferSubData(GL_UNIFORM_BUFFER, obj.ubo_offset * sizeof(mat4), sizeof(mat4), value_ptr(obj.model_mat)); if (obj.type == ObjectType::ASTEROID) { glUseProgram(modelGroups[ObjectType::ASTEROID].shaderProgram); ostringstream oss; oss << "hp[" << obj.ubo_offset << "]"; glUniform1f(glGetUniformLocation(modelGroups[ObjectType::ASTEROID].shaderProgram, oss.str().c_str()), ((Asteroid&)obj).hp); } else if (obj.type == ObjectType::EXPLOSION) { glUseProgram(modelGroups[ObjectType::EXPLOSION].shaderProgram); ostringstream oss; oss << "explosion_start_time[" << obj.ubo_offset << "]"; glUniform1f(glGetUniformLocation(modelGroups[ObjectType::EXPLOSION].shaderProgram, oss.str().c_str()), ((ParticleEffect&)obj).startTime); } modelGroups[obj.type].numPoints += obj.num_points; bufferInfo->ubo_offset++; } void transformObject(SceneObject& obj, const mat4& transform, GLuint ubo) { if (obj.deleted) return; obj.model_transform = transform * obj.model_transform; obj.model_mat = obj.model_transform * obj.model_base; obj.bounding_center = vec3(transform * vec4(obj.bounding_center, 1.0f)); glBindBuffer(GL_UNIFORM_BUFFER, ubo); glBufferSubData(GL_UNIFORM_BUFFER, obj.ubo_offset * sizeof(mat4), sizeof(mat4), value_ptr(obj.model_mat)); } void translateLaser(Laser* laser, const vec3& translation, GLuint ubo) { // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created, // and then re-used here vec3 start = vec3(laser->model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f)); vec3 end = vec3(laser->model_transform * vec4(0.0f, 0.0f, laser->points[38], 1.0f)); vec3 ray = end - start; float length = glm::length(ray); float xAxisRotation = asin(ray.y / length); float yAxisRotation = atan2(-ray.x, -ray.z); vec3 normal(rotate(mat4(1.0f), yAxisRotation, vec3(0.0f, 1.0f, 0.0f)) * rotate(mat4(1.0f), xAxisRotation, vec3(1.0f, 0.0f, 0.0f)) * vec4(0.0f, 1.0f, 0.0f, 1.0f)); // To project point P onto line AB: // projection = A + dot(AP,AB) / dot(AB,AB) * AB vec3 projOnLaser = start + glm::dot(cam_pos - start, ray) / (length*length) * ray; vec3 laserToCam = cam_pos - projOnLaser; float zAxisRotation = -atan2(glm::dot(glm::cross(normal, laserToCam), glm::normalize(ray)), glm::dot(normal, laserToCam)); laser->model_base = rotate(mat4(1.0f), zAxisRotation, vec3(0.0f, 0.0f, 1.0f)); transformObject(*laser, translate(mat4(1.0f), translation), ubo); } void updateLaserTarget(Laser* laser, vector& objects, ShaderModelGroup& laserSmg, GLuint asteroid_sp) { // TODO: A lot of the values calculated here can be calculated once and saved when the laser is created, // and then re-used here vec3 start = vec3(laser->model_transform * vec4(0.0f, 0.0f, 0.0f, 1.0f)); vec3 end = vec3(laser->model_transform * vec4(0.0f, 0.0f, laser->points[2] + laser->points[20], 1.0f)); vec3 intersection(0.0f), closestIntersection(0.0f); Asteroid* closestAsteroid = NULL; for (vector::iterator it = objects.begin(); it != objects.end(); it++) { if ((*it)->type == ObjectType::ASTEROID && !(*it)->deleted && getLaserAndAsteroidIntersection(start, end, **it, intersection)) { // TODO: Implement a more generic algorithm for testing the closest object by getting the distance between the points if (closestAsteroid == NULL || intersection.z > closestIntersection.z) { // TODO: At this point, find the real intersection of the laser with one of the asteroid's sides closestAsteroid = (Asteroid*)*it; closestIntersection = intersection; } } } float width = laser->points[0] - laser->points[2]; if (laser->targetAsteroid != closestAsteroid) { if (laser->targetAsteroid != NULL) { if (laser == leftLaser) { leftLaserEffect->deleted = true; } else if (laser == rightLaser) { rightLaserEffect->deleted = true; } } EffectOverTime* eot = NULL; if (closestAsteroid != NULL) { eot = new EffectOverTime(closestAsteroid->hp, -20.0f, closestAsteroid); effects.push_back(eot); } if (laser == leftLaser) { leftLaserEffect = eot; } else if (laser == rightLaser) { rightLaserEffect = eot; } } laser->targetAsteroid = closestAsteroid; float length = 5.24f; // I think this was to make sure the laser went past the end of the screen if (closestAsteroid != NULL) { length = glm::length(closestIntersection - start); // TODO: Find a more generic way of updating the asteroid hp than in updateLaserTarget glUseProgram(asteroid_sp); ostringstream oss; oss << "hp[" << closestAsteroid->ubo_offset << "]"; glUniform1f(glGetUniformLocation(asteroid_sp, oss.str().c_str()), closestAsteroid->hp); } laser->points[20] = -length + width / 2; laser->points[23] = -length + width / 2; laser->points[29] = -length + width / 2; laser->points[38] = -length; laser->points[41] = -length; laser->points[44] = -length + width / 2; laser->points[47] = -length; laser->points[50] = -length + width / 2; laser->points[53] = -length + width / 2; AttribInfo* attrib = &laserSmg.attribs["vertex_position"]; glBindBuffer(GL_ARRAY_BUFFER, attrib->buffer); glBufferSubData(GL_ARRAY_BUFFER, laser->vertex_vbo_offset * GLsizeof(attrib->type) * attrib->size, laser->num_points * GLsizeof(attrib->type) * attrib->size, getVectorAttribPtr(*laser, attrib->fieldOffset)); } bool getLaserAndAsteroidIntersection(vec3& start, vec3& end, SceneObject& asteroid, vec3& intersection) { /* ### LINE EQUATIONS ### x = x1 + u * (x2 - x1) y = y1 + u * (y2 - y1) z = z1 + u * (z2 - z1) ### SPHERE EQUATION ### (x - x3)^2 + (y - y3)^2 + (z - z3)^2 = r^2 ### QUADRATIC EQUATION TO SOLVE ### a*u^2 + b*u + c = 0 WHERE THE CONSTANTS ARE a = (x2 - x1)^2 + (y2 - y1)^2 + (z2 - z1)^2 b = 2*( (x2 - x1)*(x1 - x3) + (y2 - y1)*(y1 - y3) + (z2 - z1)*(z1 - z3) ) c = x3^2 + y3^2 + z3^2 + x1^2 + y1^2 + z1^2 - 2(x3*x1 + y3*y1 + z3*z1) - r^2 u = (-b +- sqrt(b^2 - 4*a*c)) / 2a If the value under the root is >= 0, we got an intersection If the value > 0, there are two solutions. Take the one closer to 0, since that's the one closer to the laser start point */ vec3& center = asteroid.bounding_center; float a = pow(end.x-start.x, 2) + pow(end.y-start.y, 2) + pow(end.z-start.z, 2); float b = 2*((start.x-end.x)*(start.x-center.x) + (end.y-start.y)*(start.y-center.y) + (end.z-start.z)*(start.z-center.z)); float c = pow(center.x, 2) + pow(center.y, 2) + pow(center.z, 2) + pow(start.x, 2) + pow(start.y, 2) + pow(start.z, 2) - 2*(center.x*start.x + center.y*start.y + center.z*start.z) - pow(asteroid.bounding_radius, 2); float discriminant = pow(b, 2) - 4*a*c; if (discriminant >= 0.0f) { // In this case, the negative root will always give the point closer to the laser start point float u = (-b - sqrt(discriminant)) / (2 * a); // Check that the intersection is within the line segment corresponding to the laser if (0.0f <= u && u <= 1.0f) { intersection = start + u * (end - start); return true; } } return false; } void renderScene(map& modelGroups, GLuint ubo) { glUseProgram(modelGroups[ObjectType::SHIP].shaderProgram); glBindVertexArray(modelGroups[ObjectType::SHIP].vao); glDrawArrays(GL_TRIANGLES, 0, modelGroups[ObjectType::SHIP].numPoints); glUseProgram(modelGroups[ObjectType::ASTEROID].shaderProgram); glBindVertexArray(modelGroups[ObjectType::ASTEROID].vao); glDrawArrays(GL_TRIANGLES, 0, modelGroups[ObjectType::ASTEROID].numPoints); glEnable(GL_BLEND); glUseProgram(modelGroups[ObjectType::LASER].shaderProgram); glBindVertexArray(modelGroups[ObjectType::LASER].vao); glDrawArrays(GL_TRIANGLES, 0, modelGroups[ObjectType::LASER].numPoints); glUseProgram(modelGroups[ObjectType::EXPLOSION].shaderProgram); glBindVertexArray(modelGroups[ObjectType::EXPLOSION].vao); glEnable(GL_PROGRAM_POINT_SIZE); glDrawArrays(GL_POINTS, 0, modelGroups[ObjectType::EXPLOSION].numPoints); glDisable(GL_PROGRAM_POINT_SIZE); glDisable(GL_BLEND); } /*** END OF REFACTORED CODE ***/ void renderSceneGui(map> valueLists) { ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); // 1. Show a simple window. // Tip: if we don't call ImGui::Begin()/ImGui::End() the widgets automatically appears in a window called "Debug". /* { static float f = 0.0f; static int counter = 0; ImGui::Text("Hello, world!"); // Display some text (you can use a format string too) ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our windows open/close state ImGui::Checkbox("Another Window", &show_another_window); if (ImGui::Button("Button")) // Buttons return true when clicked (NB: most widgets return true when edited/activated) counter++; ImGui::SameLine(); ImGui::Text("counter = %d", counter); ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); } */ { ImGui::SetNextWindowSize(ImVec2(95, 46), ImGuiCond_Once); ImGui::SetNextWindowPos(ImVec2(10, 50), ImGuiCond_Once); ImGui::Begin("WndStats", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); renderGuiValueList(valueLists["stats value list"]); ImGui::End(); } { ImGui::SetNextWindowSize(ImVec2(250, 35), ImGuiCond_Once); ImGui::SetNextWindowPos(ImVec2(380, 10), ImGuiCond_Once); ImGui::Begin("WndMenubar", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); ImGui::InvisibleButton("", ImVec2(155, 18)); ImGui::SameLine(); if (ImGui::Button("Main Menu")) { events.push(Event::GO_TO_MAIN_MENU); } ImGui::End(); } { ImGui::SetNextWindowSize(ImVec2(200, 200), ImGuiCond_Once); ImGui::SetNextWindowPos(ImVec2(430, 60), ImGuiCond_Once); ImGui::Begin("WndDebug", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); renderGuiValueList(valueLists["debug value list"]); ImGui::End(); } ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); } void renderMainMenu() { } /*** START OF REFACTORED CODE ***/ void renderMainMenuGui() { ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); { int padding = 4; ImGui::SetNextWindowPos(ImVec2(-padding, -padding), ImGuiCond_Once); ImGui::SetNextWindowSize(ImVec2(windowWidth + 2 * padding, windowHeight + 2 * padding), ImGuiCond_Always); ImGui::Begin("WndMain", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); ImGui::InvisibleButton("", ImVec2(10, 80)); ImGui::InvisibleButton("", ImVec2(285, 18)); ImGui::SameLine(); if (ImGui::Button("New Game")) { events.push(Event::GO_TO_GAME); } ImGui::InvisibleButton("", ImVec2(10, 15)); ImGui::InvisibleButton("", ImVec2(300, 18)); ImGui::SameLine(); if (ImGui::Button("Quit")) { events.push(Event::QUIT); } ImGui::End(); } ImGui::Render(); ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); } /*** END OF REFACTORED CODE ***/ void initGuiValueLists(map> valueLists) { valueLists["stats value list"] = vector(); valueLists["debug value list"] = vector(); } void renderGuiValueList(vector& values) { float maxWidth = 0.0f; float cursorStartPos = ImGui::GetCursorPosX(); for (vector::iterator it = values.begin(); it != values.end(); it++) { float textWidth = ImGui::CalcTextSize(it->label.c_str()).x; if (maxWidth < textWidth) maxWidth = textWidth; } stringstream ss; for (vector::iterator it = values.begin(); it != values.end(); it++) { ss.str(""); ss.clear(); switch (it->type) { case UIVALUE_INT: ss << it->label << ": " << *(unsigned int*)it->value; break; case UIVALUE_DOUBLE: ss << it->label << ": " << *(double*)it->value; break; } float textWidth = ImGui::CalcTextSize(it->label.c_str()).x; ImGui::SetCursorPosX(cursorStartPos + maxWidth - textWidth); ImGui::Text("%s", ss.str().c_str()); } } /*** START OF REFACTORED CODE ***/ Asteroid* createAsteroid(vec3 pos) { Asteroid* obj = new Asteroid(); obj->type = ObjectType::ASTEROID; obj->hp = 10.0f; obj->points = { // front 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, // top 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, // bottom 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, // back 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, // right 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, // left -1.0f, 1.0f, 1.0f, -1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, }; obj->colors = { // front 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, // top 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, // bottom 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, // back 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, // right 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, // left 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, 0.4f, }; obj->texcoords = { 0.0f }; mat4 T = translate(mat4(1.0f), pos); mat4 R = rotate(mat4(1.0f), radians(60.0f), vec3(1.0f, 1.0f, -1.0f)); obj->model_base = T * R * scale(mat4(1.0f), vec3(0.1f, 0.1f, 0.1f)); obj->translate_mat = T; initObject(obj); // This accounts for the scaling in model_base. // Dividing by 8 instead of 10 since the bounding radius algorithm // under-calculates the true value. // TODO: Once the intersection check with the sides of the asteroid is done, // this can be removed. obj->bounding_radius /= 8.0f; return obj; } // TODO: Maybe pass in startTime instead of calling glfwGetTime() here ParticleEffect* createExplosion(mat4 model_mat) { ParticleEffect* obj = new ParticleEffect(); obj->type = ObjectType::EXPLOSION; initObject(obj); obj->num_points = EXPLOSION_PARTICLE_COUNT; obj->model_base = model_mat; obj->startTime = glfwGetTime(); obj->duration = 0.5f; // This is also hard-coded in the shader. TODO; Pass this to the shader in an indexable ubo. obj->particleVelocities.clear(); obj->particleTimes.clear(); float t_accum = 0.0f; // start time for (int i = 0; i < EXPLOSION_PARTICLE_COUNT; i++) { obj->particleTimes.push_back(t_accum); t_accum += 0.01f; float randx = ((float)rand() / (float)RAND_MAX) - 0.5f; float randy = ((float)rand() / (float)RAND_MAX) - 0.5f; obj->particleVelocities.push_back(randx); obj->particleVelocities.push_back(randy); obj->particleVelocities.push_back(0.0f); } return obj; } /*** END OF REFACTORED CODE ***/