#include "GameEngine.h" #include "Key.h" #include "Mouse.h" #include Clock GameEngine::tps; Clock GameEngine::fps; InitFunction GameEngine::init = nullptr; TickFunction GameEngine::tick = nullptr; RenderTickFunction GameEngine::renderTick = nullptr; GLuint GameEngine::vShader = 0; GLuint GameEngine::fShader = 0; GLFWwindow* GameEngine::window = nullptr; GLuint GameEngine::program = 0; int GameEngine::scale = 1; int GameEngine::width = 0; int GameEngine::height = 0; float GameEngine::fovY = 60; float GameEngine::nearClip = 0.1f; float GameEngine::farClip = 1000.0f; Matrix3D GameEngine::proj; Camera GameEngine::camera; DirectRenderer GameEngine::directRenderer; GLint GameEngine::unifProjMatrix = 0; GLint GameEngine::unifViewMatrix = 0; GLint GameEngine::unifUseTexture = 0; GLint GameEngine::unifUseColor = 0; GLint GameEngine::unifUseMixColor = 0; GLint GameEngine::unifMixColorLoc = 0; Camera& GameEngine::getCamera() { return camera; } DirectRenderer& GameEngine::getDirectRenderer() { return directRenderer; } void GameEngine::onKeyEvent(GLFWwindow* w, int key, int scancode, int action, int mods) { if(action == GLFW_RELEASE) { if(key == GLFW_KEY_ESCAPE) { //activeFocus = 0; glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_NORMAL); } Key::release(key); } else if(action == GLFW_PRESS) { Key::press(key); } } void GameEngine::onMouseClick(GLFWwindow* w, int button, int action, int mods) { if(action == GLFW_PRESS) { /*if(!activeFocus) { glfwSetInputMode(w, GLFW_CURSOR, GLFW_CURSOR_DISABLED); oldMouseX = 0; oldMouseY = 0; activeFocus = 1; } else {*/ Mouse::press(button); //} } else if(action == GLFW_RELEASE) { Mouse::release(button); } } /*static void onMouseMove(GLFWwindow* w, double x, double y) { if(activeFocus) { if(oldMouseX == 0 && oldMouseY == 0) { oldMouseX = x; oldMouseY = y; } else { mouseMove(x - oldMouseX, y - oldMouseY); oldMouseX = x; oldMouseY = y; } } }*/ void GameEngine::onWindowResize(GLFWwindow* w, int width, int height) { glViewport(0, 0, width, height); GameEngine::width = width; GameEngine::height = height; updateScale(); } GLchar* GameEngine::readFile(const char* name) { ifstream in; in.open(name); if(!in.fail()) { int size = 128; int index = 0; GLchar* content = new GLchar[size]; while(true) { GLchar c = in.get(); if(in.eof()) { break; } if(index >= size - 1) { GLchar* newContent = new GLchar[size * 2]; memcpy(newContent, content, size); size *= 2; delete[] content; content = newContent; } content[index] = c; index++; } content[index] = '\0'; index++; in.close(); return content; } return nullptr; } bool GameEngine::checkShaderErrors(const char* name, GLuint shader) { bool returnValue = false; cout << "compiling " << name << " shader ..." << endl; GLenum error = glGetError(); if(error) { cout << "error: " << glGetError() << endl; returnValue = true; } else { cout << "no error occured ..." << endl; } GLint compiled[1]; glGetShaderiv(shader, GL_COMPILE_STATUS, compiled); if(compiled[0]) { cout << name << " shader successfully compiled" << endl; } else { cout << name << "compiling of " << name << " failed:" << endl; GLchar buffer[512]; GLsizei bufferSize = 512; GLsizei charsUsed = 0; glGetShaderInfoLog(shader, bufferSize, &charsUsed, buffer); // glGetProgramInfoLog should be null terminated ... buffer[bufferSize - 1] = '\0'; cout << buffer << endl; returnValue = true; } return returnValue; } GLuint GameEngine::compileProgram(const GLchar* vertex, const GLchar* fragment) { vShader = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vShader, 1, &vertex, nullptr); glCompileShader(vShader); if(checkShaderErrors("vertex", vShader)) { return 0; } fShader = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fShader, 1, &fragment, nullptr); glCompileShader(fShader); if(checkShaderErrors("fragment", fShader)) { return 0; } GLuint program = glCreateProgram(); glAttachShader(program, vShader); glAttachShader(program, fShader); glLinkProgram(program); cout << "linking shaders to program ..." << endl; GLenum error = glGetError(); if(error) { cout << "error: " << glGetError() << endl; return 0; } else { cout << "no error occured ..." << endl; } GLint compiled[1]; glGetProgramiv(program, GL_LINK_STATUS, compiled); if(compiled[0]) { cout << "shaders successfully linked" << endl; } else { cout << "linking of shaders failed:" << endl; GLchar buffer[512]; GLsizei bufferSize = 512; GLsizei charsUsed = 0; glGetProgramInfoLog(program, bufferSize, &charsUsed, buffer); // glGetProgramInfoLog should be null terminated ... buffer[bufferSize - 1] = '\0'; cout << buffer << endl; return 0; } glUseProgram(program); return program; } GLuint GameEngine::createProgram() { GLchar* vertex = readFile("shader/vertex.vs"); if(vertex == nullptr) { cout << "cannot read vertex.vs" << endl; return 0; } GLchar* fragment = readFile("shader/fragment.fs"); if(fragment == nullptr) { cout << "cannot read fragment.fs" << endl; delete[] vertex; return 0; } GLuint program = compileProgram(vertex, fragment); delete[] vertex; delete[] fragment; return program; } void GameEngine::updateProjection() { float q = 1.0f / tanf((0.5f * fovY) * M_PI / 180.0f); proj.set(0, 0, (q * height) / width); proj.set(1, 1, q); proj.set(2, 2, (nearClip + farClip) / (nearClip - farClip)); proj.set(3, 2, -1.0f); proj.set(2, 3, (2.0f * nearClip * farClip) / (nearClip - farClip)); proj.set(3, 3, 0); glUniformMatrix4fv(unifProjMatrix, 1, 0, proj.getValues()); } void GameEngine::updateView(Matrix3D& m) { glUniformMatrix4fv(unifViewMatrix, 1, 0, m.getValues()); } void GameEngine::onInit() { Key::init(); Mouse::init(); init(); unifProjMatrix = glGetUniformLocation(program, "projMatrix"); unifViewMatrix = glGetUniformLocation(program, "viewMatrix"); unifUseTexture = glGetUniformLocation(program, "useTexture"); unifUseColor = glGetUniformLocation(program, "useColor"); unifUseMixColor = glGetUniformLocation(program, "useMixColor"); unifMixColorLoc = glGetUniformLocation(program, "mixColor"); directRenderer.init(); } void GameEngine::onTick() { tick(); Key::tick(); Mouse::tick(); } void GameEngine::start(int width, int height, const char* name, InitFunction init, TickFunction tick, RenderTickFunction renderTick) { GameEngine::width = width; GameEngine::height = height; GameEngine::init = init; GameEngine::tick = tick; GameEngine::renderTick = renderTick; updateScale(); if(!glfwInit()) { cout << "could not initialize GLFW" << endl; return; } glfwDefaultWindowHints(); glfwWindowHint(GLFW_VISIBLE, 0); glfwWindowHint(GLFW_RESIZABLE, 1); window = glfwCreateWindow(width, height, name, nullptr, nullptr); if(!window) { cout << "could not create window" << endl; glfwTerminate(); return; } glfwSetKeyCallback(window, onKeyEvent); glfwSetFramebufferSizeCallback(window, onWindowResize); glfwSetMouseButtonCallback(window, onMouseClick); //glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); //glfwSetCursorPosCallback(window, onMouseMove); glfwMakeContextCurrent(window); glfwSwapInterval(1); glfwShowWindow(window); GLenum err = glewInit(); if(GLEW_OK != err) { cout << "could not initialize GLEW: " << glewGetErrorString(err) << endl; return; } cout << "Status: Using GLEW " << glewGetString(GLEW_VERSION) << endl; program = createProgram(); if(program == 0) { onTerm(); return; } glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); onInit(); uint64_t newTime = glfwGetTimerValue(); uint64_t oldTime = newTime; uint64_t lag = 0; while(!glfwWindowShouldClose(window)) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); oldTime = newTime; newTime = glfwGetTimerValue(); lag += newTime - oldTime; int ticksPerFrame = 0; while(lag >= NANOS_PER_TICK) { lag -= NANOS_PER_TICK; tps.update(); onTick(); ticksPerFrame++; if(ticksPerFrame >= MAX_TICKS_PER_FRAME) { long skip = lag / NANOS_PER_TICK; lag -= skip * NANOS_PER_TICK; if(skip > 0) { cout << "skipped " << skip << " game ticks " << lag << endl; } break; } } updateProjection(); fps.update(); renderTick((float) lag / NANOS_PER_TICK); glfwSwapBuffers(window); glfwPollEvents(); } onTerm(); } void GameEngine::onTerm() { if(vShader != 0) { glDeleteShader(vShader); } if(fShader != 0) { glDeleteShader(fShader); } if(program != 0) { glDeleteProgram(program); } glfwDestroyWindow(window); glfwTerminate(); } void GameEngine::setTextureEnabled(bool use) { glUniform1i(unifUseTexture, use); } void GameEngine::setColorEnabled(bool use) { glUniform1i(unifUseColor, use); } void GameEngine::setMixColorEnabled(bool use) { glUniform1i(unifUseMixColor, use); } void GameEngine::setMixColor(float r, float g, float b, float a) { glUniform4f(unifMixColorLoc, r, g, b, a); } void GameEngine::setUseBlending(bool use) { if(use) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendEquation(GL_FUNC_ADD); } else { glDisable(GL_BLEND); } } void GameEngine::printError() { GLenum error = glGetError(); switch(error) { case GL_NO_ERROR: cout << "> No error has been recorded." << endl; break; case GL_INVALID_ENUM: cout << "> An unacceptable value is specified for an enumerated argument." << endl; break; case GL_INVALID_VALUE: cout << "> A numeric argument is out of range." << endl; break; case GL_INVALID_OPERATION: cout << "> The specified operation is not allowed in the current state." << endl; break; case GL_INVALID_FRAMEBUFFER_OPERATION: cout << "> The framebuffer object is not complete." << endl; break; case GL_OUT_OF_MEMORY: cout << "> There is not enough memory left to execute the command." << endl; break; case GL_STACK_UNDERFLOW: cout << "> An attempt has been made to perform an operation that would cause an internal stack to underflow." << endl; break; case GL_STACK_OVERFLOW: cout << "> An attempt has been made to perform an operation that would cause an internal stack to overflow." << endl; break; default: cout << "> Unknown OpenGL error: " << error << endl; } } void GameEngine::stop() { glfwSetWindowShouldClose(window, 1); } void GameEngine::updateScale() { scale = 1; while(width / (scale + 1) >= 400 && height / (scale + 1) >= 300) { scale++; } } double GameEngine::getTicksPerSecond() { return tps.getUpdatesPerSecond(); } double GameEngine::getFramesPerSecond() { return fps.getUpdatesPerSecond(); }