325 lines
9.8 KiB
C++

#include "engine.h"
#include <chrono>
#include <cstring>
#include <iostream>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/gtc/type_ptr.hpp>
#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <stb_image.h>
#include <stb_image_write.h>
#include "camera.h"
#include "constants.h"
#include "defaults.h"
#include "errordialog.h"
#include "gameobject.h"
#include "objparser.h"
#include "physics.h"
#include "resource.h"
#include "scene.h"
#include "ui.h"
#include "uielements.h"
#include "internal.h"
#include "internal/input.h"
#include "internal/ui.h"
kek::KekData kek::kekData;
namespace kek::Engine {
static TextElement *fpsText;
static void framebufferSizeCallback(GLFWwindow *window, int w, int h) {
glViewport(0, 0, w, h);
kekData.screenWidth = w;
kekData.screenHeight = h;
}
static void glDebugOutput(GLenum source, GLenum type, unsigned int id, GLenum severity, GLsizei length, const char *message, const void *userParam) {
// if(id == 0x20071) return;
if(severity == GL_DEBUG_SEVERITY_NOTIFICATION) return;
std::cout << "OpenGL Debug (" << id << "): " << message << std::endl;
}
int init() {
// Init GLFW
if(glfwInit() != GL_TRUE) {
const char *errorMsg;
int code = glfwGetError(&errorMsg);
if(code != GLFW_NO_ERROR) {
ErrorDialog::showError("Failed to initialize GLFW: " + std::string(errorMsg) + std::string(" (") + std::to_string(code) + std::string(")"));
} else {
ErrorDialog::showError("Failed to initialize GLFW: Unknown error");
}
return KEK_ERROR;
}
std::cout << "Initialized GLFW" << std::endl;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_SAMPLES, 4);
glfwWindowHint(GLFW_MAXIMIZED, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE);
kekData.screenWidth = 800.0f;
kekData.screenHeight = 600.0f;
kekData.window = glfwCreateWindow(kekData.screenWidth, kekData.screenHeight, "KekEngine", nullptr, nullptr);
if(!kekData.window) {
const char *errorMsg;
int code = glfwGetError(&errorMsg);
if(code != GLFW_NO_ERROR) {
ErrorDialog::showError("Failed to create window: " + std::string(errorMsg) + " (" + std::to_string(code) + ")");
} else {
ErrorDialog::showError("Failed to create window: Unknown error");
}
glfwTerminate();
return KEK_ERROR;
}
glfwMakeContextCurrent(kekData.window);
std::cout << "Initialized window" << std::endl;
// Init GLEW
glewExperimental = GL_TRUE;
if(glewInit() != GLEW_OK) {
ErrorDialog::showError("Failed to initialize GLEW");
glfwTerminate();
return KEK_ERROR;
}
std::cout << "Initialized GLEW" << std::endl;
std::cout << "OpenGL version: " << glGetString(GL_VERSION) << std::endl;
int flags;
glGetIntegerv(GL_CONTEXT_FLAGS, &flags);
if(flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
glDebugMessageCallback(glDebugOutput, nullptr);
glDebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_TRUE);
}
glViewport(0, 0, kekData.screenWidth, kekData.screenHeight);
glfwSetFramebufferSizeCallback(kekData.window, framebufferSizeCallback);
glEnable(GL_DEPTH_TEST);
glEnable(GL_MULTISAMPLE);
glEnable(GL_CULL_FACE);
glCullFace(GL_BACK);
glClearColor(0.1f, 0.3f, 0.1f, 0.0f);
// glfwSwapInterval(0);
stbi_set_flip_vertically_on_load(true);
if(Resource::init() != KEK_SUCCESS) {
glfwTerminate();
return KEK_ERROR;
}
FT_Error error = FT_Init_FreeType(&kekData.freetype);
if(error) {
ErrorDialog::showError("Failed to initialize FreeType: " + std::string(FT_Error_String(error)));
glfwTerminate();
return KEK_ERROR;
}
UI::init();
Input::init();
Physics::init();
Defaults::init();
MemoryBuffer *buf = Resource::loadResource("image/icon.png");
if(buf) {
int width, height, nrChannels;
unsigned char *data = stbi_load_from_memory((stbi_uc *) buf->buffer, buf->length, &width, &height, &nrChannels, 0);
GLFWimage image;
image.width = width;
image.height = height;
image.pixels = data;
glfwSetWindowIcon(kekData.window, 1, &image);
delete buf;
}
kekData.activeCamera = new Camera();
kekData.shader = new Shader("shader/mesh/vertex.glsl", "shader/mesh/fragment.glsl");
kekData.shader->initLighting();
glfwSetCursorPosCallback(kekData.window, Input::onCursorPosCallback);
Input::setCursorMode(GLFWCursorMode::CAPTURE);
glfwSetKeyCallback(kekData.window, Input::onKeyCallback);
glfwSetCharCallback(kekData.window, Input::onKeyCharCallback);
glfwSetMouseButtonCallback(kekData.window, Input::onMouseButtonCallback);
fpsText = new TextElement(uiPx(0), uiPx(0));
UI::addElement(fpsText);
return KEK_SUCCESS;
}
int start() {
int prevTime = 0;
while(!glfwWindowShouldClose(kekData.window)) {
auto start = std::chrono::high_resolution_clock::now();
// Clear the screen
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glViewport(0, 0, kekData.screenWidth, kekData.screenHeight);
Physics::step(kekData.lastFrameTime);
for(auto cb : kekData.input->periodicCallbacks) {
cb.second(kekData.window);
}
if(kekData.activeScene) {
kekData.shader->use();
glm::mat4 view = kekData.activeCamera->transformationMatrix();
glm::mat4 projection;
projection = glm::perspective(glm::radians(90.0f), kekData.screenWidth / (float) kekData.screenHeight, KEK_CAMERA_NEAR, KEK_CAMERA_FAR);
glm::vec3 position = kekData.activeCamera->getPosition();
glUniformMatrix4fv(glGetUniformLocation(kekData.shader->id, "view"), 1, GL_FALSE, glm::value_ptr(view));
glUniformMatrix4fv(glGetUniformLocation(kekData.shader->id, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
glUniform3fv(glGetUniformLocation(kekData.shader->id, "cameraPos"), 1, glm::value_ptr(position));
if(kekData.shader->lighting) {
LightList *lights = kekData.activeScene->lights;
std::vector<Light *> shaderLights; // TODO: Maybe don't compute on every frame
for(PointLight *light : lights->point) {
if(glm::length2(glm::abs(light->getPosition() - kekData.activeCamera->getPosition())) < KEK_LIGHT_MAX_DISTANCE_SQUARED) {
shaderLights.push_back(light);
}
}
for(DirectionalLight *light : lights->directional) {
shaderLights.push_back(light);
}
for(SpotLight *light : lights->spot) {
if(glm::length2(glm::abs(light->getPosition() - kekData.activeCamera->getPosition())) < KEK_LIGHT_MAX_DISTANCE_SQUARED) {
shaderLights.push_back(light);
}
}
unsigned int i = 0;
int numPointLights = 0, numDirectionalLights = 0, numSpotLights = 0;
for(Light *light : shaderLights) {
ShaderLight shLight;
memcpy(shLight.color, glm::value_ptr(light->color), sizeof(shLight.color));
switch(light->getType()) {
case LightType::POINT: {
PointLight *l = (PointLight *) light;
memcpy(&shLight.position, glm::value_ptr(l->getPosition()), sizeof(shLight.position));
memcpy(&shLight.attenuation, glm::value_ptr(glm::vec3(l->constant, l->linear, l->quadratic)), sizeof(shLight.attenuation));
numPointLights++;
break;
}
case LightType::DIRECTIONAL: {
DirectionalLight *l = (DirectionalLight *) light;
memcpy(&shLight.direction, glm::value_ptr(l->direction), sizeof(shLight.direction));
numDirectionalLights++;
break;
}
case LightType::SPOT: {
SpotLight *l = (SpotLight *) light;
memcpy(&shLight.position, glm::value_ptr(l->getPosition()), sizeof(shLight.position));
memcpy(&shLight.direction, glm::value_ptr(l->direction), sizeof(shLight.direction));
memcpy(&shLight.attenuation, glm::value_ptr(glm::vec3(l->constant, l->linear, l->quadratic)), sizeof(shLight.attenuation));
memcpy(&shLight.cutoff, glm::value_ptr(glm::vec2(glm::cos(l->innerCutoff), glm::cos(l->outerCutoff))), sizeof(shLight.cutoff));
numSpotLights++;
break;
}
}
kekData.shader->lighting->buffer[i] = shLight;
i++;
}
glNamedBufferData(kekData.shader->lighting->ubo, kekData.shader->lighting->blockSize, &kekData.shader->lighting->buffer, GL_DYNAMIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, KEK_UNIFORM_LIGHTS_BINDING, kekData.shader->lighting->ubo);
glUniform1i(glGetUniformLocation(kekData.shader->id, "numPointLights"), numPointLights);
glUniform1i(glGetUniformLocation(kekData.shader->id, "numDirectionalLights"), numDirectionalLights);
glUniform1i(glGetUniformLocation(kekData.shader->id, "numSpotLights"), numSpotLights);
}
kekData.activeScene->draw(kekData.shader);
}
glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glm::mat4 uiProjection = glm::ortho(0.0f, (float) kekData.screenWidth, (float) kekData.screenHeight, 0.0f);
int time = (int) (glfwGetTime() * 10);
if(time != prevTime) fpsText->setText("FPS: " + std::to_string((int) floor(1.0f / kekData.lastFrameTime)) + " (" + std::to_string(kekData.lastFrameTime * 1000) + " ms)");
prevTime = time;
for(UIElement *uiEl : kekData.ui->elements) {
UIPoint pos = uiEl->getScreenPosition();
uiEl->drawAll(pos, uiProjection);
}
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
// Swap buffers and poll window events
glfwSwapBuffers(kekData.window);
glfwPollEvents();
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<float> secsTaken = end - start;
kekData.lastFrameTime = secsTaken.count();
}
return KEK_SUCCESS;
}
void exit() {
Defaults::destroy();
Physics::destroy();
Input::destroy();
UI::destroy();
Resource::destroy();
FT_Done_FreeType(kekData.freetype);
}
void setActiveScene(Scene *scene) {
kekData.activeScene = scene;
}
Scene *getActiveScene() {
return kekData.activeScene;
}
Player *getPlayer() {
return kekData.player;
}
Camera *getActiveCamera() {
return kekData.activeCamera;
}
}