💿🐜 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.
 

1956 lines
61 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 "model-loader.hpp"
#include "material-loader.hpp"
#include "states/loading-state.hpp"
#include "states/splash-state.hpp"
#include "states/title-state.hpp"
#include "states/game-state.hpp"
#include "game/colony.hpp"
#include "game/tool.hpp"
#include "ui/menu.hpp"
#include "ui/toolbar.hpp"
#include "ui/pie-menu.hpp"
#include "debug.hpp"
#include "camera-controller.hpp"
#include "configuration.hpp"
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <cstdio>
#include <sstream>
#include <SDL2/SDL.h>
#include <dirent.h>
#define OPENGL_VERSION_MAJOR 3
#define OPENGL_VERSION_MINOR 3
#undef max
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("swap_interval", &swapInterval);
// 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);
// Get all possible display modes for the default display
int displayModeCount = SDL_GetNumDisplayModes(0);
for (int i = displayModeCount - 1; i >= 0; --i)
{
SDL_DisplayMode displayMode;
if (SDL_GetDisplayMode(0, i, &displayMode) != 0)
{
std::cerr << "Failed to get display mode: \"" << SDL_GetError() << "\"" << std::endl;
close(EXIT_FAILURE);
return;
}
resolutions.push_back(Vector2(displayMode.w, displayMode.h));
}
// Read requested windowed and fullscreen resolutions from settings
Vector2 requestedWindowedResolution;
Vector2 requestedFullscreenResolution;
settings.get("windowed_width", &requestedWindowedResolution.x);
settings.get("windowed_height", &requestedWindowedResolution.y);
settings.get("fullscreen_width", &requestedFullscreenResolution.x);
settings.get("fullscreen_height", &requestedFullscreenResolution.y);
// Determine desktop resolution
SDL_DisplayMode desktopDisplayMode;
if (SDL_GetDesktopDisplayMode(0, &desktopDisplayMode) != 0)
{
std::cerr << "Failed to get desktop display mode: \"" << SDL_GetError() << "\"" << std::endl;
close(EXIT_FAILURE);
return;
}
Vector2 desktopResolution;
desktopResolution.x = static_cast<float>(desktopDisplayMode.w);
desktopResolution.y = static_cast<float>(desktopDisplayMode.h);
// Replace requested resolutions of -1 with native resolution
requestedWindowedResolution.x = (requestedWindowedResolution.x == -1.0f) ? desktopResolution.x : requestedWindowedResolution.x;
requestedWindowedResolution.y = (requestedWindowedResolution.y == -1.0f) ? desktopResolution.y : requestedWindowedResolution.y;
requestedFullscreenResolution.x = (requestedFullscreenResolution.x == -1.0f) ? desktopResolution.x : requestedFullscreenResolution.x;
requestedFullscreenResolution.y = (requestedFullscreenResolution.y == -1.0f) ? desktopResolution.y : requestedFullscreenResolution.y;
// Find indices of closest resolutions to requested windowed and fullscreen resolutions
windowedResolutionIndex = 0;
fullscreenResolutionIndex = 0;
float minWindowedResolutionDistance = std::numeric_limits<float>::max();
float minFullscreenResolutionDistance = std::numeric_limits<float>::max();
for (std::size_t i = 0; i < resolutions.size(); ++i)
{
Vector2 windowedResolutionDifference = resolutions[i] - requestedWindowedResolution;
float windowedResolutionDistance = glm::dot(windowedResolutionDifference, windowedResolutionDifference);
if (windowedResolutionDistance <= minWindowedResolutionDistance)
{
minWindowedResolutionDistance = windowedResolutionDistance;
windowedResolutionIndex = i;
}
Vector2 fullscreenResolutionDifference = resolutions[i] - requestedFullscreenResolution;
float fullscreenResolutionDistance = glm::dot(fullscreenResolutionDifference, fullscreenResolutionDifference);
if (fullscreenResolutionDistance <= minFullscreenResolutionDistance)
{
minFullscreenResolutionDistance = fullscreenResolutionDistance;
fullscreenResolutionIndex = i;
}
}
// Determine window parameters and current resolution
Uint32 windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI;
if (fullscreen)
{
resolution = resolutions[fullscreenResolutionIndex];
windowFlags |= SDL_WINDOW_FULLSCREEN;
}
else
{
resolution = resolutions[windowedResolutionIndex];
}
// Get window title string
std::string title;
strings.get("title", &title);
// Create window
std::cout << "Creating a " << resolution.x << "x" << resolution.y;
std::cout << ((fullscreen) ? " fullscreen" : " windowed");
std::cout << " window... ";
window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, static_cast<int>(resolution.x), static_cast<int>(resolution.y), windowFlags);
if (window == nullptr)
{
std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl;
close(EXIT_FAILURE);
return;
}
else
{
std::cout << "success" << std::endl;
}
// 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;
}
// Clear screen to black
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
SDL_GL_SwapWindow(window);
// 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;
// Get requested language
languageIndex = 0;
std::string requestedLanguage;
settings.get("language", &requestedLanguage);
// Find available languages
{
std::string stringsDirectory = appDataPath + "strings/";
// Open strings directory
DIR* dir = opendir(stringsDirectory.c_str());
if (dir == nullptr)
{
std::cout << "Failed to open strings directory \"" << stringsDirectory << "\"" << std::endl;
close(EXIT_FAILURE);
return;
}
// Scan directory for .txt files
for (struct dirent* entry = readdir(dir); entry != nullptr; entry = readdir(dir))
{
if (entry->d_type == DT_DIR || *entry->d_name == '.')
{
continue;
}
std::string filename = entry->d_name;
std::string::size_type delimeter = filename.find_last_of('.');
if (delimeter == std::string::npos)
{
continue;
}
std::string extension = filename.substr(delimeter + 1);
if (extension != "txt")
{
continue;
}
// Add language
std::string language = filename.substr(0, delimeter);
languages.push_back(language);
if (language == requestedLanguage)
{
languageIndex = languages.size() - 1;
}
}
// Close biomes directory
closedir(dir);
}
// Load strings
std::string stringsFile = appDataPath + "strings/" + languages[languageIndex] + ".txt";
std::cout << "Loading strings from \"" << stringsFile << "\"... ";
if (!strings.load(stringsFile))
{
std::cout << "failed" << std::endl;
}
else
{
std::cout << "success" << std::endl;
}
// Setup input
inputManager = new SDLInputManager();
keyboard = (*inputManager->getKeyboards()).front();
mouse = (*inputManager->getMice()).front();
// Allocate states
loadingState = new LoadingState(this);
splashState = new SplashState(this);
titleState = new TitleState(this);
gameState = new GameState(this);
// Setup loaders
textureLoader = new TextureLoader();
materialLoader = new MaterialLoader();
modelLoader = new ModelLoader();
modelLoader->setMaterialLoader(materialLoader);
// Allocate game variables
surfaceCam = new SurfaceCameraController();
// Enter loading state
state = nextState = loadingState;
state->enter();
displayDebugInfo = false;
}
Application::~Application()
{
SDL_GL_DeleteContext(context);
SDL_DestroyWindow(window);
SDL_Quit();
}
int Application::execute()
{
// Fixed timestep
// @see http://gafferongames.com/game-physics/fix-your-timestep/
t = 0.0f;
dt = 1.0f / 60.0f;
float accumulator = 0.0f;
float maxFrameTime = 0.25f;
int performanceSampleSize = 15; // Number of frames to sample
int performanceSampleFrame = 0; // Current sample frame
float performanceSampleTime = 0.0f; // Current sample time
// Start frame timer
frameTimer.start();
while (state != nullptr)
{
// Calculate frame time (in milliseconds) then reset frame timer
float frameTime = static_cast<float>(frameTimer.microseconds().count()) / 1000.0f;
frameTimer.reset();
// Add frame time (in seconds) to accumulator
accumulator += std::min<float>(frameTime / 1000.0f, maxFrameTime);
// If the user tried to close the application
if (inputManager->wasClosed())
{
// Close the application
close(EXIT_SUCCESS);
}
else
{
// Execute current state
while (accumulator >= dt)
{
state->execute();
// Update controls
menuControlProfile->update();
gameControlProfile->update();
// Perform tweening
tweener->update(dt);
accumulator -= dt;
t += dt;
}
}
// Check for state change
if (nextState != state)
{
// Exit current state
state->exit();
// Enter next state (if valid)
state = nextState;
if (nextState != nullptr)
{
state->enter();
tweener->update(0.0f);
// Reset frame timer to counteract frames eaten by state exit() and enter() functions
frameTimer.reset();
}
else
{
break;
}
}
// Update input
inputManager->update();
// Check if fullscreen was toggled
if (toggleFullscreen.isTriggered() && !toggleFullscreen.wasTriggered())
{
changeFullscreen();
}
// Check if debug display was toggled
if (toggleDebugDisplay.isTriggered() && !toggleDebugDisplay.wasTriggered())
{
setDisplayDebugInfo(!displayDebugInfo);
}
// Add frame time to performance sample time and increment the frame count
performanceSampleTime += frameTime;
++performanceSampleFrame;
// If performance sample is complete
if (performanceSampleFrame >= performanceSampleSize)
{
// Calculate mean frame time
float meanFrameTime = performanceSampleTime / static_cast<float>(performanceSampleSize);
// Reset perform sample timers
performanceSampleTime = 0.0f;
performanceSampleFrame = 0;
// Update frame time label
if (frameTimeLabel->isVisible())
{
std::string frameTimeString;
std::stringstream stream;
stream.precision(2);
stream << std::fixed << meanFrameTime;
stream >> frameTimeString;
frameTimeLabel->setText(frameTimeString);
}
}
// Update UI
uiRootElement->update();
uiBatcher->batch(uiBatch, uiRootElement);
// Render scene
renderer.render(scene);
// Swap buffers
SDL_GL_SwapWindow(window);
}
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)
{
resolution = resolutions[fullscreenResolutionIndex];
SDL_SetWindowSize(window, static_cast<int>(resolution.x), static_cast<int>(resolution.y));
if (SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN) != 0)
{
std::cerr << "Failed to set fullscreen mode: \"" << SDL_GetError() << "\"" << std::endl;
fullscreen = false;
}
}
else
{
resolution = resolutions[windowedResolutionIndex];
if (SDL_SetWindowFullscreen(window, 0) != 0)
{
std::cerr << "Failed to set windowed mode: \"" << SDL_GetError() << "\"" << std::endl;
fullscreen = true;
}
else
{
SDL_SetWindowSize(window, static_cast<int>(resolution.x), static_cast<int>(resolution.y));
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
}
// Print mode and resolution
if (fullscreen)
{
std::cout << "Changed to fullscreen mode at resolution " << resolution.x << "x" << resolution.y << std::endl;
}
else
{
std::cout << "Changed to windowed mode at resolution " << resolution.x << "x" << resolution.y << std::endl;
}
// Save settings
settings.set("fullscreen", fullscreen);
saveUserSettings();
// Resize UI
resizeUI();
// 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;
}
}
bool Application::loadModels()
{
antModel = modelLoader->load("data/models/debug-worker.mdl");
antHillModel = modelLoader->load("data/models/ant-hill.mdl");
nestModel = modelLoader->load("data/models/nest.mdl");
forcepsModel = modelLoader->load("data/models/forceps.mdl");
lensModel = modelLoader->load("data/models/lens.mdl");
brushModel = modelLoader->load("data/models/brush.mdl");
biomeFloorModel = modelLoader->load("data/models/desert-floor.mdl");
if (!antModel || !antHillModel || !nestModel || !forcepsModel || !lensModel || !brushModel)
{
return false;
}
antModelInstance.setModel(antModel);
antModelInstance.setTransform(Transform::getIdentity());
antHillModelInstance.setModel(antHillModel);
antHillModelInstance.setRotation(glm::angleAxis(glm::radians(90.0f), Vector3(1, 0, 0)));
nestModelInstance.setModel(nestModel);
biomeFloorModelInstance.setModel(biomeFloorModel);
return true;
}
bool Application::loadScene()
{
// Create scene layers
backgroundLayer = scene.addLayer();
defaultLayer = scene.addLayer();
uiLayer = scene.addLayer();
// BG
bgBatch.resize(1);
BillboardBatch::Range* bgRange = bgBatch.addRange();
bgRange->start = 0;
bgRange->length = 1;
Billboard* bgBillboard = bgBatch.getBillboard(0);
bgBillboard->setDimensions(Vector2(1.0f, 1.0f));
bgBillboard->setTranslation(Vector3(0.5f, 0.5f, 0.0f));
bgBillboard->setTintColor(Vector4(1, 1, 1, 1));
bgBatch.update();
vignettePass.setRenderTarget(&defaultRenderTarget);
//bgCompositor.addPass(&vignettePass);
bgCompositor.load(nullptr);
bgCamera.setOrthographic(0, 1.0f, 1.0f, 0, -1.0f, 1.0f);
bgCamera.lookAt(glm::vec3(0), glm::vec3(0, 0, -1), glm::vec3(0, 1, 0));
bgCamera.setCompositor(&bgCompositor);
bgCamera.setCompositeIndex(0);
// Set shadow map resolution
shadowMapResolution = 4096;
// Generate shadow map framebuffer
glGenFramebuffers(1, &shadowMapFramebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, shadowMapFramebuffer);
// Generate shadow map depth texture
glGenTextures(1, &shadowMapDepthTexture);
glBindTexture(GL_TEXTURE_2D, shadowMapDepthTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, shadowMapResolution, shadowMapResolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
// Attach depth texture to framebuffer
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, shadowMapDepthTexture, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
// Unbind shadow map depth texture
glBindTexture(GL_TEXTURE_2D, 0);
// Setup shadow map render target
shadowMapRenderTarget.width = shadowMapResolution;
shadowMapRenderTarget.height = shadowMapResolution;
shadowMapRenderTarget.framebuffer = shadowMapFramebuffer;
// Setup shadow map render pass
shadowMapPass.setRenderTarget(&shadowMapRenderTarget);
shadowMapPass.setViewCamera(&camera);
shadowMapPass.setLightCamera(&sunlightCamera);
// Setup shadow map compositor
shadowMapCompositor.addPass(&shadowMapPass);
shadowMapCompositor.load(nullptr);
// Setup skybox pass
skyboxPass.setRenderTarget(&defaultRenderTarget);
// Setup clear depth pass
clearDepthPass.setRenderTarget(&defaultRenderTarget);
clearDepthPass.setClear(false, true, false);
clearDepthPass.setClearDepth(1.0f);
// Setup soil pass
soilPass.setRenderTarget(&defaultRenderTarget);
// Setup lighting pass
lightingPass.setRenderTarget(&defaultRenderTarget);
lightingPass.setShadowMap(shadowMapDepthTexture);
lightingPass.setShadowCamera(&sunlightCamera);
lightingPass.setShadowMapPass(&shadowMapPass);
// Setup debug pass
debugPass.setRenderTarget(&defaultRenderTarget);
defaultCompositor.addPass(&clearDepthPass);
defaultCompositor.addPass(&skyboxPass);
defaultCompositor.addPass(&soilPass);
defaultCompositor.addPass(&lightingPass);
//defaultCompositor.addPass(&debugPass);
defaultCompositor.load(nullptr);
// Setup sunlight camera
sunlightCamera.lookAt(Vector3(0.5f, 2.0f, 2.0f), Vector3(0, 0, 0), Vector3(0, 1, 0));
sunlightCamera.setOrthographic(-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f);
sunlightCamera.setCompositor(&shadowMapCompositor);
sunlightCamera.setCompositeIndex(0);
sunlightCamera.setCullingMask(nullptr);
defaultLayer->addObject(&sunlightCamera);
// Setup camera
camera.lookAt(Vector3(0.0f, 0.0f, 10.0f), Vector3(0.0f, 0.0f, 0.0f), Vector3(0.0f, 1.0f, 0.0f));
camera.setCompositor(&defaultCompositor);
camera.setCompositeIndex(1);
defaultLayer->addObject(&camera);
// Debug
lineBatcher = new LineBatcher(4096);
BillboardBatch* lineBatch = lineBatcher->getBatch();
lineBatch->setAlignment(&camera, BillboardAlignmentMode::CYLINDRICAL);
lineBatch->setAlignmentVector(Vector3(1, 0, 0));
defaultLayer->addObject(lineBatch);
return true;
}
bool Application::loadUI()
{
// Load fonts
FontLoader* fontLoader = new FontLoader();
menuFont = new Font(512, 512);
if (!fontLoader->load("data/fonts/Varela-Regular.ttf", static_cast<int>(fontSizePX + 0.5f), menuFont))
{
std::cerr << "Failed to load menu font" << std::endl;
}
copyrightFont = new Font(256, 256);
if (!fontLoader->load("data/fonts/Varela-Regular.ttf", static_cast<int>(fontSizePX * 0.8f + 0.5f), copyrightFont))
{
std::cerr << "Failed to load copyright font" << std::endl;
}
levelNameFont = new Font(512, 512);
if (!fontLoader->load("data/fonts/Vollkorn-Regular.ttf", static_cast<int>(fontSizePX * 2.0f + 0.5f), levelNameFont))
{
std::cerr << "Failed to load level name font" << std::endl;
}
delete fontLoader;
// Load UI textures
textureLoader->setGamma(1.0f);
textureLoader->setCubemap(false);
textureLoader->setMipmapChain(false);
textureLoader->setMaxAnisotropy(1.0f);
textureLoader->setWrapS(false);
textureLoader->setWrapT(false);
splashTexture = textureLoader->load("data/textures/ui-splash.png");
titleTexture = textureLoader->load("data/textures/ui-title.png");
rectangularPaletteTexture = textureLoader->load("data/textures/rectangular-palette.png");
foodIndicatorTexture = textureLoader->load("data/textures/food-indicator.png");
toolBrushTexture = textureLoader->load("data/textures/tool-brush.png");
toolLensTexture = textureLoader->load("data/textures/tool-lens.png");
toolForcepsTexture = textureLoader->load("data/textures/tool-forceps.png");
toolTrowelTexture = textureLoader->load("data/textures/tool-trowel.png");
toolbarTopTexture = textureLoader->load("data/textures/toolbar-top.png");
toolbarBottomTexture = textureLoader->load("data/textures/toolbar-bottom.png");
toolbarMiddleTexture = textureLoader->load("data/textures/toolbar-middle.png");
toolbarButtonRaisedTexture = textureLoader->load("data/textures/toolbar-button-raised.png");
toolbarButtonDepressedTexture = textureLoader->load("data/textures/toolbar-button-depressed.png");
arcNorthTexture = textureLoader->load("data/textures/pie-menu-arc-north.png");
arcEastTexture = textureLoader->load("data/textures/pie-menu-arc-east.png");
arcSouthTexture = textureLoader->load("data/textures/pie-menu-arc-south.png");
arcWestTexture = textureLoader->load("data/textures/pie-menu-arc-west.png");
mouseLeftTexture = textureLoader->load("data/textures/mouse-left.png");
mouseRightTexture = textureLoader->load("data/textures/mouse-right.png");
depthTexture = new Texture();
depthTexture->setTextureID(shadowMapDepthTexture);
depthTexture->setWidth(shadowMapResolution);
depthTexture->setHeight(shadowMapResolution);
// Set colors
selectedColor = Vector4(1.0f, 1.0f, 1.0f, 1.0f);
deselectedColor = Vector4(1.0f, 1.0f, 1.0f, 0.35f);
// Create tweener
tweener = new Tweener();
// Setup root UI element
uiRootElement = new UIContainer();
uiRootElement->setDimensions(resolution);
mouse->addMouseMotionObserver(uiRootElement);
mouse->addMouseButtonObserver(uiRootElement);
// Create blackout element (for screen transitions)
blackoutImage = new UIImage();
blackoutImage->setDimensions(resolution);
blackoutImage->setLayerOffset(ANTKEEPER_UI_LAYER_BLACKOUT);
blackoutImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 1.0f));
blackoutImage->setVisible(false);
uiRootElement->addChild(blackoutImage);
// Create darken element (for darkening title screen)
darkenImage = new UIImage();
darkenImage->setDimensions(resolution);
darkenImage->setLayerOffset(ANTKEEPER_UI_LAYER_DARKEN);
darkenImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 0.35f));
darkenImage->setVisible(false);
uiRootElement->addChild(darkenImage);
// Create splash screen background element
splashBackgroundImage = new UIImage();
splashBackgroundImage->setDimensions(resolution);
splashBackgroundImage->setLayerOffset(-1);
splashBackgroundImage->setTintColor(Vector4(0.0f, 0.0f, 0.0f, 1.0f));
splashBackgroundImage->setVisible(false);
uiRootElement->addChild(splashBackgroundImage);
// Create splash screen element
splashImage = new UIImage();
splashImage->setAnchor(Anchor::CENTER);
splashImage->setDimensions(Vector2(splashTexture->getWidth(), splashTexture->getHeight()));
splashImage->setTexture(splashTexture);
splashImage->setVisible(false);
uiRootElement->addChild(splashImage);
// Create game title element
titleImage = new UIImage();
titleImage->setAnchor(Vector2(0.5f, 0.0f));
titleImage->setDimensions(Vector2(titleTexture->getWidth(), titleTexture->getHeight()));
titleImage->setTranslation(Vector2(0.0f, (int)(resolution.y * (1.0f / 4.0f) - titleTexture->getHeight() * 0.5f)));
titleImage->setTexture(titleTexture);
titleImage->setVisible(false);
titleImage->setLayerOffset(ANTKEEPER_UI_LAYER_MENU);
uiRootElement->addChild(titleImage);
frameTimeLabel = new UILabel();
frameTimeLabel->setAnchor(Vector2(0.0f, 0.0f));
frameTimeLabel->setLayerOffset(99);
frameTimeLabel->setFont(copyrightFont);
frameTimeLabel->setTranslation(Vector2(0.0f));
frameTimeLabel->setTintColor(Vector4(1.0f, 1.0f, 0.0f, 1.0f));
frameTimeLabel->setText("");
frameTimeLabel->setVisible(false);
uiRootElement->addChild(frameTimeLabel);
//bool frameTimeLabelVisible = false;
//settings.get("show_frame_time", &frameTimeLabelVisible);
//frameTimeLabel->setVisible(frameTimeLabelVisible);
// Create "Press any key" element
anyKeyLabel = new UILabel();
anyKeyLabel->setAnchor(Vector2(0.5f, 1.0f));
anyKeyLabel->setFont(menuFont);
anyKeyLabel->setTranslation(Vector2(0.0f, (int)(-resolution.y * (1.0f / 4.0f) - menuFont->getMetrics().getHeight() * 0.5f)));
anyKeyLabel->setVisible(false);
uiRootElement->addChild(anyKeyLabel);
rectangularPaletteImage = new UIImage();
rectangularPaletteImage->setAnchor(Vector2(0.0f, 1.0f));
rectangularPaletteImage->setDimensions(Vector2(rectangularPaletteTexture->getWidth(), rectangularPaletteTexture->getHeight()));
rectangularPaletteImage->setTranslation(Vector2(16.0f, -16.0f));
rectangularPaletteImage->setTexture(rectangularPaletteTexture);
rectangularPaletteImage->setVisible(false);
rectangularPaletteImage->setActive(false);
rectangularPaletteImage->setLayerOffset(ANTKEEPER_UI_LAYER_HUD);
uiRootElement->addChild(rectangularPaletteImage);
contextButtonImage0 = new UIImage();
contextButtonImage0->setAnchor(Vector2(0.5f, 1.0f));
contextButtonImage0->setDimensions(Vector2(mouseLeftTexture->getWidth(), mouseLeftTexture->getHeight()));
contextButtonImage0->setTranslation(Vector2(0.0f, -16.0f));
contextButtonImage0->setTexture(mouseLeftTexture);
//uiRootElement->addChild(contextButtonImage0);
foodIndicatorImage = new UIImage();
foodIndicatorImage->setAnchor(Vector2(1.0f, 0.0f));
foodIndicatorImage->setDimensions(Vector2(foodIndicatorTexture->getWidth(), foodIndicatorTexture->getHeight()));
foodIndicatorImage->setTranslation(Vector2(-16.0f, 16.0f));
foodIndicatorImage->setTexture(foodIndicatorTexture);
//uiRootElement->addChild(foodIndicatorImage);
depthTextureImage = new UIImage();
depthTextureImage->setAnchor(Vector2(0.0f, 1.0f));
depthTextureImage->setDimensions(Vector2(256, 256));
depthTextureImage->setTranslation(Vector2(0.0f, 0.0f));
depthTextureImage->setTexture(depthTexture);
depthTextureImage->setVisible(false);
uiRootElement->addChild(depthTextureImage);
// Create level name label
levelNameLabel = new UILabel();
levelNameLabel->setAnchor(Vector2(0.5f, 0.5f));
levelNameLabel->setFont(levelNameFont);
levelNameLabel->setVisible(false);
levelNameLabel->setLayerOffset(ANTKEEPER_UI_LAYER_HUD);
uiRootElement->addChild(levelNameLabel);
// Create toolbar
toolbar = new Toolbar();
toolbar->setToolbarTopTexture(toolbarTopTexture);
toolbar->setToolbarBottomTexture(toolbarBottomTexture);
toolbar->setToolbarMiddleTexture(toolbarMiddleTexture);
toolbar->setButtonRaisedTexture(toolbarButtonRaisedTexture);
toolbar->setButtonDepressedTexture(toolbarButtonDepressedTexture);
toolbar->addButton(toolBrushTexture, std::bind(&std::printf, "0\n"), std::bind(&std::printf, "0\n"));
toolbar->addButton(toolLensTexture, std::bind(&std::printf, "1\n"), std::bind(&std::printf, "1\n"));
toolbar->addButton(toolForcepsTexture, std::bind(&std::printf, "2\n"), std::bind(&std::printf, "2\n"));
toolbar->addButton(toolTrowelTexture, std::bind(&std::printf, "3\n"), std::bind(&std::printf, "3\n"));
toolbar->resize();
//uiRootElement->addChild(toolbar->getContainer());
toolbar->getContainer()->setVisible(false);
toolbar->getContainer()->setActive(false);
// Create pie menu
pieMenu = new PieMenu(tweener);
pieMenu->addOption(arcNorthTexture, toolLensTexture, std::bind(&Application::selectTool, this, lens), std::bind(&Application::deselectTool, this, lens));
pieMenu->addOption(arcEastTexture, toolForcepsTexture, std::bind(&Application::selectTool, this, forceps), std::bind(&Application::deselectTool, this, forceps));
pieMenu->addOption(arcSouthTexture, toolTrowelTexture, std::bind(&Application::selectTool, this, nullptr), std::bind(&Application::deselectTool, this, nullptr));
pieMenu->addOption(arcWestTexture, toolBrushTexture, std::bind(&Application::selectTool, this, brush), std::bind(&Application::deselectTool, this, brush));
uiRootElement->addChild(pieMenu->getContainer());
pieMenu->resize();
pieMenu->getContainer()->setVisible(false);
pieMenu->getContainer()->setActive(true);
// Setup screen fade in/fade out tween
fadeInTween = new Tween<Vector4>(EaseFunction::IN_CUBIC, 0.0f, 2.0f, Vector4(0.0f, 0.0f, 0.0f, 1.0f), Vector4(0.0f, 0.0f, 0.0f, -1.0f));
fadeInTween->setUpdateCallback(std::bind(&UIElement::setTintColor, blackoutImage, std::placeholders::_1));
tweener->addTween(fadeInTween);
fadeOutTween = new Tween<Vector4>(EaseFunction::OUT_CUBIC, 0.0f, 2.0f, Vector4(0.0f, 0.0f, 0.0f, 0.0f), Vector4(0.0f, 0.0f, 0.0f, 1.0f));
fadeOutTween->setUpdateCallback(std::bind(&UIElement::setTintColor, blackoutImage, std::placeholders::_1));
tweener->addTween(fadeOutTween);
// Setup splash screen tween
splashFadeInTween = new Tween<Vector4>(EaseFunction::IN_CUBIC, 0.0f, 0.5f, Vector4(1.0f, 1.0f, 1.0f, 0.0f), Vector4(0.0f, 0.0f, 0.0f, 1.0f));
splashFadeInTween->setUpdateCallback(std::bind(&UIElement::setTintColor, splashImage, std::placeholders::_1));
tweener->addTween(splashFadeInTween);
splashHangTween = new Tween<float>(EaseFunction::OUT_CUBIC, 0.0f, 1.0f, 0.0f, 1.0f);
tweener->addTween(splashHangTween);
splashFadeOutTween = new Tween<Vector4>(EaseFunction::OUT_CUBIC, 0.0f, 0.5f, Vector4(1.0f, 1.0f, 1.0f, 1.0f), Vector4(0.0f, 0.0f, 0.0f, -1.0f));
splashFadeOutTween->setUpdateCallback(std::bind(&UIElement::setTintColor, splashImage, std::placeholders::_1));
tweener->addTween(splashFadeOutTween);
splashFadeInTween->setEndCallback(std::bind(&TweenBase::start, splashHangTween));
splashHangTween->setEndCallback(std::bind(&TweenBase::start, splashFadeOutTween));
splashFadeOutTween->setEndCallback(std::bind(&Application::changeState, this, titleState));
// Setup game title tween
titleFadeInTween = new Tween<Vector4>(EaseFunction::IN_CUBIC, 0.0f, 2.0f, Vector4(1.0f, 1.0f, 1.0f, 0.0f), Vector4(0.0f, 0.0f, 0.0f, 1.0f));
titleFadeInTween->setUpdateCallback(std::bind(&UIElement::setTintColor, titleImage, std::placeholders::_1));
tweener->addTween(titleFadeInTween);
titleFadeOutTween = new Tween<Vector4>(EaseFunction::OUT_CUBIC, 0.0f, 0.25f, Vector4(1.0f, 1.0f, 1.0f, 1.0f), Vector4(0.0f, 0.0f, 0.0f, -1.0f));
titleFadeOutTween->setUpdateCallback(std::bind(&UIElement::setTintColor, titleImage, std::placeholders::_1));
tweener->addTween(titleFadeOutTween);
// Setup "Press any key" tween
anyKeyFadeInTween = new Tween<Vector4>(EaseFunction::LINEAR, 0.0f, 1.5f, Vector4(1.0f, 1.0f, 1.0f, 0.0f), Vector4(1.0f, 1.0f, 1.0f, 1.0f));
anyKeyFadeInTween->setUpdateCallback(std::bind(&UIElement::setTintColor, anyKeyLabel, std::placeholders::_1));
tweener->addTween(anyKeyFadeInTween);
anyKeyFadeOutTween = new Tween<Vector4>(EaseFunction::LINEAR, 0.0f, 1.5f, Vector4(1.0f, 1.0f, 1.0f, 1.0f), Vector4(1.0f, 1.0f, 1.0f, -1.0f));
anyKeyFadeOutTween->setUpdateCallback(std::bind(&UIElement::setTintColor, anyKeyLabel, std::placeholders::_1));
anyKeyFadeInTween->setEndCallback(std::bind(&TweenBase::start, anyKeyFadeOutTween));
anyKeyFadeOutTween->setEndCallback(std::bind(&TweenBase::start, anyKeyFadeInTween));
tweener->addTween(anyKeyFadeOutTween);
float menuFadeInDuration = 0.15f;
Vector4 menuFadeInStartColor = Vector4(1.0f, 1.0f, 1.0f, 0.0f);
Vector4 menuFadeInDeltaColor = Vector4(0.0f, 0.0f, 0.0f, 1.0f);
float menuFadeOutDuration = 0.15f;
Vector4 menuFadeOutStartColor = Vector4(1.0f, 1.0f, 1.0f, 1.0f);
Vector4 menuFadeOutDeltaColor = Vector4(0.0f, 0.0f, 0.0f, -1.0f);
float menuSlideInDuration = 0.35f;
Vector2 menuSlideInStartTranslation = Vector2(-64.0f, 0.0f);
Vector2 menuSlideInDeltaTranslation = Vector2((int)(64.0f + resolution.x / 8.0f), 0.0f);
// Setup main menu tween
menuFadeInTween = new Tween<Vector4>(EaseFunction::OUT_QUINT, 0.0f, menuFadeInDuration, menuFadeInStartColor, menuFadeInDeltaColor);
tweener->addTween(menuFadeInTween);
menuFadeOutTween = new Tween<Vector4>(EaseFunction::OUT_QUINT, 0.0f, menuFadeOutDuration, menuFadeOutStartColor, menuFadeOutDeltaColor);
tweener->addTween(menuFadeOutTween);
menuSlideInTween = new Tween<Vector2>(EaseFunction::OUT_QUINT, 0.0f, menuSlideInDuration, menuSlideInStartTranslation, menuSlideInDeltaTranslation);
tweener->addTween(menuSlideInTween);
// Camera translation tween
cameraTranslationTween = new Tween<Vector3>(EaseFunction::OUT_CUBIC, 0.0f, 0.0f, Vector3(0.0f), Vector3(0.0f));
tweener->addTween(cameraTranslationTween);
// Tool tweens
forcepsSwoopTween = new Tween<float>(EaseFunction::OUT_CUBIC, 0.0f, 1.0f, 0.0f, 0.5f);
tweener->addTween(forcepsSwoopTween);
// Build menu system
activeMenu = nullptr;
previousActiveMenu = nullptr;
// Allocate menus
mainMenu = new Menu();
levelsMenu = new Menu();
optionsMenu = new Menu();
pauseMenu = new Menu();
// Main menu
{
mainMenu->setFont(menuFont);
mainMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.8f));
mainMenu->getUIContainer()->setLayerOffset(ANTKEEPER_UI_LAYER_MENU);
mainMenu->setLineSpacing(1.0f);
mainMenuContinueItem = mainMenu->addItem();
mainMenuContinueItem->setActivatedCallback(std::bind(&Application::continueGame, this));
mainMenuLevelsItem = mainMenu->addItem();
mainMenuLevelsItem->setActivatedCallback(std::bind(&Application::openMenu, this, levelsMenu));
mainMenuNewGameItem = mainMenu->addItem();
mainMenuNewGameItem->setActivatedCallback(std::bind(&Application::newGame, this));
mainMenuSandboxItem = mainMenu->addItem();
mainMenuSandboxItem->setActivatedCallback(std::bind(&std::printf, "1\n"));
mainMenuOptionsItem = mainMenu->addItem();
mainMenuOptionsItem->setActivatedCallback(std::bind(&Application::openMenu, this, optionsMenu));
mainMenuExitItem = mainMenu->addItem();
mainMenuExitItem->setActivatedCallback(std::bind(&Application::close, this, EXIT_SUCCESS));
mainMenu->getUIContainer()->setActive(false);
mainMenu->getUIContainer()->setVisible(false);
uiRootElement->addChild(mainMenu->getUIContainer());
}
// Levels menu
{
levelsMenu->setFont(menuFont);
levelsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.8f));
levelsMenu->getUIContainer()->setLayerOffset(ANTKEEPER_UI_LAYER_MENU);
levelsMenu->setLineSpacing(1.0f);
for (std::size_t world = 0; world < campaign.getWorldCount(); ++world)
{
for (std::size_t level = 0; level < campaign.getLevelCount(world); ++level)
{
MenuItem* levelItem = levelsMenu->addItem();
levelItem->setActivatedCallback
(
[this, world, level]()
{
loadWorld(world);
loadLevel(level);
// Close levels menu
closeMenu();
// Begin title fade-out
titleFadeOutTween->reset();
titleFadeOutTween->start();
// Begin fade-out
fadeOutTween->setEndCallback(std::bind(&Application::changeState, this, gameState));
fadeOutTween->reset();
fadeOutTween->start();
}
);
}
}
levelsMenuBackItem = levelsMenu->addItem();
levelsMenuBackItem->setActivatedCallback
(
[this]()
{
openMenu(previousActiveMenu);
}
);
levelsMenu->getUIContainer()->setActive(false);
levelsMenu->getUIContainer()->setVisible(false);
uiRootElement->addChild(levelsMenu->getUIContainer());
}
// Options menu
{
optionsMenu->setFont(menuFont);
optionsMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.8f));
optionsMenu->getUIContainer()->setLayerOffset(ANTKEEPER_UI_LAYER_MENU);
optionsMenu->setLineSpacing(1.0f);
optionsMenu->setColumnMargin(menuFont->getWidth("MM"));
optionsMenuWindowedResolutionItem = optionsMenu->addItem();
optionsMenuFullscreenResolutionItem = optionsMenu->addItem();
for (const Vector2& resolution: resolutions)
{
optionsMenuWindowedResolutionItem->addValue();
optionsMenuFullscreenResolutionItem->addValue();
}
optionsMenuWindowedResolutionItem->setValueIndex(windowedResolutionIndex);
optionsMenuWindowedResolutionItem->setActivatedCallback(std::bind(&Application::incrementMenuItem, this));
optionsMenuWindowedResolutionItem->setValueChangedCallback(std::bind(&Application::selectWindowedResolution, this, std::placeholders::_1));
optionsMenuFullscreenResolutionItem->setValueIndex(fullscreenResolutionIndex);
optionsMenuFullscreenResolutionItem->setActivatedCallback(std::bind(&Application::incrementMenuItem, this));
optionsMenuFullscreenResolutionItem->setValueChangedCallback(std::bind(&Application::selectFullscreenResolution, this, std::placeholders::_1));
optionsMenuFullscreenItem = optionsMenu->addItem();
optionsMenuFullscreenItem->addValue();
optionsMenuFullscreenItem->addValue();
optionsMenuFullscreenItem->setValueIndex((fullscreen == 0) ? 0 : 1);
optionsMenuFullscreenItem->setActivatedCallback(std::bind(&Application::incrementMenuItem, this));
optionsMenuFullscreenItem->setValueChangedCallback(std::bind(&Application::selectFullscreenMode, this, std::placeholders::_1));
optionsMenuVSyncItem = optionsMenu->addItem();
optionsMenuVSyncItem->addValue();
optionsMenuVSyncItem->addValue();
optionsMenuVSyncItem->setValueIndex((swapInterval == 0) ? 0 : 1);
optionsMenuVSyncItem->setActivatedCallback(std::bind(&Application::incrementMenuItem, this));
optionsMenuVSyncItem->setValueChangedCallback(std::bind(&Application::selectVSyncMode, this, std::placeholders::_1));
optionsMenuLanguageItem = optionsMenu->addItem();
for (std::size_t i = 0; i < languages.size(); ++i)
{
optionsMenuLanguageItem->addValue();
}
optionsMenuLanguageItem->setValueIndex(languageIndex);
optionsMenuLanguageItem->setActivatedCallback(std::bind(&Application::incrementMenuItem, this));
optionsMenuLanguageItem->setValueChangedCallback(std::bind(&Application::selectLanguage, this, std::placeholders::_1));
optionsMenuControlsItem = optionsMenu->addItem();
optionsMenuBackItem = optionsMenu->addItem();
optionsMenuBackItem->setActivatedCallback
(
[this]()
{
openMenu(previousActiveMenu);
}
);
optionsMenu->getUIContainer()->setActive(false);
optionsMenu->getUIContainer()->setVisible(false);
uiRootElement->addChild(optionsMenu->getUIContainer());
}
// Pause menu
{
pauseMenu->setFont(menuFont);
pauseMenu->getUIContainer()->setAnchor(Vector2(0.5f, 0.5f));
pauseMenu->getUIContainer()->setLayerOffset(ANTKEEPER_UI_LAYER_MENU);
pauseMenu->setLineSpacing(1.0f);
pauseMenuResumeItem = pauseMenu->addItem();
pauseMenuResumeItem->setActivatedCallback(std::bind(&Application::unpauseSimulation, this));
pauseMenuLevelsItem = pauseMenu->addItem();
pauseMenuLevelsItem->setActivatedCallback(std::bind(&Application::openMenu, this, levelsMenu));
pauseMenuOptionsItem = pauseMenu->addItem();
pauseMenuOptionsItem->setActivatedCallback(std::bind(&Application::openMenu, this, optionsMenu));
pauseMenuMainMenuItem = pauseMenu->addItem();
pauseMenuMainMenuItem->setActivatedCallback
(
[this]()
{
// Close pause menu
closeMenu();
// Begin fade-out to title state
fadeOutTween->setEndCallback(std::bind(&Application::changeState, this, titleState));
fadeOutTween->reset();
fadeOutTween->start();
}
);
pauseMenuExitItem = pauseMenu->addItem();
pauseMenuExitItem->setActivatedCallback(std::bind(&Application::close, this, EXIT_SUCCESS));
pauseMenu->getUIContainer()->setActive(false);
pauseMenu->getUIContainer()->setVisible(false);
uiRootElement->addChild(pauseMenu->getUIContainer());
}
// Set UI strings
restringUI();
// Setup UI batch
uiBatch = new BillboardBatch();
uiBatch->resize(512);
uiBatcher = new UIBatcher();
// Setup UI render pass and compositor
uiPass.setRenderTarget(&defaultRenderTarget);
uiCompositor.addPass(&uiPass);
uiCompositor.load(nullptr);
// Setup UI camera
uiCamera.lookAt(glm::vec3(0), glm::vec3(0, 0, -1), glm::vec3(0, 1, 0));
uiCamera.setCompositor(&uiCompositor);
uiCamera.setCompositeIndex(0);
// Setup UI scene
uiLayer->addObject(uiBatch);
uiLayer->addObject(&uiCamera);
defaultRenderTarget.width = static_cast<int>(resolution.x);
defaultRenderTarget.height = static_cast<int>(resolution.y);
defaultRenderTarget.framebuffer = 0;
resizeUI();
return true;
}
bool Application::loadControls()
{
// 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("toggle_debug_display", &toggleDebugDisplay);
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);
toggleDebugDisplay.bindKey(keyboard, SDL_SCANCODE_GRAVE);
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);
gameControlProfile->registerControl("walk-forward", &walkForward);
gameControlProfile->registerControl("walk-back", &walkBack);
gameControlProfile->registerControl("turn-left", &turnLeft);
gameControlProfile->registerControl("turn-right", &turnRight);
gameControlProfile->registerControl("toggle-pause", &togglePause);
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);
cameraZoomIn.bindMouseWheelAxis(mouse, MouseWheelAxis::POSITIVE_Y);
cameraZoomOut.bindMouseWheelAxis(mouse, MouseWheelAxis::NEGATIVE_Y);
cameraToggleOverheadView.bindKey(keyboard, SDL_SCANCODE_R);
cameraToggleNestView.bindKey(keyboard, SDL_SCANCODE_F);
walkForward.bindKey(keyboard, SDL_SCANCODE_UP);
walkBack.bindKey(keyboard, SDL_SCANCODE_DOWN);
turnLeft.bindKey(keyboard, SDL_SCANCODE_LEFT);
turnRight.bindKey(keyboard, SDL_SCANCODE_RIGHT);
togglePause.bindKey(keyboard, SDL_SCANCODE_SPACE);
return true;
}
bool Application::loadGame()
{
// Load biosphere
biosphere.load("data/biomes/");
// Load campaign
campaign.load("data/levels/");
currentWorldIndex = 0;
currentLevelIndex = 0;
simulationPaused = false;
// Allocate level
currentLevel = new Level();
// Create colony
colony = new Colony();
colony->setAntModel(antModel);
currentTool = nullptr;
// Create tools
forceps = new Forceps(forcepsModel);
forceps->setColony(colony);
forceps->setCameraController(surfaceCam);
lens = new Lens(lensModel);
lens->setCameraController(surfaceCam);
brush = new Brush(brushModel);
brush->setCameraController(surfaceCam);
loadWorld(0);
loadLevel(0);
return true;
}
void Application::resizeUI()
{
// Adjust render target dimensions
defaultRenderTarget.width = static_cast<int>(resolution.x);
defaultRenderTarget.height = static_cast<int>(resolution.y);
// Adjust UI dimensions
uiRootElement->setDimensions(resolution);
uiRootElement->update();
darkenImage->setDimensions(resolution);
// Adjust UI camera projection
uiCamera.setOrthographic(0.0f, resolution.x, resolution.y, 0.0f, -1.0f, 1.0f);
}
void Application::restringUI()
{
// Get strings
std::string pressAnyKeyString;
std::string backString;
std::string onString;
std::string offString;
std::string continueString;
std::string newGameString;
std::string levelsString;
std::string sandboxString;
std::string optionsString;
std::string exitString;
std::string windowedResolutionString;
std::string fullscreenResolutionString;
std::string fullscreenString;
std::string verticalSyncString;
std::string languageString;
std::string controlsString;
std::string resumeString;
std::string mainMenuString;
strings.get("press-any-key", &pressAnyKeyString);
strings.get("back", &backString);
strings.get("on", &onString);
strings.get("off", &offString);
strings.get("continue", &continueString);
strings.get("new-game", &newGameString);
strings.get("levels", &levelsString);
strings.get("sandbox", &sandboxString);
strings.get("options", &optionsString);
strings.get("exit", &exitString);
strings.get("windowed-resolution", &windowedResolutionString);
strings.get("fullscreen-resolution", &fullscreenResolutionString);
strings.get("fullscreen", &fullscreenString);
strings.get("vertical-sync", &verticalSyncString);
strings.get("language", &languageString);
strings.get("controls", &controlsString);
strings.get("resume", &resumeString);
strings.get("main-menu", &mainMenuString);
// Title screen
anyKeyLabel->setText(pressAnyKeyString);
// Main menu
mainMenuContinueItem->setName(continueString);
mainMenuLevelsItem->setName(levelsString);
mainMenuNewGameItem->setName(newGameString);
mainMenuSandboxItem->setName(sandboxString);
mainMenuOptionsItem->setName(optionsString);
mainMenuExitItem->setName(exitString);
// Levels menu
std::size_t levelItemIndex = 0;
for (std::size_t world = 0; world < campaign.getWorldCount(); ++world)
{
for (std::size_t level = 0; level < campaign.getLevelCount(world); ++level)
{
// Look up level name
std::string levelName = getLevelName(world, level);
// Create label
std::stringstream stream;
stream << (world + 1) << "-" << (level + 1) << ": " << levelName;
// Set item name
MenuItem* levelItem = levelsMenu->getItem(levelItemIndex);
levelItem->setName(stream.str());
++levelItemIndex;
}
}
levelsMenuBackItem->setName(backString);
// Options menu
optionsMenuWindowedResolutionItem->setName(windowedResolutionString);
optionsMenuFullscreenResolutionItem->setName(fullscreenResolutionString);
std::size_t resolutionIndex = 0;
for (std::size_t i = 0; i < resolutions.size(); ++i)
{
std::stringstream stream;
stream << resolutions[i].x << "x" << resolutions[i].y;
optionsMenuWindowedResolutionItem->setValueName(i, stream.str());
optionsMenuFullscreenResolutionItem->setValueName(i, stream.str());
}
optionsMenuFullscreenItem->setName(fullscreenString);
optionsMenuFullscreenItem->setValueName(0, offString);
optionsMenuFullscreenItem->setValueName(1, onString);
optionsMenuVSyncItem->setName(verticalSyncString);
optionsMenuVSyncItem->setValueName(0, offString);
optionsMenuVSyncItem->setValueName(1, onString);
optionsMenuLanguageItem->setName(languageString);
for (std::size_t i = 0; i < languages.size(); ++i)
{
std::string languageName;
strings.get(languages[i], &languageName);
optionsMenuLanguageItem->setValueName(i, languageName);
}
optionsMenuControlsItem->setName(controlsString);
optionsMenuBackItem->setName(backString);
// Pause menu
pauseMenuResumeItem->setName(resumeString);
pauseMenuLevelsItem->setName(levelsString);
pauseMenuOptionsItem->setName(optionsString);
pauseMenuMainMenuItem->setName(mainMenuString);
pauseMenuExitItem->setName(exitString);
}
void Application::openMenu(Menu* menu)
{
if (activeMenu != nullptr)
{
closeMenu();
}
activeMenu = menu;
activeMenu->getUIContainer()->setActive(true);
activeMenu->getUIContainer()->setVisible(true);
if (activeMenu->getSelectedItem() == nullptr)
{
activeMenu->select(0);
}
}
void Application::closeMenu()
{
if (activeMenu != nullptr)
{
activeMenu->getUIContainer()->setActive(false);
activeMenu->getUIContainer()->setVisible(false);
previousActiveMenu = activeMenu;
activeMenu = nullptr;
}
}
void Application::selectMenuItem(std::size_t index)
{
if (activeMenu != nullptr)
{
activeMenu->select(index);
}
}
void Application::activateMenuItem()
{
if (activeMenu != nullptr)
{
activeMenu->activate();
}
}
void Application::incrementMenuItem()
{
if (activeMenu != nullptr)
{
MenuItem* item = activeMenu->getSelectedItem();
if (item != nullptr)
{
if (item->getValueCount() != 0)
{
item->setValueIndex((item->getValueIndex() + 1) % item->getValueCount());
}
}
}
}
void Application::decrementMenuItem()
{
if (activeMenu != nullptr)
{
MenuItem* item = activeMenu->getSelectedItem();
if (item != nullptr)
{
if (item->getValueCount() != 0)
{
if (!item->getValueIndex())
{
item->setValueIndex(item->getValueCount() - 1);
}
else
{
item->setValueIndex(item->getValueIndex() - 1);
}
}
}
}
}
void Application::continueGame()
{
closeMenu();
int world = 0;
int level = 0;
settings.get("continue_world", &world);
settings.get("continue_level", &level);
if (world != currentWorldIndex)
{
loadWorld(world);
}
if (level != currentLevelIndex)
{
loadLevel(level);
}
// Begin title fade-out
titleFadeOutTween->reset();
titleFadeOutTween->start();
// Begin fade-out
fadeOutTween->setEndCallback(std::bind(&Application::changeState, this, gameState));
fadeOutTween->reset();
fadeOutTween->start();
}
void Application::newGame()
{
// Close main menu
closeMenu();
// Begin title fade-out
titleFadeOutTween->reset();
titleFadeOutTween->start();
if (currentWorldIndex != 0 || currentLevelIndex != 0)
{
// Select first level of the first world
currentWorldIndex = 0;
currentLevelIndex = 0;
// Begin fade-out
fadeOutTween->setEndCallback(std::bind(&Application::changeState, this, gameState));
fadeOutTween->reset();
fadeOutTween->start();
}
else
{
// Begin fade-out
fadeOutTween->setEndCallback(std::bind(&Application::changeState, this, gameState));
fadeOutTween->reset();
fadeOutTween->start();
// Change state
//changeState(gameState);
}
}
void Application::deselectTool(Tool* tool)
{
if (tool != nullptr)
{
tool->setActive(false);
return;
}
}
void Application::selectTool(Tool* tool)
{
if (tool != nullptr)
{
tool->setActive(true);
}
currentTool = tool;
}
void Application::loadWorld(std::size_t index)
{
// Set current world
currentWorldIndex = index;
// Get world biome
const LevelParameterSet* levelParams = campaign.getLevelParams(currentWorldIndex, 0);
const Biome* biome = &biosphere.biomes[levelParams->biome];
// Setup rendering passes
soilPass.setHorizonOTexture(biome->soilHorizonO);
soilPass.setHorizonATexture(biome->soilHorizonA);
soilPass.setHorizonBTexture(biome->soilHorizonB);
soilPass.setHorizonCTexture(biome->soilHorizonC);
lightingPass.setDiffuseCubemap(biome->diffuseCubemap);
lightingPass.setSpecularCubemap(biome->specularCubemap);
skyboxPass.setCubemap(biome->specularCubemap);
}
void Application::loadLevel(std::size_t index)
{
// Set current level
currentLevelIndex = index;
// Load level
const LevelParameterSet* levelParams = campaign.getLevelParams(currentWorldIndex, currentLevelIndex);
currentLevel->load(*levelParams);
currentLevel->terrain.getSurfaceModel()->getGroup(0)->material = materialLoader->load("data/materials/debug-terrain-surface.mtl");
}
/*
void Application::loadLevel()
{
if (currentLevel < 1 || currentLevel >= campaign.levels[currentWorld].size())
{
std::cout << "Attempted to load invalid level" << std::endl;
return;
}
const LevelParameterSet* levelParams = campaign.getLevelParams(currentWorld, currentLevel);
const Biome* biome = &biosphere.biomes[levelParams->biome];
soilPass.setHorizonOTexture(biome->soilHorizonO);
soilPass.setHorizonATexture(biome->soilHorizonA);
soilPass.setHorizonBTexture(biome->soilHorizonB);
soilPass.setHorizonCTexture(biome->soilHorizonC);
std::string heightmap = std::string("data/textures/") + level->heightmap;
currentLevelTerrain->load(heightmap);
// Set skybox
skyboxPass.setCubemap(biome->specularCubemap);
//changeState(playState);
}
*/
void Application::pauseSimulation()
{
simulationPaused = true;
darkenImage->setVisible(true);
openMenu(pauseMenu);
pauseMenu->select(0);
}
void Application::unpauseSimulation()
{
simulationPaused = false;
darkenImage->setVisible(false);
closeMenu();
}
void Application::setDisplayDebugInfo(bool display)
{
displayDebugInfo = display;
frameTimeLabel->setVisible(displayDebugInfo);
depthTextureImage->setVisible(displayDebugInfo);
}
std::string Application::getLevelName(std::size_t world, std::size_t level) const
{
// Form level ID string
char levelIDBuffer[6];
std::sprintf(levelIDBuffer, "%02d-%02d", static_cast<int>(world + 1), static_cast<int>(level + 1));
std::string levelID(levelIDBuffer);
// Look up level name
std::string levelName;
strings.get(levelIDBuffer, &levelName);
return levelName;
}
void Application::selectWindowedResolution(std::size_t index)
{
windowedResolutionIndex = index;
if (!fullscreen)
{
// Select resolution
resolution = resolutions[windowedResolutionIndex];
// Resize window
SDL_SetWindowSize(window, static_cast<int>(resolution.x), static_cast<int>(resolution.y));
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
// Resize UI
resizeUI();
// Notify window observers
inputManager->update();
}
// Save settings
settings.set("windowed_width", resolutions[windowedResolutionIndex].x);
settings.set("windowed_height", resolutions[windowedResolutionIndex].y);
saveUserSettings();
}
void Application::selectFullscreenResolution(std::size_t index)
{
fullscreenResolutionIndex = index;
if (fullscreen)
{
// Select resolution
resolution = resolutions[fullscreenResolutionIndex];
// Resize window
SDL_SetWindowSize(window, static_cast<int>(resolution.x), static_cast<int>(resolution.y));
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
// Resize UI
resizeUI();
// Notify window observers
inputManager->update();
}
// Save settings
settings.set("fullscreen_width", resolutions[fullscreenResolutionIndex].x);
settings.set("fullscreen_height", resolutions[fullscreenResolutionIndex].y);
saveUserSettings();
}
void Application::selectFullscreenMode(std::size_t index)
{
fullscreen = (index == 1);
if (fullscreen)
{
resolution = resolutions[fullscreenResolutionIndex];
SDL_SetWindowSize(window, static_cast<int>(resolution.x), static_cast<int>(resolution.y));
if (SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN) != 0)
{
std::cerr << "Failed to set fullscreen mode: \"" << SDL_GetError() << "\"" << std::endl;
fullscreen = false;
}
}
else
{
resolution = resolutions[windowedResolutionIndex];
if (SDL_SetWindowFullscreen(window, 0) != 0)
{
std::cerr << "Failed to set windowed mode: \"" << SDL_GetError() << "\"" << std::endl;
fullscreen = true;
}
else
{
SDL_SetWindowSize(window, static_cast<int>(resolution.x), static_cast<int>(resolution.y));
SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
}
// Print mode and resolution
if (fullscreen)
{
std::cout << "Changed to fullscreen mode at resolution " << resolution.x << "x" << resolution.y << std::endl;
}
else
{
std::cout << "Changed to windowed mode at resolution " << resolution.x << "x" << resolution.y << std::endl;
}
// Save settings
settings.set("fullscreen", fullscreen);
saveUserSettings();
// Resize UI
resizeUI();
// Notify window observers
inputManager->update();
}
// index: 0 = off, 1 = on
void Application::selectVSyncMode(std::size_t index)
{
swapInterval = (index == 0) ? 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::selectLanguage(std::size_t index)
{
// Set language index
languageIndex = index;
// Clear strings
strings.clear();
// Load strings
std::string stringsFile = appDataPath + "strings/" + languages[languageIndex] + ".txt";
std::cout << "Loading strings from \"" << stringsFile << "\"... ";
if (!strings.load(stringsFile))
{
std::cout << "failed" << std::endl;
}
else
{
std::cout << "success" << std::endl;
}
// Save settings
settings.set("language", languages[languageIndex]);
saveUserSettings();
// Restring UI
restringUI();
}