/*
|
|
* Copyright (C) 2017 Christopher J. Howard
|
|
*
|
|
* This file is part of Antkeeper Source Code.
|
|
*
|
|
* Antkeeper Source Code is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Antkeeper Source Code is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Antkeeper Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "application.hpp"
|
|
#include "application-state.hpp"
|
|
#include "states/splash-state.hpp"
|
|
#include "states/title-state.hpp"
|
|
#include "states/experiment-state.hpp"
|
|
#include <cstdlib>
|
|
#include <iostream>
|
|
#include <SDL.h>
|
|
|
|
#define OPENGL_VERSION_MAJOR 3
|
|
#define OPENGL_VERSION_MINOR 3
|
|
|
|
#include "model-loader.hpp"
|
|
#include "material-loader.hpp"
|
|
|
|
Application::Application(int argc, char* argv[]):
|
|
state(nullptr),
|
|
nextState(nullptr),
|
|
terminationCode(EXIT_SUCCESS)
|
|
{
|
|
window = nullptr;
|
|
context = nullptr;
|
|
|
|
// Initialize SDL
|
|
std::cout << "Initializing SDL... ";
|
|
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER) < 0)
|
|
{
|
|
std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl;
|
|
close(EXIT_FAILURE);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "success" << std::endl;
|
|
}
|
|
|
|
// Print SDL version strings
|
|
SDL_version compiled;
|
|
SDL_version linked;
|
|
SDL_VERSION(&compiled);
|
|
SDL_GetVersion(&linked);
|
|
std::cout << "Compiled with SDL " << (int)compiled.major << "." << (int)compiled.minor << "." << (int)compiled.patch << std::endl;
|
|
std::cout << "Linking to SDL " << (int)linked.major << "." << (int)linked.minor << "." << (int)linked.patch << std::endl;
|
|
|
|
// Find app and user data paths
|
|
appDataPath = std::string(SDL_GetBasePath()) + "data/";
|
|
userDataPath = SDL_GetPrefPath("cjhoward", "antkeeper");
|
|
std::cout << "Application data path: \"" << appDataPath << "\"" << std::endl;
|
|
std::cout << "User data path: \"" << userDataPath << "\"" << std::endl;
|
|
|
|
// Form pathes to settings files
|
|
defaultSettingsFilename = appDataPath + "default-settings.txt";
|
|
userSettingsFilename = userDataPath + "settings.txt";
|
|
|
|
// Load default settings
|
|
std::cout << "Loading default settings from \"" << defaultSettingsFilename << "\"... ";
|
|
if (!settings.load(defaultSettingsFilename))
|
|
{
|
|
std::cout << "failed" << std::endl;
|
|
close(EXIT_FAILURE);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "success" << std::endl;
|
|
}
|
|
|
|
// Load user settings
|
|
std::cout << "Loading user settings from \"" << userSettingsFilename << "\"... ";
|
|
if (!settings.load(userSettingsFilename))
|
|
{
|
|
// Failed, save default settings as user settings
|
|
std::cout << "failed" << std::endl;
|
|
saveUserSettings();
|
|
}
|
|
else
|
|
{
|
|
std::cout << "success" << std::endl;
|
|
}
|
|
|
|
// Get values of required settings
|
|
settings.get("fullscreen", &fullscreen);
|
|
settings.get("fullscreen_width", &fullscreenWidth);
|
|
settings.get("fullscreen_height", &fullscreenHeight);
|
|
settings.get("windowed_width", &windowedWidth);
|
|
settings.get("windowed_height", &windowedHeight);
|
|
settings.get("swap_interval", &swapInterval);
|
|
|
|
// Load strings
|
|
std::string language;
|
|
settings.get("language", &language);
|
|
std::string stringsFile = appDataPath + "strings/" + language + ".txt";
|
|
std::cout << "Loading strings from \"" << stringsFile << "\"... ";
|
|
if (!strings.load(stringsFile))
|
|
{
|
|
std::cout << "failed" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "success" << std::endl;
|
|
}
|
|
|
|
// Select OpenGL version
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, OPENGL_VERSION_MAJOR);
|
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, OPENGL_VERSION_MINOR);
|
|
|
|
// Set OpenGL buffer attributes
|
|
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
|
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
|
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
|
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
|
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
|
|
|
// Check desktop display mode
|
|
SDL_DisplayMode displayMode;
|
|
if (SDL_GetDesktopDisplayMode(0, &displayMode) != 0)
|
|
{
|
|
std::cerr << "Failed to get desktop display mode: \"" << SDL_GetError() << "\"" << std::endl;
|
|
close(EXIT_FAILURE);
|
|
return;
|
|
}
|
|
|
|
// Check (usable?) display bounds
|
|
SDL_Rect displayBounds;
|
|
if (SDL_GetDisplayBounds(0, &displayBounds) != 0)
|
|
{
|
|
std::cerr << "Failed to get display bounds: \"" << SDL_GetError() << "\"" << std::endl;
|
|
close(EXIT_FAILURE);
|
|
return;
|
|
}
|
|
|
|
// Use display resolution if settings request
|
|
if (windowedWidth == -1 || windowedHeight == -1)
|
|
{
|
|
windowedWidth = displayBounds.w;
|
|
windowedHeight = displayBounds.h;
|
|
}
|
|
if (fullscreenWidth == -1 || fullscreenHeight == -1)
|
|
{
|
|
fullscreenWidth = displayMode.w;
|
|
fullscreenHeight = displayMode.h;
|
|
}
|
|
|
|
// Determine window parameters
|
|
Uint32 windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
|
|
if (fullscreen)
|
|
{
|
|
width = fullscreenWidth;
|
|
height = fullscreenHeight;
|
|
windowFlags |= SDL_WINDOW_FULLSCREEN;
|
|
}
|
|
else
|
|
{
|
|
width = windowedWidth;
|
|
height = windowedHeight;
|
|
}
|
|
|
|
// Get window title string
|
|
std::string title;
|
|
strings.get("title", &title);
|
|
|
|
// Create window
|
|
std::cout << "Creating a window... ";
|
|
window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, windowFlags);
|
|
if (window == nullptr)
|
|
{
|
|
std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl;
|
|
close(EXIT_FAILURE);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "success" << std::endl;
|
|
}
|
|
|
|
// Get actual window size
|
|
SDL_GetWindowSize(window, &width, &height);
|
|
if (fullscreen)
|
|
{
|
|
fullscreenWidth = width;
|
|
fullscreenHeight = height;
|
|
}
|
|
else
|
|
{
|
|
windowedWidth = width;
|
|
windowedHeight = height;
|
|
}
|
|
|
|
// Print video driver
|
|
const char* videoDriver = SDL_GetCurrentVideoDriver();
|
|
if (!videoDriver)
|
|
{
|
|
std::cout << "Unable to determine video driver" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Using video driver \"" << videoDriver << "\"" << std::endl;
|
|
}
|
|
|
|
// Create an OpenGL context
|
|
std::cout << "Creating an OpenGL context... ";
|
|
context = SDL_GL_CreateContext(window);
|
|
if (context == nullptr)
|
|
{
|
|
std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl;
|
|
close(EXIT_FAILURE);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "success" << std::endl;
|
|
}
|
|
|
|
// Initialize GL3W
|
|
std::cout << "Initializing GL3W... ";
|
|
if (gl3wInit())
|
|
{
|
|
std::cout << "failed" << std::endl;
|
|
close(EXIT_FAILURE);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "success" << std::endl;
|
|
}
|
|
|
|
// Check if OpenGL version is supported
|
|
if (!gl3wIsSupported(OPENGL_VERSION_MAJOR, OPENGL_VERSION_MINOR))
|
|
{
|
|
std::cout << "OpenGL " << OPENGL_VERSION_MAJOR << "." << OPENGL_VERSION_MINOR << " not supported" << std::endl;
|
|
close(EXIT_FAILURE);
|
|
return;
|
|
}
|
|
|
|
// Print OpenGL and GLSL version strings
|
|
std::cout << "Using OpenGL " << glGetString(GL_VERSION) << ", GLSL " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
|
|
|
|
// Set swap interval (vsync)
|
|
if (swapInterval)
|
|
{
|
|
std::cout << "Enabling vertical sync... ";
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Disabling vertical sync... ";
|
|
}
|
|
if (SDL_GL_SetSwapInterval(swapInterval) != 0)
|
|
{
|
|
std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl;
|
|
swapInterval = SDL_GL_GetSwapInterval();
|
|
}
|
|
else
|
|
{
|
|
std::cout << "success" << std::endl;
|
|
}
|
|
|
|
// Get display DPI
|
|
std::cout << "Getting DPI of display 0... ";
|
|
if (SDL_GetDisplayDPI(0, &dpi, nullptr, nullptr) != 0)
|
|
{
|
|
std::cerr << "failed: \"" << SDL_GetError() << "\"" << std::endl;
|
|
|
|
std::cout << "Reverting to default DPI" << std::endl;
|
|
settings.get("default_dpi", &dpi);
|
|
}
|
|
else
|
|
{
|
|
std::cout << "success" << std::endl;
|
|
}
|
|
|
|
// Print DPI
|
|
std::cout << "Rendering at " << dpi << " DPI" << std::endl;
|
|
|
|
// Determine base font size
|
|
settings.get("font_size", &fontSizePT);
|
|
fontSizePX = fontSizePT * (1.0f / 72.0f) * dpi;
|
|
|
|
// Print font size
|
|
std::cout << "Base font size is " << fontSizePT << "pt (" << fontSizePX << "px)" << std::endl;
|
|
|
|
// Setup input
|
|
inputManager = new SDLInputManager();
|
|
keyboard = (*inputManager->getKeyboards()).front();
|
|
mouse = (*inputManager->getMice()).front();
|
|
|
|
// Setup menu navigation controls
|
|
menuControlProfile = new ControlProfile(inputManager);
|
|
menuControlProfile->registerControl("menu_left", &menuLeft);
|
|
menuControlProfile->registerControl("menu_right", &menuRight);
|
|
menuControlProfile->registerControl("menu_up", &menuUp);
|
|
menuControlProfile->registerControl("menu_down", &menuDown);
|
|
menuControlProfile->registerControl("menu_select", &menuSelect);
|
|
menuControlProfile->registerControl("menu_cancel", &menuCancel);
|
|
menuControlProfile->registerControl("toggle_fullscreen", &toggleFullscreen);
|
|
menuControlProfile->registerControl("escape", &escape);
|
|
menuLeft.bindKey(keyboard, SDL_SCANCODE_LEFT);
|
|
menuLeft.bindKey(keyboard, SDL_SCANCODE_A);
|
|
menuRight.bindKey(keyboard, SDL_SCANCODE_RIGHT);
|
|
menuRight.bindKey(keyboard, SDL_SCANCODE_D);
|
|
menuUp.bindKey(keyboard, SDL_SCANCODE_UP);
|
|
menuUp.bindKey(keyboard, SDL_SCANCODE_W);
|
|
menuDown.bindKey(keyboard, SDL_SCANCODE_DOWN);
|
|
menuDown.bindKey(keyboard, SDL_SCANCODE_S);
|
|
menuSelect.bindKey(keyboard, SDL_SCANCODE_RETURN);
|
|
menuSelect.bindKey(keyboard, SDL_SCANCODE_SPACE);
|
|
menuSelect.bindKey(keyboard, SDL_SCANCODE_Z);
|
|
menuCancel.bindKey(keyboard, SDL_SCANCODE_BACKSPACE);
|
|
menuCancel.bindKey(keyboard, SDL_SCANCODE_X);
|
|
toggleFullscreen.bindKey(keyboard, SDL_SCANCODE_F11);
|
|
escape.bindKey(keyboard, SDL_SCANCODE_ESCAPE);
|
|
|
|
// Setup in-game controls
|
|
gameControlProfile = new ControlProfile(inputManager);
|
|
gameControlProfile->registerControl("camera-move-forward", &cameraMoveForward);
|
|
gameControlProfile->registerControl("camera-move-back", &cameraMoveBack);
|
|
gameControlProfile->registerControl("camera-move-left", &cameraMoveLeft);
|
|
gameControlProfile->registerControl("camera-move-right", &cameraMoveRight);
|
|
gameControlProfile->registerControl("camera-rotate-cw", &cameraRotateCW);
|
|
gameControlProfile->registerControl("camera-rotate-ccw", &cameraRotateCCW);
|
|
gameControlProfile->registerControl("camera-zoom-in", &cameraZoomIn);
|
|
gameControlProfile->registerControl("camera-zoom-out", &cameraZoomOut);
|
|
gameControlProfile->registerControl("camera-toggle-nest-view", &cameraToggleNestView);
|
|
gameControlProfile->registerControl("camera-toggle-overhead-view", &cameraToggleOverheadView);
|
|
cameraMoveForward.bindKey(keyboard, SDL_SCANCODE_W);
|
|
cameraMoveBack.bindKey(keyboard, SDL_SCANCODE_S);
|
|
cameraMoveLeft.bindKey(keyboard, SDL_SCANCODE_A);
|
|
cameraMoveRight.bindKey(keyboard, SDL_SCANCODE_D);
|
|
cameraRotateCW.bindKey(keyboard, SDL_SCANCODE_Q);
|
|
cameraRotateCCW.bindKey(keyboard, SDL_SCANCODE_E);
|
|
cameraZoomIn.bindKey(keyboard, SDL_SCANCODE_EQUALS);
|
|
cameraZoomOut.bindKey(keyboard, SDL_SCANCODE_MINUS);
|
|
cameraToggleOverheadView.bindKey(keyboard, SDL_SCANCODE_R);
|
|
cameraToggleNestView.bindKey(keyboard, SDL_SCANCODE_F);
|
|
cameraOverheadView = true;
|
|
cameraNestView = false;
|
|
|
|
// Allocate states
|
|
splashState = new SplashState(this);
|
|
titleState = new TitleState(this);
|
|
experimentState = new ExperimentState(this);
|
|
|
|
// Clear screen to black
|
|
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
|
glClear(GL_COLOR_BUFFER_BIT);
|
|
SDL_GL_SwapWindow(window);
|
|
|
|
// Setup loaders
|
|
materialLoader = new MaterialLoader();
|
|
modelLoader = new ModelLoader();
|
|
modelLoader->setMaterialLoader(materialLoader);
|
|
|
|
// Enter splash state
|
|
state = nextState = splashState;
|
|
state->enter();
|
|
}
|
|
|
|
Application::~Application()
|
|
{
|
|
SDL_GL_DeleteContext(context);
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
}
|
|
|
|
int Application::execute()
|
|
{
|
|
while (state != nullptr)
|
|
{
|
|
state->execute();
|
|
|
|
if (nextState != state)
|
|
{
|
|
state->exit();
|
|
|
|
state = nextState;
|
|
if (nextState != nullptr)
|
|
{
|
|
state->enter();
|
|
}
|
|
}
|
|
}
|
|
|
|
return terminationCode;
|
|
}
|
|
|
|
void Application::changeState(ApplicationState* state)
|
|
{
|
|
nextState = state;
|
|
}
|
|
|
|
void Application::setTerminationCode(int code)
|
|
{
|
|
terminationCode = code;
|
|
}
|
|
|
|
void Application::close(int terminationCode)
|
|
{
|
|
setTerminationCode(terminationCode);
|
|
changeState(nullptr);
|
|
}
|
|
|
|
void Application::changeFullscreen()
|
|
{
|
|
fullscreen = !fullscreen;
|
|
|
|
if (fullscreen)
|
|
{
|
|
width = fullscreenWidth;
|
|
height = fullscreenHeight;
|
|
|
|
SDL_SetWindowSize(window, width, height);
|
|
if (SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN) != 0)
|
|
{
|
|
std::cerr << "Failed to set fullscreen mode: \"" << SDL_GetError() << "\"" << std::endl;
|
|
fullscreen = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
width = windowedWidth;
|
|
height = windowedHeight;
|
|
|
|
if (SDL_SetWindowFullscreen(window, 0) != 0)
|
|
{
|
|
std::cerr << "Failed to set windowed mode: \"" << SDL_GetError() << "\"" << std::endl;
|
|
fullscreen = true;
|
|
}
|
|
else
|
|
{
|
|
SDL_SetWindowSize(window, width, height);
|
|
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
|
}
|
|
}
|
|
|
|
// Get actual window size
|
|
SDL_GetWindowSize(window, &width, &height);
|
|
|
|
// Print mode and resolution
|
|
if (fullscreen)
|
|
{
|
|
std::cout << "Changed to fullscreen mode at resolution " << width << "x" << height << std::endl;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Changed to windowed mode at resolution " << width << "x" << height << std::endl;
|
|
}
|
|
|
|
// Save settings
|
|
settings.set("fullscreen", fullscreen);
|
|
saveUserSettings();
|
|
|
|
// Notify window observers
|
|
inputManager->update();
|
|
}
|
|
|
|
void Application::changeVerticalSync()
|
|
{
|
|
swapInterval = (swapInterval == 1) ? 0 : 1;
|
|
|
|
if (swapInterval == 1)
|
|
{
|
|
std::cout << "Enabling vertical sync... ";
|
|
}
|
|
else
|
|
{
|
|
std::cout << "Disabling vertical sync... ";
|
|
}
|
|
|
|
if (SDL_GL_SetSwapInterval(swapInterval) != 0)
|
|
{
|
|
std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl;
|
|
swapInterval = SDL_GL_GetSwapInterval();
|
|
}
|
|
else
|
|
{
|
|
std::cout << "success" << std::endl;
|
|
}
|
|
|
|
// Save settings
|
|
settings.set("swap_interval", swapInterval);
|
|
saveUserSettings();
|
|
}
|
|
|
|
void Application::saveUserSettings()
|
|
{
|
|
std::cout << "Saving user setttings to \"" << userSettingsFilename << "\"... ";
|
|
if (!settings.save(userSettingsFilename))
|
|
{
|
|
std::cout << "failed" << std::endl;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "success" << std::endl;
|
|
}
|
|
}
|
|
|
|
void Application::resizeUI()
|
|
{
|
|
// Adjust UI dimensions
|
|
uiRootElement->setDimensions(Vector2(width, height));
|
|
uiRootElement->update();
|
|
|
|
// Adjust UI camera projection
|
|
uiCamera.setOrthographic(0, width, height, 0, -1.0f, 1.0f);
|
|
}
|
|
|
|
void Application::enterMenu(std::size_t index)
|
|
{
|
|
if (index != currentMenuIndex)
|
|
{
|
|
exitMenu(currentMenuIndex);
|
|
}
|
|
|
|
// Select next menu
|
|
currentMenuIndex = index;
|
|
selectedMenuItemIndex = 0;
|
|
currentMenu = menus[currentMenuIndex];
|
|
menus[currentMenuIndex]->getItem(selectedMenuItemIndex)->select();
|
|
|
|
// Start menu fade-in tween
|
|
menuFadeInTween->setUpdateCallback(std::bind(UIElement::setTintColor, menuContainers[currentMenuIndex], std::placeholders::_1));
|
|
menuFadeInTween->setEndCallback(std::bind(UIElement::setActive, menuContainers[currentMenuIndex], true));
|
|
menuFadeInTween->reset();
|
|
menuFadeInTween->start();
|
|
|
|
// Start menu slide-in tween
|
|
menuSlideInTween->setUpdateCallback(std::bind(UIElement::setTranslation, menuContainers[currentMenuIndex], std::placeholders::_1));
|
|
menuSlideInTween->reset();
|
|
menuSlideInTween->start();
|
|
|
|
// Make menu visible
|
|
menuContainers[currentMenuIndex]->setVisible(true);
|
|
}
|
|
|
|
void Application::exitMenu(std::size_t index)
|
|
{
|
|
// Deactivate previous menu
|
|
menuContainers[currentMenuIndex]->setActive(false);
|
|
|
|
// Fade out previous menu
|
|
menuFadeOutTween->setUpdateCallback(std::bind(UIElement::setTintColor, menuContainers[currentMenuIndex], std::placeholders::_1));
|
|
menuFadeOutTween->setEndCallback(std::bind(UIElement::setVisible, menuContainers[currentMenuIndex], false));
|
|
menuFadeOutTween->reset();
|
|
menuFadeOutTween->start();
|
|
}
|
|
|
|
void Application::selectMenuItem(std::size_t index)
|
|
{
|
|
if (currentMenu == nullptr || index > currentMenu->getItemCount())
|
|
{
|
|
std::cout << "Selected invalid menu item" << std::endl;
|
|
return;
|
|
}
|
|
|
|
MenuItem* previousItem = currentMenu->getItem(selectedMenuItemIndex);
|
|
previousItem->deselect();
|
|
|
|
selectedMenuItemIndex = index;
|
|
|
|
MenuItem* nextItem = currentMenu->getItem(selectedMenuItemIndex);
|
|
nextItem->select();
|
|
}
|
|
|
|
void Application::activateMenuItem(std::size_t index)
|
|
{
|
|
if (index > menus[currentMenuIndex]->getItemCount())
|
|
{
|
|
std::cout << "Activated invalid menu item" << std::endl;
|
|
return;
|
|
}
|
|
|
|
menus[currentMenuIndex]->getItem(index)->deselect();
|
|
menus[currentMenuIndex]->getItem(index)->activate();
|
|
}
|