/* * 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 . */ #include "application.hpp" #include "application-state.hpp" #include "states/splash-state.hpp" #include "states/title-state.hpp" #include "states/experiment-state.hpp" #include #include #include #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(); }