ICT290 / src / engine / engine.cpp
engine.cpp
Raw
#include "engine.h"
#include <iostream>
#include <memory>
#include <sstream>
#include "../scene/mainMenu/developerScreen.h"

Engine::Engine() {
    startSDL();
    startOpenGL();
    UI::startUI();
    startManagers();
    if (!loadSettings("../res/preload/settings.txt")) {
        std::cerr << "Failed to load settings, defaults will be used.\n";
    }
    SDL_ShowWindow(m_window);
}

Engine::~Engine() {
    UI::stopUI();
    stopOpenGL();
    stopSDL();
    if (!saveSettings("../res/preload/settings.txt")) {
        std::cerr << "Failed to save settings.\n";
    }
}

void Engine::startManagers() {
    if (!m_resources.textureManager.init("../res/preload/textures.txt")) {
        throw std::runtime_error("Texture manager failed to initialize.\n");
    }
    if (!m_resources.modelManager.init("../res/preload/as1Models.txt")) {
        throw std::runtime_error("Model manager failed to initialize.\n");
    }
    if (!m_resources.modelManager.init("../res/preload/as2Models.txt")) {
        throw std::runtime_error("Model manager failed to initialize.\n");
    }
}

void Engine::startOpenGL() {
    // OpenGL context
    m_GLContext = SDL_GL_CreateContext(m_window);

    if (m_GLContext == nullptr) {
        throw std::runtime_error(SDL_GetError());
    }

    // Load GL extensions using glad
    if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress)) {
        throw std::runtime_error("Failed to initialize the OpenGL context.\n");
    }

    // Vsync
    SDL_GL_SetSwapInterval(1);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    // Set preferences
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_TEXTURE_2D);

    // Set hints
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
    glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
    glHint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_NICEST);
    glHint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glClearColor(0.f, 0.f, 0.f, 1.0f);

    setViewport(m_resources.settings.width, m_resources.settings.height);
}

void Engine::setViewport(int width, int height) {
    if (height <= 0) {
        height = 1;
    }

    glViewport(0, 0, (GLsizei)width, (GLsizei)height);
}

void Engine::stopOpenGL() {
    SDL_GL_DeleteContext(m_GLContext);
}

void Engine::startSDL() {
    if (SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_EVENTS) != 0) {
        throw std::runtime_error(SDL_GetError());
    }

    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);

    // MSAA x16
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
    SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 16);

    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);

    m_window = SDL_CreateWindow(m_resources.settings.gameTitle.c_str(),
                                0,
                                0,
                                m_resources.settings.width,
                                m_resources.settings.height,
                                SDL_WINDOW_INPUT_GRABBED | SDL_WINDOW_OPENGL
                                    | SDL_WINDOW_HIDDEN
                                    | SDL_WINDOW_ALLOW_HIGHDPI);

    if (m_window == nullptr) {
        throw std::runtime_error(SDL_GetError());
    }
}

void Engine::stopSDL() {
    SDL_DestroyWindow(m_window);
    m_window = nullptr;

    SDL_Quit();
}

void Engine::run() {
    std::shared_ptr<State> currentState;

    m_stateManager.push(devScreen::init(m_resources));

    m_engineRunning = true;

    // Update delta to negate start up time
    deltaTime.update(m_resources);

    while (m_engineRunning) {
        // Update settings
        if (m_resources.settings.settingsUpdated) {
            updateSettings();
        }

        // retrieve the top state
        currentState = m_stateManager.top();

        if (currentState == nullptr) {
            m_engineRunning = false;
        } else {
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            currentState->handleEvents(m_event);

            // Update
            currentState->update(deltaTime.update(m_resources));

            // Draw if the states aren't swapping
            if (currentState->newState == nullptr) {
                currentState->draw();

                // SDL MAGIC
                SDL_GL_SwapWindow(m_window);
            }
        }
    }
}

float Engine::DeltaTime::update(Resources& resources) {
    current = (float)SDL_GetTicks() / 1000;
    delta = current - previous;
    previous = current;
    fpsDelta += delta;
    if (fpsDelta >= 1) {
        resources.settings.fps = fps;
        fps = 0;
        fpsDelta = 0;
    } else {
        ++fps;
    }

    return delta;
}

void Engine::updateSettings() {
    if (m_resources.settings.width <= 0) {
        m_resources.settings.width = 1920;
    }
    if (m_resources.settings.height <= 0) {
        m_resources.settings.height = 1080;
    }
    if (m_resources.settings.volume > 100) {
        m_resources.settings.volume = 100;
    }
    if (m_resources.settings.volume < 0) {
        m_resources.settings.volume = 0;
    }
    SDL_SetWindowFullscreen(m_window, (Uint32)m_resources.settings.fullscreen);
    setViewport(m_resources.settings.width, m_resources.settings.height);
    SDL_SetWindowSize(m_window,
                      m_resources.settings.width,
                      m_resources.settings.height);
    auto top = m_stateManager.top();
    top->camera.setPerspective(45.f,
                               (float)m_resources.settings.width
                                   / (float)m_resources.settings.height,
                               0.1f,
                               10000);
    top->camera.sensitivity = m_resources.settings.mouseSens;
    top->audioManager.setMasterVolume(m_resources.settings.volume);
    top->audioManager.setMute(m_resources.settings.mute);
    m_resources.settings.settingsUpdated = false;

    SDL_GL_SetSwapInterval(m_resources.settings.vsync);
}

bool Engine::loadSettings(const std::string& filename) {
    std::ifstream inFile(filename);

    if (!inFile) {
        std::cerr << "Unable to open file: " << filename << '\n';
        return false;
    }

    std::string line;
    while (!inFile.eof()) {
        std::getline(inFile, line);
        std::stringstream ss(line);
        std::string setting;
        ss >> setting;

        if (setting == "Resolution:") {
            ss >> m_resources.settings.width;
            ss >> m_resources.settings.height;
        } else if (setting == "Fullscreen:") {
            ss >> m_resources.settings.fullscreen;
        } else if (setting == "Vsync:") {
            ss >> m_resources.settings.vsync;
        } else if (setting == "Volume:") {
            ss >> m_resources.settings.volume;
        } else if (setting == "Mute:") {
            ss >> m_resources.settings.mute;
        }
    }
    m_resources.settings.settingsUpdated = true;

    return true;
}

bool Engine::saveSettings(const std::string& filename) {
    std::ofstream outFile(filename);

    if (!outFile) {
        std::cerr << "Unable to open file: " << filename << '\n';
        return false;
    }

    outFile << "Resolution: " << m_resources.settings.width << ' '
            << m_resources.settings.height << '\n';

    outFile << "Fullscreen: " << m_resources.settings.fullscreen << '\n';

    outFile << "Vsync: " << m_resources.settings.vsync << '\n';

    outFile << "Volume: " << m_resources.settings.volume << '\n';

    outFile << "Mute: " << m_resources.settings.mute << '\n';

    return true;
}