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

592 lines
16 KiB

7 years ago
  1. /*
  2. * Copyright (C) 2017 Christopher J. Howard
  3. *
  4. * This file is part of Antkeeper Source Code.
  5. *
  6. * Antkeeper Source Code is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * Antkeeper Source Code is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with Antkeeper Source Code. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include "application.hpp"
  20. #include "application-state.hpp"
  21. #include "states/splash-state.hpp"
  22. #include "states/title-state.hpp"
  23. #include "states/experiment-state.hpp"
  24. #include <cstdlib>
  25. #include <iostream>
  26. #include <SDL.h>
  27. #define OPENGL_VERSION_MAJOR 3
  28. #define OPENGL_VERSION_MINOR 3
  29. #include "model-loader.hpp"
  30. #include "material-loader.hpp"
  31. Application::Application(int argc, char* argv[]):
  32. state(nullptr),
  33. nextState(nullptr),
  34. terminationCode(EXIT_SUCCESS)
  35. {
  36. window = nullptr;
  37. context = nullptr;
  38. // Initialize SDL
  39. std::cout << "Initializing SDL... ";
  40. if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER) < 0)
  41. {
  42. std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl;
  43. close(EXIT_FAILURE);
  44. return;
  45. }
  46. else
  47. {
  48. std::cout << "success" << std::endl;
  49. }
  50. // Print SDL version strings
  51. SDL_version compiled;
  52. SDL_version linked;
  53. SDL_VERSION(&compiled);
  54. SDL_GetVersion(&linked);
  55. std::cout << "Compiled with SDL " << (int)compiled.major << "." << (int)compiled.minor << "." << (int)compiled.patch << std::endl;
  56. std::cout << "Linking to SDL " << (int)linked.major << "." << (int)linked.minor << "." << (int)linked.patch << std::endl;
  57. // Find app and user data paths
  58. appDataPath = std::string(SDL_GetBasePath()) + "data/";
  59. userDataPath = SDL_GetPrefPath("cjhoward", "antkeeper");
  60. std::cout << "Application data path: \"" << appDataPath << "\"" << std::endl;
  61. std::cout << "User data path: \"" << userDataPath << "\"" << std::endl;
  62. // Form pathes to settings files
  63. defaultSettingsFilename = appDataPath + "default-settings.txt";
  64. userSettingsFilename = userDataPath + "settings.txt";
  65. // Load default settings
  66. std::cout << "Loading default settings from \"" << defaultSettingsFilename << "\"... ";
  67. if (!settings.load(defaultSettingsFilename))
  68. {
  69. std::cout << "failed" << std::endl;
  70. close(EXIT_FAILURE);
  71. return;
  72. }
  73. else
  74. {
  75. std::cout << "success" << std::endl;
  76. }
  77. // Load user settings
  78. std::cout << "Loading user settings from \"" << userSettingsFilename << "\"... ";
  79. if (!settings.load(userSettingsFilename))
  80. {
  81. // Failed, save default settings as user settings
  82. std::cout << "failed" << std::endl;
  83. saveUserSettings();
  84. }
  85. else
  86. {
  87. std::cout << "success" << std::endl;
  88. }
  89. // Get values of required settings
  90. settings.get("fullscreen", &fullscreen);
  91. settings.get("fullscreen_width", &fullscreenWidth);
  92. settings.get("fullscreen_height", &fullscreenHeight);
  93. settings.get("windowed_width", &windowedWidth);
  94. settings.get("windowed_height", &windowedHeight);
  95. settings.get("swap_interval", &swapInterval);
  96. // Load strings
  97. std::string language;
  98. settings.get("language", &language);
  99. std::string stringsFile = appDataPath + "strings/" + language + ".txt";
  100. std::cout << "Loading strings from \"" << stringsFile << "\"... ";
  101. if (!strings.load(stringsFile))
  102. {
  103. std::cout << "failed" << std::endl;
  104. }
  105. else
  106. {
  107. std::cout << "success" << std::endl;
  108. }
  109. // Select OpenGL version
  110. SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
  111. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, OPENGL_VERSION_MAJOR);
  112. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, OPENGL_VERSION_MINOR);
  113. // Set OpenGL buffer attributes
  114. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
  115. SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
  116. SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
  117. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
  118. SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
  119. // Check desktop display mode
  120. SDL_DisplayMode displayMode;
  121. if (SDL_GetDesktopDisplayMode(0, &displayMode) != 0)
  122. {
  123. std::cerr << "Failed to get desktop display mode: \"" << SDL_GetError() << "\"" << std::endl;
  124. close(EXIT_FAILURE);
  125. return;
  126. }
  127. // Check (usable?) display bounds
  128. SDL_Rect displayBounds;
  129. if (SDL_GetDisplayBounds(0, &displayBounds) != 0)
  130. {
  131. std::cerr << "Failed to get display bounds: \"" << SDL_GetError() << "\"" << std::endl;
  132. close(EXIT_FAILURE);
  133. return;
  134. }
  135. // Use display resolution if settings request
  136. if (windowedWidth == -1 || windowedHeight == -1)
  137. {
  138. windowedWidth = displayBounds.w;
  139. windowedHeight = displayBounds.h;
  140. }
  141. if (fullscreenWidth == -1 || fullscreenHeight == -1)
  142. {
  143. fullscreenWidth = displayMode.w;
  144. fullscreenHeight = displayMode.h;
  145. }
  146. // Determine window parameters
  147. Uint32 windowFlags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
  148. if (fullscreen)
  149. {
  150. width = fullscreenWidth;
  151. height = fullscreenHeight;
  152. windowFlags |= SDL_WINDOW_FULLSCREEN;
  153. }
  154. else
  155. {
  156. width = windowedWidth;
  157. height = windowedHeight;
  158. }
  159. // Get window title string
  160. std::string title;
  161. strings.get("title", &title);
  162. // Create window
  163. std::cout << "Creating a window... ";
  164. window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, windowFlags);
  165. if (window == nullptr)
  166. {
  167. std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl;
  168. close(EXIT_FAILURE);
  169. return;
  170. }
  171. else
  172. {
  173. std::cout << "success" << std::endl;
  174. }
  175. // Get actual window size
  176. SDL_GetWindowSize(window, &width, &height);
  177. if (fullscreen)
  178. {
  179. fullscreenWidth = width;
  180. fullscreenHeight = height;
  181. }
  182. else
  183. {
  184. windowedWidth = width;
  185. windowedHeight = height;
  186. }
  187. // Print video driver
  188. const char* videoDriver = SDL_GetCurrentVideoDriver();
  189. if (!videoDriver)
  190. {
  191. std::cout << "Unable to determine video driver" << std::endl;
  192. }
  193. else
  194. {
  195. std::cout << "Using video driver \"" << videoDriver << "\"" << std::endl;
  196. }
  197. // Create an OpenGL context
  198. std::cout << "Creating an OpenGL context... ";
  199. context = SDL_GL_CreateContext(window);
  200. if (context == nullptr)
  201. {
  202. std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl;
  203. close(EXIT_FAILURE);
  204. return;
  205. }
  206. else
  207. {
  208. std::cout << "success" << std::endl;
  209. }
  210. // Initialize GL3W
  211. std::cout << "Initializing GL3W... ";
  212. if (gl3wInit())
  213. {
  214. std::cout << "failed" << std::endl;
  215. close(EXIT_FAILURE);
  216. return;
  217. }
  218. else
  219. {
  220. std::cout << "success" << std::endl;
  221. }
  222. // Check if OpenGL version is supported
  223. if (!gl3wIsSupported(OPENGL_VERSION_MAJOR, OPENGL_VERSION_MINOR))
  224. {
  225. std::cout << "OpenGL " << OPENGL_VERSION_MAJOR << "." << OPENGL_VERSION_MINOR << " not supported" << std::endl;
  226. close(EXIT_FAILURE);
  227. return;
  228. }
  229. // Print OpenGL and GLSL version strings
  230. std::cout << "Using OpenGL " << glGetString(GL_VERSION) << ", GLSL " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl;
  231. // Set swap interval (vsync)
  232. if (swapInterval)
  233. {
  234. std::cout << "Enabling vertical sync... ";
  235. }
  236. else
  237. {
  238. std::cout << "Disabling vertical sync... ";
  239. }
  240. if (SDL_GL_SetSwapInterval(swapInterval) != 0)
  241. {
  242. std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl;
  243. swapInterval = SDL_GL_GetSwapInterval();
  244. }
  245. else
  246. {
  247. std::cout << "success" << std::endl;
  248. }
  249. // Get display DPI
  250. std::cout << "Getting DPI of display 0... ";
  251. if (SDL_GetDisplayDPI(0, &dpi, nullptr, nullptr) != 0)
  252. {
  253. std::cerr << "failed: \"" << SDL_GetError() << "\"" << std::endl;
  254. std::cout << "Reverting to default DPI" << std::endl;
  255. settings.get("default_dpi", &dpi);
  256. }
  257. else
  258. {
  259. std::cout << "success" << std::endl;
  260. }
  261. // Print DPI
  262. std::cout << "Rendering at " << dpi << " DPI" << std::endl;
  263. // Determine base font size
  264. settings.get("font_size", &fontSizePT);
  265. fontSizePX = fontSizePT * (1.0f / 72.0f) * dpi;
  266. // Print font size
  267. std::cout << "Base font size is " << fontSizePT << "pt (" << fontSizePX << "px)" << std::endl;
  268. // Setup input
  269. inputManager = new SDLInputManager();
  270. keyboard = (*inputManager->getKeyboards()).front();
  271. mouse = (*inputManager->getMice()).front();
  272. // Setup menu navigation controls
  273. menuControlProfile = new ControlProfile(inputManager);
  274. menuControlProfile->registerControl("menu_left", &menuLeft);
  275. menuControlProfile->registerControl("menu_right", &menuRight);
  276. menuControlProfile->registerControl("menu_up", &menuUp);
  277. menuControlProfile->registerControl("menu_down", &menuDown);
  278. menuControlProfile->registerControl("menu_select", &menuSelect);
  279. menuControlProfile->registerControl("menu_cancel", &menuCancel);
  280. menuControlProfile->registerControl("toggle_fullscreen", &toggleFullscreen);
  281. menuControlProfile->registerControl("escape", &escape);
  282. menuLeft.bindKey(keyboard, SDL_SCANCODE_LEFT);
  283. menuLeft.bindKey(keyboard, SDL_SCANCODE_A);
  284. menuRight.bindKey(keyboard, SDL_SCANCODE_RIGHT);
  285. menuRight.bindKey(keyboard, SDL_SCANCODE_D);
  286. menuUp.bindKey(keyboard, SDL_SCANCODE_UP);
  287. menuUp.bindKey(keyboard, SDL_SCANCODE_W);
  288. menuDown.bindKey(keyboard, SDL_SCANCODE_DOWN);
  289. menuDown.bindKey(keyboard, SDL_SCANCODE_S);
  290. menuSelect.bindKey(keyboard, SDL_SCANCODE_RETURN);
  291. menuSelect.bindKey(keyboard, SDL_SCANCODE_SPACE);
  292. menuSelect.bindKey(keyboard, SDL_SCANCODE_Z);
  293. menuCancel.bindKey(keyboard, SDL_SCANCODE_BACKSPACE);
  294. menuCancel.bindKey(keyboard, SDL_SCANCODE_X);
  295. toggleFullscreen.bindKey(keyboard, SDL_SCANCODE_F11);
  296. escape.bindKey(keyboard, SDL_SCANCODE_ESCAPE);
  297. // Setup in-game controls
  298. gameControlProfile = new ControlProfile(inputManager);
  299. gameControlProfile->registerControl("camera-move-forward", &cameraMoveForward);
  300. gameControlProfile->registerControl("camera-move-back", &cameraMoveBack);
  301. gameControlProfile->registerControl("camera-move-left", &cameraMoveLeft);
  302. gameControlProfile->registerControl("camera-move-right", &cameraMoveRight);
  303. gameControlProfile->registerControl("camera-rotate-cw", &cameraRotateCW);
  304. gameControlProfile->registerControl("camera-rotate-ccw", &cameraRotateCCW);
  305. gameControlProfile->registerControl("camera-zoom-in", &cameraZoomIn);
  306. gameControlProfile->registerControl("camera-zoom-out", &cameraZoomOut);
  307. gameControlProfile->registerControl("camera-toggle-nest-view", &cameraToggleNestView);
  308. gameControlProfile->registerControl("camera-toggle-overhead-view", &cameraToggleOverheadView);
  309. cameraMoveForward.bindKey(keyboard, SDL_SCANCODE_W);
  310. cameraMoveBack.bindKey(keyboard, SDL_SCANCODE_S);
  311. cameraMoveLeft.bindKey(keyboard, SDL_SCANCODE_A);
  312. cameraMoveRight.bindKey(keyboard, SDL_SCANCODE_D);
  313. cameraRotateCW.bindKey(keyboard, SDL_SCANCODE_Q);
  314. cameraRotateCCW.bindKey(keyboard, SDL_SCANCODE_E);
  315. cameraZoomIn.bindKey(keyboard, SDL_SCANCODE_EQUALS);
  316. cameraZoomOut.bindKey(keyboard, SDL_SCANCODE_MINUS);
  317. cameraToggleOverheadView.bindKey(keyboard, SDL_SCANCODE_R);
  318. cameraToggleNestView.bindKey(keyboard, SDL_SCANCODE_F);
  319. cameraOverheadView = true;
  320. cameraNestView = false;
  321. // Allocate states
  322. splashState = new SplashState(this);
  323. titleState = new TitleState(this);
  324. experimentState = new ExperimentState(this);
  325. // Clear screen to black
  326. glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
  327. glClear(GL_COLOR_BUFFER_BIT);
  328. SDL_GL_SwapWindow(window);
  329. // Setup loaders
  330. materialLoader = new MaterialLoader();
  331. modelLoader = new ModelLoader();
  332. modelLoader->setMaterialLoader(materialLoader);
  333. // Enter splash state
  334. state = nextState = splashState;
  335. state->enter();
  336. }
  337. Application::~Application()
  338. {
  339. SDL_GL_DeleteContext(context);
  340. SDL_DestroyWindow(window);
  341. SDL_Quit();
  342. }
  343. int Application::execute()
  344. {
  345. while (state != nullptr)
  346. {
  347. state->execute();
  348. if (nextState != state)
  349. {
  350. state->exit();
  351. state = nextState;
  352. if (nextState != nullptr)
  353. {
  354. state->enter();
  355. }
  356. }
  357. }
  358. return terminationCode;
  359. }
  360. void Application::changeState(ApplicationState* state)
  361. {
  362. nextState = state;
  363. }
  364. void Application::setTerminationCode(int code)
  365. {
  366. terminationCode = code;
  367. }
  368. void Application::close(int terminationCode)
  369. {
  370. setTerminationCode(terminationCode);
  371. changeState(nullptr);
  372. }
  373. void Application::changeFullscreen()
  374. {
  375. fullscreen = !fullscreen;
  376. if (fullscreen)
  377. {
  378. width = fullscreenWidth;
  379. height = fullscreenHeight;
  380. SDL_SetWindowSize(window, width, height);
  381. if (SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN) != 0)
  382. {
  383. std::cerr << "Failed to set fullscreen mode: \"" << SDL_GetError() << "\"" << std::endl;
  384. fullscreen = false;
  385. }
  386. }
  387. else
  388. {
  389. width = windowedWidth;
  390. height = windowedHeight;
  391. if (SDL_SetWindowFullscreen(window, 0) != 0)
  392. {
  393. std::cerr << "Failed to set windowed mode: \"" << SDL_GetError() << "\"" << std::endl;
  394. fullscreen = true;
  395. }
  396. else
  397. {
  398. SDL_SetWindowSize(window, width, height);
  399. SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
  400. }
  401. }
  402. // Get actual window size
  403. SDL_GetWindowSize(window, &width, &height);
  404. // Print mode and resolution
  405. if (fullscreen)
  406. {
  407. std::cout << "Changed to fullscreen mode at resolution " << width << "x" << height << std::endl;
  408. }
  409. else
  410. {
  411. std::cout << "Changed to windowed mode at resolution " << width << "x" << height << std::endl;
  412. }
  413. // Save settings
  414. settings.set("fullscreen", fullscreen);
  415. saveUserSettings();
  416. // Notify window observers
  417. inputManager->update();
  418. }
  419. void Application::changeVerticalSync()
  420. {
  421. swapInterval = (swapInterval == 1) ? 0 : 1;
  422. if (swapInterval == 1)
  423. {
  424. std::cout << "Enabling vertical sync... ";
  425. }
  426. else
  427. {
  428. std::cout << "Disabling vertical sync... ";
  429. }
  430. if (SDL_GL_SetSwapInterval(swapInterval) != 0)
  431. {
  432. std::cout << "failed: \"" << SDL_GetError() << "\"" << std::endl;
  433. swapInterval = SDL_GL_GetSwapInterval();
  434. }
  435. else
  436. {
  437. std::cout << "success" << std::endl;
  438. }
  439. // Save settings
  440. settings.set("swap_interval", swapInterval);
  441. saveUserSettings();
  442. }
  443. void Application::saveUserSettings()
  444. {
  445. std::cout << "Saving user setttings to \"" << userSettingsFilename << "\"... ";
  446. if (!settings.save(userSettingsFilename))
  447. {
  448. std::cout << "failed" << std::endl;
  449. }
  450. else
  451. {
  452. std::cout << "success" << std::endl;
  453. }
  454. }
  455. void Application::resizeUI()
  456. {
  457. // Adjust UI dimensions
  458. uiRootElement->setDimensions(Vector2(width, height));
  459. uiRootElement->update();
  460. // Adjust UI camera projection
  461. uiCamera.setOrthographic(0, width, height, 0, -1.0f, 1.0f);
  462. }
  463. void Application::enterMenu(std::size_t index)
  464. {
  465. if (index != currentMenuIndex)
  466. {
  467. exitMenu(currentMenuIndex);
  468. }
  469. // Select next menu
  470. currentMenuIndex = index;
  471. selectedMenuItemIndex = 0;
  472. currentMenu = menus[currentMenuIndex];
  473. menus[currentMenuIndex]->getItem(selectedMenuItemIndex)->select();
  474. // Start menu fade-in tween
  475. menuFadeInTween->setUpdateCallback(std::bind(UIElement::setTintColor, menuContainers[currentMenuIndex], std::placeholders::_1));
  476. menuFadeInTween->setEndCallback(std::bind(UIElement::setActive, menuContainers[currentMenuIndex], true));
  477. menuFadeInTween->reset();
  478. menuFadeInTween->start();
  479. // Start menu slide-in tween
  480. menuSlideInTween->setUpdateCallback(std::bind(UIElement::setTranslation, menuContainers[currentMenuIndex], std::placeholders::_1));
  481. menuSlideInTween->reset();
  482. menuSlideInTween->start();
  483. // Make menu visible
  484. menuContainers[currentMenuIndex]->setVisible(true);
  485. }
  486. void Application::exitMenu(std::size_t index)
  487. {
  488. // Deactivate previous menu
  489. menuContainers[currentMenuIndex]->setActive(false);
  490. // Fade out previous menu
  491. menuFadeOutTween->setUpdateCallback(std::bind(UIElement::setTintColor, menuContainers[currentMenuIndex], std::placeholders::_1));
  492. menuFadeOutTween->setEndCallback(std::bind(UIElement::setVisible, menuContainers[currentMenuIndex], false));
  493. menuFadeOutTween->reset();
  494. menuFadeOutTween->start();
  495. }
  496. void Application::selectMenuItem(std::size_t index)
  497. {
  498. if (currentMenu == nullptr || index > currentMenu->getItemCount())
  499. {
  500. std::cout << "Selected invalid menu item" << std::endl;
  501. return;
  502. }
  503. MenuItem* previousItem = currentMenu->getItem(selectedMenuItemIndex);
  504. previousItem->deselect();
  505. selectedMenuItemIndex = index;
  506. MenuItem* nextItem = currentMenu->getItem(selectedMenuItemIndex);
  507. nextItem->select();
  508. }
  509. void Application::activateMenuItem(std::size_t index)
  510. {
  511. if (index > menus[currentMenuIndex]->getItemCount())
  512. {
  513. std::cout << "Activated invalid menu item" << std::endl;
  514. return;
  515. }
  516. menus[currentMenuIndex]->getItem(index)->deselect();
  517. menus[currentMenuIndex]->getItem(index)->activate();
  518. }