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

1104 lines
41 KiB

  1. /*
  2. * Copyright (C) 2020 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 "configuration.hpp"
  21. #include "state/application-states.hpp"
  22. #include "filesystem.hpp"
  23. #include "math.hpp"
  24. #include "timestamp.hpp"
  25. // STL
  26. #include <cstdlib>
  27. #include <iostream>
  28. #include <stdexcept>
  29. #include <thread>
  30. #include <string>
  31. #include <iomanip>
  32. // External
  33. #include <glad/glad.h>
  34. #include <SDL2/SDL.h>
  35. #include "stb/stb_image_write.h"
  36. // Debug
  37. #include "debug/ansi-codes.hpp"
  38. // Resources
  39. #include "resources/resource-manager.hpp"
  40. // Input
  41. #include "input/sdl-scancode-table.hpp"
  42. #include "input/sdl-game-controller-tables.hpp"
  43. #include "input/scancode.hpp"
  44. #include "input/input-mapping.hpp"
  45. // Rasterizer
  46. #include "rasterizer/rasterizer.hpp"
  47. #include "rasterizer/framebuffer.hpp"
  48. #include "rasterizer/texture-2d.hpp"
  49. #include "rasterizer/pixel-type.hpp"
  50. #include "rasterizer/pixel-format.hpp"
  51. #include "rasterizer/vertex-buffer.hpp"
  52. #include "rasterizer/vertex-array.hpp"
  53. #include "rasterizer/vertex-attribute-type.hpp"
  54. #include "rasterizer/texture-wrapping.hpp"
  55. #include "rasterizer/texture-filter.hpp"
  56. // Renderer
  57. #include "renderer/simple-render-pass.hpp"
  58. #include "renderer/passes/shadow-map-pass.hpp"
  59. #include "renderer/passes/sky-pass.hpp"
  60. #include "renderer/passes/clear-pass.hpp"
  61. #include "renderer/passes/material-pass.hpp"
  62. #include "renderer/passes/bloom-pass.hpp"
  63. #include "renderer/passes/final-pass.hpp"
  64. #include "renderer/vertex-attributes.hpp"
  65. #include "renderer/material-flags.hpp"
  66. #include "renderer/material-property.hpp"
  67. // Animation
  68. #include "animation/animator.hpp"
  69. // Scene
  70. #include "scene/billboard.hpp"
  71. #include "scene/model-instance.hpp"
  72. // Systems
  73. #include "systems/behavior-system.hpp"
  74. #include "systems/camera-system.hpp"
  75. #include "systems/collision-system.hpp"
  76. #include "systems/locomotion-system.hpp"
  77. #include "systems/model-system.hpp"
  78. #include "systems/nest-system.hpp"
  79. #include "systems/placement-system.hpp"
  80. #include "systems/samara-system.hpp"
  81. #include "systems/subterrain-system.hpp"
  82. #include "systems/terrain-system.hpp"
  83. #include "systems/vegetation-system.hpp"
  84. #include "systems/tool-system.hpp"
  85. #include "systems/control-system.hpp"
  86. #include "systems/ui-system.hpp"
  87. // Entity components
  88. #include "entity/components/cavity-component.hpp"
  89. using namespace vmq::operators;
  90. application::application(int argc, char** argv):
  91. closed(false),
  92. exit_status(EXIT_SUCCESS)
  93. {
  94. // Format log messages
  95. logger.set_warning_prefix("Warning: ");
  96. logger.set_error_prefix("Error: ");
  97. logger.set_success_prefix(std::string());
  98. #if defined(DEBUG)
  99. #if !defined(_WIN32)
  100. logger.set_warning_prefix(std::string(ansi::bold) + std::string(ansi::yellow) + std::string("Warning: ") + std::string(ansi::reset) + std::string(ansi::yellow));
  101. logger.set_warning_postfix(ansi::reset);
  102. logger.set_error_prefix(std::string(ansi::bold) + std::string(ansi::red) + std::string("Error: ") + std::string(ansi::reset) + std::string(ansi::red));
  103. logger.set_error_postfix(ansi::reset);
  104. logger.set_success_prefix(ansi::green);
  105. logger.set_success_postfix(ansi::reset);
  106. #endif
  107. #endif
  108. // Redirect logger output
  109. #if defined(DEBUG)
  110. logger.redirect(&std::cout);
  111. #else
  112. std::string log_filename = "log.txt";
  113. log_filestream.open(log_filename.c_str());
  114. logger.redirect(&log_filestream);
  115. #endif
  116. // Determine application name
  117. std::string application_name;
  118. #if defined(_WIN32)
  119. application_name = "Antkeeper";
  120. #else
  121. application_name = "antkeeper";
  122. #endif
  123. // Form resource paths
  124. data_path = get_data_path(application_name) + "data/";
  125. config_path = get_config_path(application_name);
  126. logger.log("Detected data path as \"" + data_path + "\"\n");
  127. logger.log("Detected config path as \"" + config_path + "\"\n");
  128. screenshots_path = config_path + "screenshots/";
  129. // Create nonexistent config directories
  130. std::vector<std::string> config_paths;
  131. config_paths.push_back(config_path);
  132. config_paths.push_back(screenshots_path);
  133. for (const std::string& path: config_paths)
  134. {
  135. if (!path_exists(path))
  136. {
  137. int create_path_task = logger.open_task("Creating directory \"" + path + "\"");
  138. if (create_directory(path))
  139. {
  140. logger.close_task(create_path_task, EXIT_SUCCESS);
  141. }
  142. else
  143. {
  144. logger.close_task(create_path_task, EXIT_FAILURE);
  145. }
  146. }
  147. }
  148. // Setup resource manager
  149. resource_manager = new ::resource_manager();
  150. resource_manager->set_logger(&logger);
  151. // Include resource search paths in order of priority
  152. resource_manager->include(config_path);
  153. resource_manager->include(data_path);
  154. resource_manager->include(data_path + "/shaders/include/");
  155. resource_manager->include(data_path + "/shaders/src/");
  156. resource_manager->include(data_path + "/models/");
  157. resource_manager->include(data_path + "/textures/");
  158. resource_manager->include(data_path + "/materials/");
  159. resource_manager->include(data_path + "/entities/");
  160. resource_manager->include(data_path + "/behaviors/");
  161. resource_manager->include(data_path + "/controls/");
  162. // Get SDL compiled version
  163. SDL_version sdl_compiled_version;
  164. SDL_VERSION(&sdl_compiled_version);
  165. std::string sdl_compiled_version_string = std::to_string(sdl_compiled_version.major) + "." + std::to_string(sdl_compiled_version.minor) + "." + std::to_string(sdl_compiled_version.patch);
  166. logger.log("Compiled against SDL " + sdl_compiled_version_string + "\n");
  167. // Get SDL linked version
  168. SDL_version sdl_linked_version;
  169. SDL_GetVersion(&sdl_linked_version);
  170. std::string sdl_linked_version_string = std::to_string(sdl_linked_version.major) + "." + std::to_string(sdl_linked_version.minor) + "." + std::to_string(sdl_linked_version.patch);
  171. logger.log("Linking against SDL " + sdl_linked_version_string + "\n");
  172. // Init SDL
  173. int sdl_init_task = logger.open_task("Initializing SDL");
  174. if (SDL_Init(SDL_INIT_VIDEO) != 0)
  175. {
  176. logger.close_task(sdl_init_task, EXIT_FAILURE);
  177. throw std::runtime_error("Failed to initialize SDL");
  178. }
  179. else
  180. {
  181. logger.close_task(sdl_init_task, EXIT_SUCCESS);
  182. }
  183. // Load default OpenGL library
  184. int load_gl_task = logger.open_task("Loading OpenGL library");
  185. if (SDL_GL_LoadLibrary(nullptr) != 0)
  186. {
  187. logger.close_task(load_gl_task, EXIT_FAILURE);
  188. }
  189. else
  190. {
  191. logger.close_task(load_gl_task, EXIT_SUCCESS);
  192. }
  193. // Set window creation hints
  194. SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
  195. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
  196. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
  197. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  198. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
  199. SDL_DisplayMode sdl_display_mode;
  200. if (SDL_GetDesktopDisplayMode(0, &sdl_display_mode) != 0)
  201. {
  202. logger.error("Failed to get desktop display mode: \"" + std::string(SDL_GetError()) + "\"\n");
  203. }
  204. else
  205. {
  206. logger.log("Detected " + std::to_string(sdl_display_mode.w) + "x" + std::to_string(sdl_display_mode.h) + " display\n");
  207. display_dimensions = {sdl_display_mode.w, sdl_display_mode.h};
  208. }
  209. int window_width = 1920;
  210. int window_height = 1080;
  211. fullscreen = true;
  212. window_width = 1280;
  213. window_height = 720;
  214. fullscreen = false;
  215. viewport = {0.0f, 0.0f, static_cast<float>(window_width), static_cast<float>(window_height)};
  216. int window_flags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_HIDDEN;
  217. if (fullscreen)
  218. {
  219. window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
  220. }
  221. // Create window
  222. int window_creation_task = logger.open_task("Creating " + std::to_string(window_width) + "x" + std::to_string(window_height) + " window");
  223. window = SDL_CreateWindow
  224. (
  225. "Antkeeper",
  226. SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
  227. window_width, window_height,
  228. window_flags
  229. );
  230. if (!window)
  231. {
  232. logger.close_task(window_creation_task, EXIT_FAILURE);
  233. throw std::runtime_error("Failed to create SDL window");
  234. }
  235. else
  236. {
  237. logger.close_task(window_creation_task, EXIT_SUCCESS);
  238. }
  239. // Create OpenGL context
  240. int create_gl_context_task = logger.open_task("Creating OpenGL 3.3 context");
  241. context = SDL_GL_CreateContext(window);
  242. if (!context)
  243. {
  244. logger.close_task(create_gl_context_task, EXIT_FAILURE);
  245. throw std::runtime_error("Failed to create OpenGL context");
  246. }
  247. else
  248. {
  249. logger.close_task(create_gl_context_task, EXIT_SUCCESS);
  250. }
  251. // Load OpenGL functions via GLAD
  252. int load_gl_functions_task = logger.open_task("Loading OpenGL functions");
  253. if (!gladLoadGLLoader((GLADloadproc)SDL_GL_GetProcAddress))
  254. {
  255. logger.close_task(load_gl_functions_task, EXIT_FAILURE);
  256. }
  257. else
  258. {
  259. logger.close_task(load_gl_functions_task, EXIT_SUCCESS);
  260. }
  261. // Set v-sync mode
  262. int swap_interval = 1;
  263. int set_vsync_task = logger.open_task((swap_interval) ? "Enabling v-sync" : "Disabling v-sync");
  264. if (SDL_GL_SetSwapInterval(swap_interval) != 0)
  265. {
  266. logger.close_task(set_vsync_task, EXIT_FAILURE);
  267. }
  268. else
  269. {
  270. logger.close_task(set_vsync_task, EXIT_SUCCESS);
  271. }
  272. // Setup rasterizer
  273. rasterizer = new ::rasterizer();
  274. // Show window
  275. SDL_ShowWindow(window);
  276. // Clear window to black
  277. rasterizer->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f);
  278. rasterizer->clear_framebuffer(true, false, false);
  279. SDL_GL_SwapWindow(window);
  280. // Hide cursor
  281. SDL_ShowCursor(SDL_DISABLE);
  282. // Init SDL joystick and game controller subsystems
  283. int init_sdl_subsystems_task = logger.open_task("Initializing SDL Joystick and Game Controller subsystems");
  284. if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
  285. {
  286. logger.close_task(init_sdl_subsystems_task, EXIT_FAILURE);
  287. throw std::runtime_error("Failed to initialize SDL Joystick or Game Controller subsystems");
  288. }
  289. else
  290. {
  291. logger.close_task(init_sdl_subsystems_task, EXIT_SUCCESS);
  292. }
  293. // Load SDL game controller mappings
  294. int load_controller_db_task = logger.open_task("Loading SDL game controller mappings from database");
  295. std::string gamecontrollerdb_path = data_path + "controls/gamecontrollerdb.txt";
  296. if (SDL_GameControllerAddMappingsFromFile(gamecontrollerdb_path.c_str()) == -1)
  297. {
  298. logger.close_task(load_controller_db_task, EXIT_FAILURE);
  299. }
  300. else
  301. {
  302. logger.close_task(load_controller_db_task, EXIT_SUCCESS);
  303. }
  304. // Setup billboard VAO
  305. {
  306. const float billboard_vertex_data[] =
  307. {
  308. -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
  309. -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
  310. 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
  311. 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
  312. -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
  313. 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f
  314. };
  315. std::size_t billboard_vertex_size = 8;
  316. std::size_t billboard_vertex_stride = sizeof(float) * billboard_vertex_size;
  317. std::size_t billboard_vertex_count = 6;
  318. billboard_vbo = new vertex_buffer(sizeof(float) * billboard_vertex_size * billboard_vertex_count, billboard_vertex_data);
  319. billboard_vao = new vertex_array();
  320. billboard_vao->bind_attribute(VERTEX_POSITION_LOCATION, *billboard_vbo, 3, vertex_attribute_type::float_32, billboard_vertex_stride, 0);
  321. billboard_vao->bind_attribute(VERTEX_TEXCOORD_LOCATION, *billboard_vbo, 2, vertex_attribute_type::float_32, billboard_vertex_stride, sizeof(float) * 3);
  322. billboard_vao->bind_attribute(VERTEX_BARYCENTRIC_LOCATION, *billboard_vbo, 3, vertex_attribute_type::float_32, billboard_vertex_stride, sizeof(float) * 5);
  323. }
  324. // Setup renderer
  325. renderer.set_billboard_vao(billboard_vao);
  326. // Load fallback material
  327. fallback_material = resource_manager->load<material>("fallback.mtl");
  328. // Create shadow map depth texture and framebuffer
  329. shadow_map_resolution = 4096;
  330. shadow_map_depth_texture = new texture_2d(shadow_map_resolution, shadow_map_resolution, pixel_type::float_32, pixel_format::d);
  331. shadow_map_depth_texture->set_wrapping(texture_wrapping::clamp, texture_wrapping::clamp);
  332. shadow_map_depth_texture->set_filters(texture_min_filter::linear, texture_mag_filter::linear);
  333. shadow_map_depth_texture->set_max_anisotropy(0.0f);
  334. shadow_map_framebuffer = new framebuffer(shadow_map_resolution, shadow_map_resolution);
  335. shadow_map_framebuffer->attach(framebuffer_attachment_type::depth, shadow_map_depth_texture);
  336. // Create HDR framebuffer (32F color, 32F depth)
  337. framebuffer_hdr_color = new texture_2d(window_width, window_height, pixel_type::float_32, pixel_format::rgb);
  338. framebuffer_hdr_color->set_wrapping(texture_wrapping::clamp, texture_wrapping::clamp);
  339. framebuffer_hdr_color->set_filters(texture_min_filter::linear, texture_mag_filter::linear);
  340. framebuffer_hdr_color->set_max_anisotropy(0.0f);
  341. framebuffer_hdr_depth = new texture_2d(window_width, window_height, pixel_type::float_32, pixel_format::d);
  342. framebuffer_hdr_depth->set_wrapping(texture_wrapping::clamp, texture_wrapping::clamp);
  343. framebuffer_hdr_depth->set_filters(texture_min_filter::linear, texture_mag_filter::linear);
  344. framebuffer_hdr_depth->set_max_anisotropy(0.0f);
  345. framebuffer_hdr = new framebuffer(window_width, window_height);
  346. framebuffer_hdr->attach(framebuffer_attachment_type::color, framebuffer_hdr_color);
  347. framebuffer_hdr->attach(framebuffer_attachment_type::depth, framebuffer_hdr_depth);
  348. // Create pingpong framebuffers (32F color, no depth)
  349. int bloom_width = window_width / 2;
  350. int bloom_height = window_height / 2;
  351. bloom_texture = new texture_2d(bloom_width, bloom_height, pixel_type::float_32, pixel_format::rgb);
  352. bloom_texture->set_wrapping(texture_wrapping::clamp, texture_wrapping::clamp);
  353. bloom_texture->set_filters(texture_min_filter::linear, texture_mag_filter::linear);
  354. bloom_texture->set_max_anisotropy(0.0f);
  355. framebuffer_bloom = new framebuffer(bloom_width, bloom_height);
  356. framebuffer_bloom->attach(framebuffer_attachment_type::color, bloom_texture);
  357. // Setup overworld passes
  358. shadow_map_clear_pass = new ::clear_pass(rasterizer, shadow_map_framebuffer);
  359. shadow_map_clear_pass->set_cleared_buffers(false, true, false);
  360. shadow_map_pass = new ::shadow_map_pass(rasterizer, shadow_map_framebuffer, resource_manager);
  361. shadow_map_pass->set_split_scheme_weight(0.75f);
  362. clear_pass = new ::clear_pass(rasterizer, framebuffer_hdr);
  363. clear_pass->set_cleared_buffers(true, true, false);
  364. sky_pass = new ::sky_pass(rasterizer, framebuffer_hdr, resource_manager);
  365. material_pass = new ::material_pass(rasterizer, framebuffer_hdr, resource_manager);
  366. material_pass->set_fallback_material(fallback_material);
  367. material_pass->set_time_tween(&time);
  368. material_pass->set_focal_point_tween(&focal_point_tween);
  369. material_pass->shadow_map_pass = shadow_map_pass;
  370. material_pass->shadow_map = shadow_map_depth_texture;
  371. bloom_pass = new ::bloom_pass(rasterizer, framebuffer_bloom, resource_manager);
  372. bloom_pass->set_source_texture(framebuffer_hdr_color);
  373. bloom_pass->set_brightness_threshold(1.0f);
  374. bloom_pass->set_blur_iterations(4);
  375. bloom_pass->set_enabled(false);
  376. final_pass = new ::final_pass(rasterizer, &rasterizer->get_default_framebuffer(), resource_manager);
  377. final_pass->set_color_texture(framebuffer_hdr_color);
  378. final_pass->set_bloom_texture(bloom_texture);
  379. // Setup overworld compositor
  380. overworld_compositor.add_pass(shadow_map_clear_pass);
  381. overworld_compositor.add_pass(shadow_map_pass);
  382. overworld_compositor.add_pass(clear_pass);
  383. overworld_compositor.add_pass(sky_pass);
  384. overworld_compositor.add_pass(material_pass);
  385. overworld_compositor.add_pass(bloom_pass);
  386. overworld_compositor.add_pass(final_pass);
  387. // Setup overworld camera
  388. overworld_camera.set_perspective(45.0f * vmq::pi<float> / 180.0f, (float)window_width / (float)window_height, 0.1f, 1000.0f);
  389. overworld_camera.set_compositor(&overworld_compositor);
  390. overworld_camera.set_composite_index(0);
  391. overworld_camera.set_active(true);
  392. // Setup underworld passes
  393. underworld_clear_pass = new ::clear_pass(rasterizer, framebuffer_hdr);
  394. underworld_clear_pass->set_cleared_buffers(true, true, false);
  395. underworld_material_pass = new ::material_pass(rasterizer, framebuffer_hdr, resource_manager);
  396. underworld_material_pass->set_fallback_material(fallback_material);
  397. underworld_material_pass->set_time_tween(&time);
  398. underworld_material_pass->set_focal_point_tween(&focal_point_tween);
  399. shader_program* underworld_final_shader = resource_manager->load<shader_program>("underground-final.glsl");
  400. underworld_final_pass = new simple_render_pass(rasterizer, &rasterizer->get_default_framebuffer(), underworld_final_shader);
  401. underworld_final_pass->set_time_tween(&time);
  402. underground_transition_property = underworld_final_pass->get_material()->add_property<float>("transition");
  403. underground_color_texture_property = underworld_final_pass->get_material()->add_property<const texture_2d*>("color_texture");
  404. underground_color_texture_property->set_value(framebuffer_hdr_color);
  405. // Setup underworld compositor
  406. underworld_compositor.add_pass(underworld_clear_pass);
  407. underworld_compositor.add_pass(underworld_material_pass);
  408. underworld_compositor.add_pass(underworld_final_pass);
  409. // Setup underworld camera
  410. underworld_camera.set_perspective(45.0f * vmq::pi<float> / 180.0f, (float)window_width / (float)window_height, 0.1f, 1000.0f);
  411. underworld_camera.look_at({0, 50, 0}, {0, 0, 0}, {0, 0, -1});
  412. underworld_camera.set_compositor(&underworld_compositor);
  413. underworld_camera.set_composite_index(0);
  414. underworld_camera.set_active(false);
  415. // Setup timeline system
  416. timeline.set_autoremove(true);
  417. // Setup animation system
  418. // ...
  419. animator = new ::animator();
  420. // ECS
  421. terrain_system = new ::terrain_system(ecs_registry, resource_manager);
  422. terrain_system->set_patch_size(TERRAIN_PATCH_SIZE);
  423. vegetation_system = new ::vegetation_system(ecs_registry);
  424. vegetation_system->set_terrain_patch_size(TERRAIN_PATCH_SIZE);
  425. vegetation_system->set_vegetation_patch_resolution(VEGETATION_PATCH_RESOLUTION);
  426. vegetation_system->set_vegetation_density(1.0f);
  427. vegetation_system->set_vegetation_model(resource_manager->load<model>("grass-tuft.obj"));
  428. vegetation_system->set_scene(&overworld_scene);
  429. tool_system = new ::tool_system(ecs_registry);
  430. tool_system->set_camera(&overworld_camera);
  431. tool_system->set_orbit_cam(&orbit_cam);
  432. tool_system->set_viewport(viewport);
  433. camera_system = new ::camera_system(ecs_registry);
  434. camera_system->set_orbit_cam(&orbit_cam);
  435. camera_system->set_viewport(viewport);
  436. subterrain_system = new ::subterrain_system(ecs_registry, resource_manager);
  437. subterrain_system->set_scene(&underworld_scene);
  438. nest_system = new ::nest_system(ecs_registry, resource_manager);
  439. collision_system = new ::collision_system(ecs_registry);
  440. samara_system = new ::samara_system(ecs_registry);
  441. placement_system = new ::placement_system(ecs_registry);
  442. behavior_system = new ::behavior_system(ecs_registry);
  443. locomotion_system = new ::locomotion_system(ecs_registry);
  444. model_system = new ::model_system(ecs_registry, overworld_scene);
  445. // Setup systems
  446. systems.push_back([this](double t, double dt){ this->overworld_scene.update_tweens(); this->underworld_scene.update_tweens(); this->ui_system->get_scene()->update_tweens(); focal_point_tween.update(); });
  447. systems.push_back([this](double t, double dt){ this->translate_sdl_events(); });
  448. systems.push_back([this](double t, double dt){ this->event_dispatcher.update(t); });
  449. systems.push_back([this](double t, double dt){ this->timeline.advance(dt); });
  450. systems.push_back(std::bind(&terrain_system::update, terrain_system, std::placeholders::_1, std::placeholders::_2));
  451. systems.push_back(std::bind(&vegetation_system::update, vegetation_system, std::placeholders::_1, std::placeholders::_2));
  452. systems.push_back(std::bind(&placement_system::update, placement_system, std::placeholders::_1, std::placeholders::_2));
  453. systems.push_back(std::bind(&nest_system::update, nest_system, std::placeholders::_1, std::placeholders::_2));
  454. systems.push_back(std::bind(&subterrain_system::update, subterrain_system, std::placeholders::_1, std::placeholders::_2));
  455. systems.push_back(std::bind(&collision_system::update, collision_system, std::placeholders::_1, std::placeholders::_2));
  456. systems.push_back(std::bind(&samara_system::update, samara_system, std::placeholders::_1, std::placeholders::_2));
  457. systems.push_back(std::bind(&camera_system::update, camera_system, std::placeholders::_1, std::placeholders::_2));
  458. systems.push_back(std::bind(&behavior_system::update, behavior_system, std::placeholders::_1, std::placeholders::_2));
  459. systems.push_back(std::bind(&locomotion_system::update, locomotion_system, std::placeholders::_1, std::placeholders::_2));
  460. systems.push_back([this](double t, double dt){ this->control_system->update(dt); });
  461. systems.push_back([this](double t, double dt){
  462. this->subterrain_light.set_translation(orbit_cam.get_focal_point());
  463. this->lantern.set_translation(orbit_cam.get_focal_point());
  464. this->spotlight.set_transform(overworld_camera.get_transform());
  465. this->focal_point_tween[1] = orbit_cam.get_focal_point();
  466. });
  467. systems.push_back([this](double t, double dt){ this->ui_system->update(dt); });
  468. systems.push_back(std::bind(&tool_system::update, tool_system, std::placeholders::_1, std::placeholders::_2));
  469. systems.push_back(std::bind(&model_system::update, model_system, std::placeholders::_1, std::placeholders::_2));
  470. systems.push_back([this](double t, double dt){ this->animator->animate(dt); });
  471. systems.push_back([this](double t, double dt){ this->application_controls.update(); this->menu_controls.update(); this->camera_controls->update(); });
  472. // Setup FSM states
  473. loading_state =
  474. {
  475. std::function<void()>(std::bind(enter_loading_state, this)),
  476. std::function<void()>(std::bind(exit_loading_state, this))
  477. };
  478. language_select_state =
  479. {
  480. std::function<void()>(std::bind(enter_language_select_state, this)),
  481. std::function<void()>(std::bind(exit_language_select_state, this))
  482. };
  483. splash_state =
  484. {
  485. std::function<void()>(std::bind(enter_splash_state, this)),
  486. std::function<void()>(std::bind(exit_splash_state, this))
  487. };
  488. title_state =
  489. {
  490. std::function<void()>(std::bind(enter_title_state, this)),
  491. std::function<void()>(std::bind(exit_title_state, this))
  492. };
  493. play_state =
  494. {
  495. std::function<void()>(std::bind(enter_play_state, this)),
  496. std::function<void()>(std::bind(exit_play_state, this))
  497. };
  498. pause_state =
  499. {
  500. std::function<void()>(std::bind(enter_pause_state, this)),
  501. std::function<void()>(std::bind(exit_pause_state, this))
  502. };
  503. // Setup frame timing
  504. frame_scheduler.set_update_callback(std::bind(&application::update, this, std::placeholders::_1, std::placeholders::_2));
  505. frame_scheduler.set_render_callback(std::bind(&application::render, this, std::placeholders::_1));
  506. frame_scheduler.set_update_rate(60.0);
  507. frame_scheduler.set_max_frame_duration(0.25);
  508. // Setup performance sampling
  509. performance_sampler.set_sample_size(15);
  510. // Setup input event routing
  511. input_event_router.set_event_dispatcher(&event_dispatcher);
  512. input_mapper.set_event_dispatcher(&event_dispatcher);
  513. // Setup input devices
  514. keyboard.set_event_dispatcher(&event_dispatcher);
  515. mouse.set_event_dispatcher(&event_dispatcher);
  516. game_controller.set_event_dispatcher(&event_dispatcher);
  517. // Setup controls
  518. application_controls.add_control(&toggle_fullscreen_control);
  519. application_controls.add_control(&dig_control);
  520. application_controls.add_control(&screenshot_control);
  521. toggle_fullscreen_control.set_activated_callback(std::bind(&application::toggle_fullscreen, this));
  522. screenshot_control.set_activated_callback([this]()
  523. {
  524. take_screenshot();
  525. });
  526. menu_back_control.set_activated_callback(std::bind(&application::close, this, 0));
  527. menu_controls.add_control(&menu_back_control);
  528. menu_controls.add_control(&menu_select_control);
  529. orbit_cam.attach(&overworld_camera);
  530. control_system = new ::control_system();
  531. control_system->set_orbit_cam(&orbit_cam);
  532. control_system->set_viewport(viewport);
  533. event_dispatcher.subscribe<mouse_moved_event>(control_system);
  534. event_dispatcher.subscribe<mouse_moved_event>(camera_system);
  535. event_dispatcher.subscribe<mouse_moved_event>(tool_system);
  536. camera_controls = control_system->get_control_set();
  537. // Application control mappings
  538. input_event_router.add_mapping(key_mapping(&toggle_fullscreen_control, nullptr, scancode::f11));
  539. input_event_router.add_mapping(key_mapping(&screenshot_control, nullptr, scancode::f12));
  540. // Add menu control mappings
  541. input_event_router.add_mapping(key_mapping(&menu_back_control, nullptr, scancode::escape));
  542. input_event_router.add_mapping(key_mapping(&menu_back_control, nullptr, scancode::backspace));
  543. input_event_router.add_mapping(game_controller_button_mapping(&menu_back_control, nullptr, game_controller_button::b));
  544. input_event_router.add_mapping(key_mapping(control_system->get_tool_menu_control(), nullptr, scancode::left_shift));
  545. input_event_router.add_mapping(game_controller_button_mapping(control_system->get_tool_menu_control(), nullptr, game_controller_button::x));
  546. input_event_router.add_mapping(key_mapping(&menu_select_control, nullptr, scancode::enter));
  547. input_event_router.add_mapping(key_mapping(&menu_select_control, nullptr, scancode::space));
  548. input_event_router.add_mapping(key_mapping(control_system->get_move_forward_control(), nullptr, scancode::w));
  549. input_event_router.add_mapping(key_mapping(control_system->get_toggle_view_control(), nullptr, scancode::tab));
  550. control_system->get_toggle_view_control()->set_activated_callback(
  551. [this]()
  552. {
  553. if (this->active_scene == &this->overworld_scene)
  554. {
  555. // Switch to underworld
  556. //this->overworld_camera.set_active(false);
  557. this->underworld_camera.set_active(true);
  558. this->active_scene = &this->underworld_scene;
  559. }
  560. else
  561. {
  562. // Switch to overworld
  563. this->underworld_camera.set_active(false);
  564. this->overworld_camera.set_active(true);
  565. this->active_scene = &this->overworld_scene;
  566. }
  567. });
  568. input_event_router.add_mapping(game_controller_axis_mapping(control_system->get_move_forward_control(), nullptr, game_controller_axis::left_y, true));
  569. input_event_router.add_mapping(key_mapping(control_system->get_move_back_control(), nullptr, scancode::s));
  570. input_event_router.add_mapping(game_controller_axis_mapping(control_system->get_move_back_control(), nullptr, game_controller_axis::left_y, false));
  571. input_event_router.add_mapping(key_mapping(control_system->get_move_left_control(), nullptr, scancode::a));
  572. input_event_router.add_mapping(game_controller_axis_mapping(control_system->get_move_left_control(), nullptr, game_controller_axis::left_x, true));
  573. input_event_router.add_mapping(key_mapping(control_system->get_move_right_control(), nullptr, scancode::d));
  574. input_event_router.add_mapping(game_controller_axis_mapping(control_system->get_move_right_control(), nullptr, game_controller_axis::left_x, false));
  575. input_event_router.add_mapping(game_controller_axis_mapping(control_system->get_rotate_ccw_control(), nullptr, game_controller_axis::right_x, false));
  576. input_event_router.add_mapping(game_controller_axis_mapping(control_system->get_rotate_cw_control(), nullptr, game_controller_axis::right_x, true));
  577. input_event_router.add_mapping(game_controller_axis_mapping(control_system->get_tilt_up_control(), nullptr, game_controller_axis::right_y, false));
  578. input_event_router.add_mapping(game_controller_axis_mapping(control_system->get_tilt_down_control(), nullptr, game_controller_axis::right_y, true));
  579. input_event_router.add_mapping(mouse_wheel_mapping(control_system->get_zoom_in_control(), nullptr, mouse_wheel_axis::positive_y));
  580. input_event_router.add_mapping(mouse_wheel_mapping(control_system->get_zoom_out_control(), nullptr, mouse_wheel_axis::negative_y));
  581. input_event_router.add_mapping(mouse_button_mapping(control_system->get_adjust_camera_control(), nullptr, 3));
  582. input_event_router.add_mapping(game_controller_button_mapping(control_system->get_ascend_control(), nullptr, game_controller_button::y));
  583. input_event_router.add_mapping(game_controller_button_mapping(control_system->get_descend_control(), nullptr, game_controller_button::a));
  584. input_event_router.add_mapping(game_controller_axis_mapping(control_system->get_zoom_out_control(), nullptr, game_controller_axis::trigger_left, false));
  585. input_event_router.add_mapping(game_controller_axis_mapping(control_system->get_zoom_in_control(), nullptr, game_controller_axis::trigger_right, false));
  586. control_system->get_adjust_camera_control()->set_activated_callback([this](){ this->set_relative_mouse_mode(true); this->tool_system->set_pick(false); });
  587. control_system->get_adjust_camera_control()->set_deactivated_callback([this](){ this->set_relative_mouse_mode(false); this->tool_system->set_pick(true); });
  588. input_event_router.add_mapping(key_mapping(&dig_control, nullptr, scancode::one));
  589. dig_control.set_activated_callback(
  590. [this]()
  591. {
  592. const float r = 25.0f;
  593. ecs::cavity_component cavity;
  594. cavity.position =
  595. {
  596. frand(-r, r),
  597. frand(-r * 2, r),
  598. frand(-r, r)
  599. };
  600. cavity.radius = frand(0.75f, 1.25f);
  601. ecs_registry.assign<ecs::cavity_component>(ecs_registry.create(), cavity);
  602. });
  603. pheromones.rows = 256;
  604. pheromones.columns = 256;
  605. pheromones.buffers = new float*[2];
  606. pheromones.buffers[0] = new float[pheromones.rows * pheromones.columns];
  607. pheromones.buffers[1] = new float[pheromones.rows * pheromones.columns];
  608. pheromones.current = 0;
  609. //diffuse(&pheromones);
  610. control_system->set_tool(nullptr);
  611. // Setup UI
  612. ui_system = new ::ui_system(resource_manager);
  613. ui_system->set_viewport(viewport);
  614. ui_system->set_tool_menu_control(control_system->get_tool_menu_control());
  615. event_dispatcher.subscribe<mouse_moved_event>(ui_system);
  616. // Setup UI camera compositor
  617. ui_clear_pass = new ::clear_pass(rasterizer, &rasterizer->get_default_framebuffer());
  618. ui_clear_pass->set_cleared_buffers(false, true, false);
  619. ui_material_pass = new ::material_pass(rasterizer, &rasterizer->get_default_framebuffer(), resource_manager);
  620. ui_material_pass->set_fallback_material(fallback_material);
  621. ui_material_pass->set_time_tween(&time);
  622. ui_compositor.add_pass(ui_clear_pass);
  623. ui_compositor.add_pass(ui_material_pass);
  624. ui_system->get_camera()->set_compositor(&ui_compositor);
  625. // Setup lights
  626. sun_indirect.set_intensity(0.25f);
  627. sun_indirect.update_tweens();
  628. sun_direct.look_at({-1.0f, 5.0f, 1.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f});
  629. sun_direct.set_intensity(1.0f);
  630. sun_direct.update_tweens();
  631. subterrain_light.set_color({1, 1, 1});
  632. subterrain_light.set_intensity(1.0f);
  633. subterrain_light.set_attenuation({1.0f, 0.09f, 0.032f});
  634. subterrain_light.update_tweens();
  635. spotlight.set_color({1, 1, 1});
  636. spotlight.set_intensity(1.0f);
  637. spotlight.set_attenuation({1.0f, 0.09f, 0.032f});
  638. spotlight.set_cutoff({vmq::radians(15.0f), vmq::radians(30.0f)});
  639. spotlight.update_tweens();
  640. spotlight.set_active(false);
  641. underworld_ambient_light.set_color({1, 1, 1});
  642. underworld_ambient_light.set_intensity(0.15f);
  643. underworld_ambient_light.update_tweens();
  644. // Darkness
  645. darkness_volume.set_model(resource_manager->load<model>("darkness-volume.obj"));
  646. lantern.set_model(resource_manager->load<model>("lantern.obj"));
  647. // Cloud
  648. cloud.set_model(resource_manager->load<model>("cloud.obj"));
  649. cloud.set_translation({0, 0, 4500});
  650. cloud.set_scale(float3{1, 1, 1} * 100.0f);
  651. // Create depth debug billboard
  652. /*
  653. material* depth_debug_material = new material();
  654. depth_debug_material->set_shader_program(resource_manager->load<shader_program>("ui-element-textured.glsl"));
  655. depth_debug_material->add_property<const texture_2d*>("background")->set_value(shadow_map_depth_texture);
  656. depth_debug_material->add_property<float4>("tint")->set_value(float4{1, 1, 1, 1});
  657. billboard* depth_debug_billboard = new billboard();
  658. depth_debug_billboard->set_material(depth_debug_material);
  659. depth_debug_billboard->set_scale({128, 128, 1});
  660. depth_debug_billboard->set_translation({-960 + 128, 1080 * 0.5f - 128, 0});
  661. depth_debug_billboard->update_tweens();
  662. ui_system->get_scene()->add_object(depth_debug_billboard);
  663. */
  664. material* billboard_material = new material();
  665. billboard_material->set_shader_program(resource_manager->load<shader_program>("ui-element-textured.glsl"));
  666. billboard_material->add_property<const texture_2d*>("background")->set_value(resource_manager->load<texture_2d>("arrow.png"));
  667. billboard_material->add_property<float4>("tint")->set_value(float4{1, 1, 1, 1});
  668. billboard_material->set_flags(MATERIAL_FLAG_TRANSLUCENT | MATERIAL_FLAG_NOT_SHADOW_CASTER);
  669. billboard* arrow_billboard = new billboard();
  670. arrow_billboard->set_material(billboard_material);
  671. arrow_billboard->set_scale(float3{1, 1, 1} * 2.0f);
  672. arrow_billboard->set_translation({0, 10, 0});
  673. arrow_billboard->set_billboard_type(billboard_type::cylindrical);
  674. arrow_billboard->set_alignment_axis({0, 1, 0});
  675. arrow_billboard->update_tweens();
  676. billboard_material = new material();
  677. billboard_material->set_shader_program(resource_manager->load<shader_program>("portal-card.glsl"));
  678. billboard_material->add_property<float4>("color")->set_value(float4{1, 1, 1, 1});
  679. billboard_material->add_property<float2>("range")->set_value(float2{50.0f, 500.0f});
  680. billboard_material->set_flags(MATERIAL_FLAG_TRANSLUCENT | MATERIAL_FLAG_NOT_SHADOW_CASTER);
  681. billboard* portal_billboard = new billboard();
  682. portal_billboard->set_material(billboard_material);
  683. portal_billboard->set_scale(float3{1, 1, 1} * 10.0f);
  684. portal_billboard->set_translation({0.0f, 0, 0});
  685. portal_billboard->set_billboard_type(billboard_type::spherical);
  686. portal_billboard->set_alignment_axis({0, 1, 0});
  687. portal_billboard->update_tweens();
  688. // Setup overworld scene
  689. overworld_scene.add_object(&overworld_camera);
  690. overworld_scene.add_object(&sun_indirect);
  691. overworld_scene.add_object(&sun_direct);
  692. overworld_scene.add_object(&spotlight);
  693. overworld_scene.add_object(&cloud);
  694. overworld_scene.add_object(arrow_billboard);
  695. // Setup underworld scene
  696. underworld_scene.add_object(&underworld_camera);
  697. underworld_scene.add_object(&underworld_ambient_light);
  698. //underworld_scene.add_object(&darkness_volume);
  699. underworld_scene.add_object(&lantern);
  700. underworld_scene.add_object(&subterrain_light);
  701. underworld_scene.add_object(portal_billboard);
  702. //model_instance* larva = new model_instance(resource_manager->load<model>("larva.obj"));
  703. //underworld_scene.add_object(larva);
  704. model_instance* flashlight = new model_instance(resource_manager->load<model>("flashlight.obj"));
  705. underworld_scene.add_object(flashlight);
  706. // Set overworld as active scene
  707. active_scene = &overworld_scene;
  708. }
  709. application::~application()
  710. {
  711. // Destroy the SDL window
  712. SDL_DestroyWindow(window);
  713. // Shutdown SDL
  714. SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);
  715. SDL_Quit();
  716. }
  717. void application::close(int status)
  718. {
  719. closed = true;
  720. exit_status = status;
  721. }
  722. int application::execute()
  723. {
  724. // Enter inital state
  725. state_machine.change_state(splash_state);
  726. // Perform initial update
  727. update(0.0, 0.0);
  728. // Reset frame scheduler
  729. frame_scheduler.reset();
  730. // Reset time tween
  731. time[0] = time[1] = 0.0;
  732. // Schedule frames until closed
  733. while (!closed)
  734. {
  735. // Tick frame scheduler
  736. frame_scheduler.tick();
  737. // Sample frame duration
  738. performance_sampler.sample(frame_scheduler.get_frame_duration());
  739. }
  740. // Exit current state
  741. state_machine.change_state({nullptr, nullptr});
  742. return exit_status;
  743. }
  744. void application::update(double t, double dt)
  745. {
  746. // Update time tween
  747. time.update();
  748. time[1] = t;
  749. // Sequentially process systems
  750. for (const auto& system: systems)
  751. {
  752. system(t, dt);
  753. }
  754. }
  755. void application::render(double alpha)
  756. {
  757. /*
  758. std::cout << std::fixed;
  759. std::cout << std::setprecision(2);
  760. std::cout << performance_sampler.mean_frame_duration() * 1000.0 << std::endl;
  761. */
  762. renderer.render(alpha, overworld_scene);
  763. renderer.render(alpha, underworld_scene);
  764. //renderer.render(alpha, *active_scene);
  765. renderer.render(alpha, *ui_system->get_scene());
  766. SDL_GL_SwapWindow(window);
  767. }
  768. void application::translate_sdl_events()
  769. {
  770. SDL_Event sdl_event;
  771. while (SDL_PollEvent(&sdl_event))
  772. {
  773. switch (sdl_event.type)
  774. {
  775. case SDL_KEYDOWN:
  776. case SDL_KEYUP:
  777. {
  778. if (sdl_event.key.repeat == 0)
  779. {
  780. scancode scancode = scancode::unknown;
  781. if (sdl_event.key.keysym.scancode <= SDL_SCANCODE_APP2)
  782. {
  783. scancode = sdl_scancode_table[sdl_event.key.keysym.scancode];
  784. }
  785. if (sdl_event.type == SDL_KEYDOWN)
  786. keyboard.press(scancode);
  787. else
  788. keyboard.release(scancode);
  789. }
  790. break;
  791. }
  792. case SDL_MOUSEMOTION:
  793. {
  794. mouse.move(sdl_event.motion.x, sdl_event.motion.y, sdl_event.motion.xrel, sdl_event.motion.yrel);
  795. break;
  796. }
  797. case SDL_MOUSEBUTTONDOWN:
  798. {
  799. mouse.press(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  800. break;
  801. }
  802. case SDL_MOUSEBUTTONUP:
  803. {
  804. mouse.release(sdl_event.button.button, sdl_event.button.x, sdl_event.button.y);
  805. break;
  806. }
  807. case SDL_MOUSEWHEEL:
  808. {
  809. int direction = (sdl_event.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1 : 1;
  810. mouse.scroll(sdl_event.wheel.x * direction, sdl_event.wheel.y * direction);
  811. break;
  812. }
  813. case SDL_CONTROLLERBUTTONDOWN:
  814. {
  815. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  816. {
  817. game_controller_button button = sdl_button_table[sdl_event.cbutton.button];
  818. game_controller.press(button);
  819. }
  820. break;
  821. }
  822. case SDL_CONTROLLERBUTTONUP:
  823. {
  824. if (sdl_event.cbutton.button != SDL_CONTROLLER_BUTTON_INVALID)
  825. {
  826. game_controller_button button = sdl_button_table[sdl_event.cbutton.button];
  827. game_controller.release(button);
  828. }
  829. break;
  830. }
  831. case SDL_CONTROLLERAXISMOTION:
  832. {
  833. if (sdl_event.caxis.axis != SDL_CONTROLLER_AXIS_INVALID)
  834. {
  835. game_controller_axis axis = sdl_axis_table[sdl_event.caxis.axis];
  836. float value = sdl_event.caxis.value;
  837. value /= (value < 0.0f) ? 32768.0f : 32767.0f;
  838. game_controller.move(axis, value);
  839. }
  840. break;
  841. }
  842. case SDL_CONTROLLERDEVICEADDED:
  843. {
  844. if (SDL_IsGameController(sdl_event.cdevice.which))
  845. {
  846. SDL_GameController* sdl_controller = SDL_GameControllerOpen(sdl_event.cdevice.which);
  847. std::string controller_name = SDL_GameControllerNameForIndex(sdl_event.cdevice.which);
  848. if (sdl_controller)
  849. {
  850. logger.log("Connected game controller \"" + controller_name + "\"\n");
  851. }
  852. else
  853. {
  854. logger.error("Failed to connected game controller \"" + controller_name + "\"\n");
  855. }
  856. }
  857. break;
  858. }
  859. case SDL_CONTROLLERDEVICEREMOVED:
  860. {
  861. SDL_GameController* sdl_controller = SDL_GameControllerFromInstanceID(sdl_event.cdevice.which);
  862. if (sdl_controller)
  863. {
  864. SDL_GameControllerClose(sdl_controller);
  865. logger.log("Disconnected game controller\n");
  866. }
  867. break;
  868. }
  869. case SDL_WINDOWEVENT:
  870. {
  871. if (sdl_event.window.event == SDL_WINDOWEVENT_RESIZED)
  872. {
  873. window_resized();
  874. }
  875. break;
  876. }
  877. case SDL_QUIT:
  878. {
  879. close(EXIT_SUCCESS);
  880. break;
  881. }
  882. }
  883. }
  884. }
  885. void application::set_relative_mouse_mode(bool enabled)
  886. {
  887. if (enabled)
  888. {
  889. SDL_GetMouseState(&std::get<0>(saved_mouse_position), &std::get<1>(saved_mouse_position));
  890. }
  891. SDL_SetRelativeMouseMode((enabled) ? SDL_TRUE : SDL_FALSE);
  892. if (!enabled)
  893. {
  894. SDL_WarpMouseInWindow(window, std::get<0>(saved_mouse_position), std::get<1>(saved_mouse_position));
  895. }
  896. }
  897. void application::toggle_fullscreen()
  898. {
  899. fullscreen = !fullscreen;
  900. if (fullscreen)
  901. {
  902. SDL_GetWindowSize(window, &std::get<0>(window_dimensions), &std::get<1>(window_dimensions));
  903. SDL_GetWindowPosition(window, &std::get<0>(window_position), &std::get<1>(window_position));
  904. SDL_SetWindowBordered(window, SDL_FALSE);
  905. SDL_SetWindowResizable(window, SDL_FALSE);
  906. SDL_SetWindowPosition(window, 0, 0);
  907. SDL_SetWindowSize(window, std::get<0>(display_dimensions), std::get<1>(display_dimensions));
  908. }
  909. else
  910. {
  911. SDL_SetWindowBordered(window, SDL_TRUE);
  912. SDL_SetWindowResizable(window, SDL_TRUE);
  913. SDL_SetWindowSize(window, std::get<0>(window_dimensions), std::get<1>(window_dimensions));
  914. SDL_SetWindowPosition(window, std::get<0>(window_position), std::get<1>(window_position));
  915. }
  916. window_resized();
  917. }
  918. void application::window_resized()
  919. {
  920. int width, height;
  921. SDL_GetWindowSize(window, &width, &height);
  922. float aspect_ratio = static_cast<float>(width) / static_cast<float>(height);
  923. viewport = {0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height)};
  924. rasterizer->window_resized(width, height);
  925. overworld_camera.set_perspective(overworld_camera.get_fov(), aspect_ratio, overworld_camera.get_clip_near(), overworld_camera.get_clip_far());
  926. underworld_camera.set_perspective(underworld_camera.get_fov(), aspect_ratio, underworld_camera.get_clip_near(), underworld_camera.get_clip_far());
  927. control_system->set_viewport(viewport);
  928. camera_system->set_viewport(viewport);
  929. tool_system->set_viewport(viewport);
  930. ui_system->set_viewport(viewport);
  931. }
  932. void application::take_screenshot() const
  933. {
  934. int x = viewport[0];
  935. int y = viewport[1];
  936. int w = viewport[2];
  937. int h = viewport[3];
  938. // Read pixel data from framebuffer
  939. unsigned char* pixels = new unsigned char[w * h * 3];
  940. glReadBuffer(GL_BACK);
  941. glReadPixels(x, y, w, h, GL_RGB, GL_UNSIGNED_BYTE, pixels);
  942. std::string filename = screenshots_path + "antkeeper-" + timestamp() + ".png";
  943. std::thread screenshot_thread(application::save_image, filename, w, h, pixels);
  944. screenshot_thread.detach();
  945. }
  946. void application::save_image(const std::string& filename, int w, int h, const unsigned char* pixels)
  947. {
  948. stbi_flip_vertically_on_write(1);
  949. stbi_write_png(filename.c_str(), w, h, 3, pixels, w * 3);
  950. delete[] pixels;
  951. }