💿🐜 Antkeeper source code https://antkeeper.com
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

593 lines
16 KiB

/*
* 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();
}