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

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