├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md └── src ├── async_renderer.hpp ├── config.hpp ├── main.cpp └── utils ├── binary_io.hpp ├── event_manager.hpp ├── number_generator.hpp ├── palette.hpp ├── thread_pool.hpp ├── to_string.hpp └── va_grid.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-*/ 2 | .idea/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(Julia VERSION 1.0.0 LANGUAGES CXX) 3 | 4 | # Default to release builds 5 | if(NOT CMAKE_BUILD_TYPE) 6 | set(CMAKE_BUILD_TYPE Release) 7 | endif() 8 | 9 | find_package(SFML 2 REQUIRED COMPONENTS graphics system window) 10 | find_package(Threads REQUIRED) 11 | 12 | set(SFML_LIBS sfml-graphics sfml-system sfml-window) 13 | 14 | file(GLOB SOURCES src/*.cpp) 15 | add_executable(${PROJECT_NAME} ${WIN32_GUI} ${SOURCES}) 16 | target_include_directories(${PROJECT_NAME} PRIVATE "include" "lib") 17 | set(SFML_LIBS sfml-system sfml-window sfml-graphics) 18 | target_link_libraries(${PROJECT_NAME} ${SFML_LIBS}) 19 | set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 17) 20 | if (UNIX) 21 | target_link_libraries(${PROJECT_NAME} pthread) 22 | endif (UNIX) 23 | 24 | if(MSVC) 25 | foreach(lib ${SFML_LIBS}) 26 | get_target_property(lib_path ${lib} LOCATION) 27 | file(COPY ${lib_path} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) 28 | endforeach() 29 | endif() 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Jean Tampon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Julia Rendering -------------------------------------------------------------------------------- /src/async_renderer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "utils/va_grid.hpp" 8 | #include "utils/palette.hpp" 9 | #include "utils/thread_pool.hpp" 10 | #include "utils/to_string.hpp" 11 | #include "utils/number_generator.hpp" 12 | 13 | #include "config.hpp" 14 | 15 | 16 | template 17 | float julia_iter(sf::Vector2 pos) 18 | { 19 | uint32_t i{0}; 20 | TFloatType zr{pos.x}; 21 | TFloatType zi{pos.y}; 22 | TFloatType mod = zr * zr + zi * zi; 23 | while (mod < TFloatType{4.0} && i < Config::max_iteration) { 24 | const TFloatType tmp = zr; 25 | zr = zr * zr - zi * zi + Config::julia_r; 26 | zi = TFloatType{2.0} * zi * tmp + Config::julia_i; 27 | mod = zr * zr + zi * zi; 28 | ++i; 29 | } 30 | return static_cast(i) - static_cast(log2(std::max(TFloatType(1.0), log2(mod)))); 31 | } 32 | 33 | template 34 | struct RenderState 35 | { 36 | VertexArrayGrid grid; 37 | TFloatType zoom = 1.0; 38 | sf::Vector2 offset = {0.0, 0.0}; 39 | 40 | RenderState(uint32_t width, uint32_t height) 41 | : grid(width, height) 42 | {} 43 | }; 44 | 45 | template 46 | struct AsyncRenderer 47 | { 48 | RenderState states[2]; 49 | 50 | TFloatType requested_zoom = 1.0; 51 | sf::Vector2 requested_center = {}; 52 | 53 | TFloatType render_zoom = 1.0; 54 | sf::Vector2 render_center = {}; 55 | 56 | uint32_t state_idx = 0; 57 | uint32_t texture_idx = 0; 58 | 59 | std::array, 32> anti_aliasing_offsets; 60 | sf::RenderTexture textures[2]; 61 | 62 | tp::ThreadPool thread_pool; 63 | 64 | Palette palette; 65 | 66 | float fade_time = Config::fade_time; 67 | 68 | AsyncRenderer(uint32_t width, uint32_t height, TFloatType zoom_) 69 | : thread_pool{16} 70 | , states{RenderState(width, height), RenderState(width, height)} 71 | { 72 | requested_zoom = zoom_; 73 | palette.addColorPoint(0.0f , sf::Color{25, 24, 23}); 74 | palette.addColorPoint(0.03f, sf::Color{120, 90, 70}); 75 | palette.addColorPoint(0.05f, sf::Color{130, 24, 23}); 76 | palette.addColorPoint(0.25f, sf::Color{250, 179, 100}); 77 | palette.addColorPoint(0.5f , sf::Color{43, 65, 98}); 78 | palette.addColorPoint(0.85f, sf::Color{11, 110, 79}); 79 | palette.addColorPoint(0.95f, sf::Color{150, 110, 79}); 80 | palette.addColorPoint(1.0f , sf::Color{255, 255, 255}); 81 | 82 | // Precompute Monte Carlo offsets 83 | for (auto& o : anti_aliasing_offsets) { 84 | o.x = RNGf::getUnder(1.0f); 85 | o.y = RNGf::getUnder(1.0f); 86 | } 87 | // If only one sample is used, no offset 88 | anti_aliasing_offsets[0] = {}; 89 | 90 | for (uint32_t i{2}; i--;) { 91 | textures[i].create(width, height); 92 | textures[i].setSmooth(true); 93 | } 94 | } 95 | 96 | ~AsyncRenderer() 97 | { 98 | thread_pool.waitForCompletion(); 99 | } 100 | 101 | void generate() 102 | { 103 | // Get the back buffer 104 | RenderState& state = states[!state_idx]; 105 | VertexArrayGrid& grid = state.grid; 106 | render_zoom = requested_zoom; 107 | render_center = requested_center; 108 | 109 | // Generate colors 110 | const sf::Vector2 win_center = static_cast(0.5) * sf::Vector2{static_cast(grid.size.x), 111 | static_cast(grid.size.y)}; 112 | const uint32_t slice_height = grid.size.y / thread_pool.m_thread_count; 113 | thread_pool.m_queue.m_remaining_tasks = thread_pool.m_thread_count; 114 | for (uint32_t k{0}; k < thread_pool.m_thread_count; ++k) { 115 | thread_pool.addTaskNoIncrement([=] { 116 | constexpr float sample_coef = 1.0f / static_cast(Config::samples_count); 117 | auto& grid = states[!state_idx].grid; 118 | for (uint32_t x{0}; x < grid.size.x; ++x) { 119 | for (uint32_t y{k * slice_height}; y < (k+1) * slice_height; ++y) { 120 | const TFloatType xf = (static_cast(x) - win_center.x) / render_zoom + render_center.x; 121 | const TFloatType yf = (static_cast(y) - win_center.y) / render_zoom + render_center.y; 122 | // AA color accumulator 123 | sf::Vector3f color_vec; 124 | // Monte Carlo color integration 125 | for (uint32_t i{Config::samples_count}; i--;) { 126 | const sf::Vector2 off = anti_aliasing_offsets[i] / render_zoom; 127 | const float iter = julia_iter({xf + off.x, yf + off.y}); 128 | const float iter_ratio = iter / static_cast(Config::max_iteration); 129 | color_vec += palette.getColorVec(iter_ratio); 130 | } 131 | // Update color 132 | grid.setCellColor(x, y, Palette::vec3ToColor(color_vec * sample_coef)); 133 | } 134 | } 135 | }); 136 | } 137 | } 138 | 139 | void render(TFloatType zoom, sf::Vector2 center, sf::RenderTarget& target) 140 | { 141 | // Check if background rendering is ready 142 | if (fade_time >= Config::fade_time && thread_pool.isDone()) 143 | { 144 | // Update state 145 | auto& state = states[!state_idx]; 146 | state.zoom = render_zoom; 147 | state.offset = render_center; 148 | // Swap buffers 149 | state_idx = !state_idx; 150 | // Update texture 151 | textures[!texture_idx].draw(states[state_idx].grid.va); 152 | textures[!texture_idx].display(); 153 | texture_idx = !texture_idx; 154 | fade_time = 0.0f; 155 | // Start next render 156 | generate(); 157 | } 158 | 159 | requested_center = center; 160 | requested_zoom = zoom; 161 | const auto scale = getStateScale(state_idx); 162 | sf::Sprite sprite_new(textures[texture_idx].getTexture()); 163 | const auto bounds = sprite_new.getGlobalBounds(); 164 | const sf::Vector2f origin = sf::Vector2f{bounds.width, bounds.height} * 0.5f; 165 | sprite_new.setOrigin(origin); 166 | sprite_new.setPosition(origin + getStateOffset(state_idx)); 167 | sprite_new.setScale(scale, scale); 168 | 169 | const auto scale_old = getStateScale(!state_idx); 170 | sf::Sprite sprite_old(textures[!texture_idx].getTexture()); 171 | sprite_old.setOrigin(origin); 172 | sprite_old.setPosition(origin + getStateOffset(!state_idx)); 173 | sprite_old.setScale(scale_old, scale_old); 174 | const auto alpha = static_cast(std::max(0.0f, 1.0f - fade_time / Config::fade_time) * 255.0f); 175 | sprite_old.setColor(sf::Color{255, 255, 255, alpha}); 176 | 177 | target.draw(sprite_new); 178 | target.draw(sprite_old); 179 | 180 | fade_time += 0.016f; 181 | } 182 | 183 | float getStateScale(uint32_t idx) const 184 | { 185 | return static_cast(requested_zoom / states[idx].zoom); 186 | } 187 | 188 | sf::Vector2 getStateOffset(uint32_t idx) const 189 | { 190 | return static_cast((states[idx].offset - requested_center) * states[idx].zoom) * getStateScale(idx); 191 | } 192 | }; -------------------------------------------------------------------------------- /src/config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct Config 5 | { 6 | using FloatType = double; 7 | 8 | static constexpr uint32_t samples_count = 16; 9 | static constexpr FloatType julia_r = static_cast(-0.8); 10 | static constexpr FloatType julia_i = static_cast(0.156); 11 | static constexpr float fade_time = 2.0f; 12 | static constexpr uint32_t max_iteration = 1000; 13 | }; -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "utils/event_manager.hpp" 4 | #include "utils/binary_io.hpp" 5 | #include "async_renderer.hpp" 6 | #include "config.hpp" 7 | 8 | int main() 9 | { 10 | const sf::Vector2u window_size{2560, 1440}; 11 | sf::RenderWindow window(sf::VideoMode(window_size.x, window_size.y), "Fractal", sf::Style::Fullscreen); 12 | window.setFramerateLimit(60); 13 | window.setMouseCursorVisible(false); 14 | window.setKeyRepeatEnabled(false); 15 | 16 | sfev::EventManager event_manager{window, true}; 17 | event_manager.addKeyReleasedCallback(sf::Keyboard::Escape, [&window](sfev::CstEv) { 18 | window.close(); 19 | }); 20 | event_manager.addEventCallback(sf::Event::Closed, [&window](sfev::CstEv) { 21 | window.close(); 22 | }); 23 | 24 | const auto zoom_factor = static_cast(1.005); 25 | const auto speed = static_cast(1.0); 26 | auto zoom = static_cast(window.getSize().y) / 2; 27 | sf::Vector2 center {0.0, 0.0}; 28 | 29 | AsyncRenderer renderer{window_size.x, window_size.y, zoom}; 30 | 31 | bool zoom_in = false; 32 | bool zoom_out = false; 33 | bool left = false; 34 | bool right = false; 35 | bool up = false; 36 | bool down = false; 37 | 38 | event_manager.addKeyPressedCallback(sf::Keyboard::A, [&](sfev::CstEv) { zoom_in = true; }); 39 | event_manager.addKeyPressedCallback(sf::Keyboard::E, [&](sfev::CstEv) { zoom_out = true; }); 40 | event_manager.addKeyReleasedCallback(sf::Keyboard::A, [&](sfev::CstEv) { zoom_in = false; }); 41 | event_manager.addKeyReleasedCallback(sf::Keyboard::E, [&](sfev::CstEv) { zoom_out = false; }); 42 | event_manager.addKeyPressedCallback(sf::Keyboard::Left, [&](sfev::CstEv) { left = true; }); 43 | event_manager.addKeyPressedCallback(sf::Keyboard::Right, [&](sfev::CstEv) { right = true; }); 44 | event_manager.addKeyPressedCallback(sf::Keyboard::Up, [&](sfev::CstEv) { up = true; }); 45 | event_manager.addKeyPressedCallback(sf::Keyboard::Down, [&](sfev::CstEv) { down = true; }); 46 | event_manager.addKeyReleasedCallback(sf::Keyboard::Left, [&](sfev::CstEv) { left = false; }); 47 | event_manager.addKeyReleasedCallback(sf::Keyboard::Right, [&](sfev::CstEv) { right = false; }); 48 | event_manager.addKeyReleasedCallback(sf::Keyboard::Up, [&](sfev::CstEv) { up = false; }); 49 | event_manager.addKeyReleasedCallback(sf::Keyboard::Down, [&](sfev::CstEv) { down = false; }); 50 | 51 | event_manager.addKeyReleasedCallback(sf::Keyboard::W, [&](sfev::CstEv) { 52 | BinaryWriter writer{"center.bin"}; 53 | writer.write(center); 54 | }); 55 | event_manager.addKeyReleasedCallback(sf::Keyboard::R, [&](sfev::CstEv) { 56 | BinaryReader reader{"center.bin"}; 57 | reader.readInto(center); 58 | }); 59 | 60 | BinaryReader reader{"center.bin"}; 61 | if (reader) { 62 | reader.readInto(center); 63 | } 64 | 65 | while (window.isOpen()) 66 | { 67 | event_manager.processEvents(); 68 | 69 | const Config::FloatType offset = speed / zoom; 70 | zoom = zoom_in ? zoom * zoom_factor : (zoom_out ? zoom / zoom_factor : zoom); 71 | // Auto zoom 72 | //zoom *= 1.0012; 73 | center.x += left ? -offset : (right ? offset : Config::FloatType{}); 74 | center.y += up ? -offset : (down ? offset : Config::FloatType{}); 75 | 76 | window.clear(sf::Color::Black); 77 | renderer.render(zoom, center, window); 78 | window.display(); 79 | } 80 | 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /src/utils/binary_io.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | 6 | struct BinaryWriter 7 | { 8 | const std::string filename; 9 | std::ofstream outfile; 10 | 11 | explicit 12 | BinaryWriter(const std::string& filename_) 13 | : filename{filename_} 14 | , outfile{filename, std::istream::out | std::ios::binary} 15 | {} 16 | 17 | ~BinaryWriter() 18 | { 19 | if (outfile) 20 | { 21 | outfile.close(); 22 | } 23 | } 24 | 25 | template 26 | void write(const TValue& value) 27 | { 28 | outfile.write(reinterpret_cast(&value), sizeof(value)); 29 | } 30 | }; 31 | 32 | struct BinaryReader 33 | { 34 | const std::string filename; 35 | std::ifstream infile; 36 | 37 | explicit 38 | BinaryReader(const std::string& filename_) 39 | : filename{filename_} 40 | , infile{filename, std::ios_base::binary} 41 | {} 42 | 43 | ~BinaryReader() 44 | { 45 | if (infile) 46 | { 47 | infile.close(); 48 | } 49 | } 50 | 51 | operator bool() const 52 | { 53 | return infile.good(); 54 | } 55 | 56 | template 57 | TValue read() 58 | { 59 | TValue result = {}; 60 | infile.read(reinterpret_cast(&result), sizeof(TValue)); 61 | return result; 62 | } 63 | 64 | template 65 | void readInto(TValue& value) 66 | { 67 | infile.read(reinterpret_cast(&value), sizeof(TValue)); 68 | } 69 | }; -------------------------------------------------------------------------------- /src/utils/event_manager.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace sfev 8 | { 9 | 10 | // Helper using for shorter types 11 | using EventCallback = std::function; 12 | 13 | template 14 | using EventCallbackMap = std::unordered_map; 15 | 16 | using CstEv = const sf::Event&; 17 | 18 | 19 | /* 20 | This class handles subtyped events like keyboard or mouse events 21 | The unpack function allows to get relevant information from the processed event 22 | */ 23 | template 24 | class SubTypeManager 25 | { 26 | public: 27 | SubTypeManager(std::function unpack) : 28 | m_unpack(unpack) 29 | {} 30 | 31 | ~SubTypeManager() = default; 32 | 33 | void processEvent(const sf::Event& event) const 34 | { 35 | T sub_value = m_unpack(event); 36 | auto it(m_callmap.find(sub_value)); 37 | if (it != m_callmap.end()) { 38 | // Call its associated callback 39 | (it->second)(event); 40 | } 41 | } 42 | 43 | void addCallback(const T& sub_value, EventCallback callback) 44 | { 45 | m_callmap[sub_value] = callback; 46 | } 47 | 48 | private: 49 | EventCallbackMap m_callmap; 50 | std::function m_unpack; 51 | }; 52 | 53 | 54 | class EventMap 55 | { 56 | public: 57 | EventMap(bool use_builtin_helpers = true) 58 | : m_key_pressed_manager([](const sf::Event& event) {return event.key.code; }) 59 | , m_key_released_manager([](const sf::Event& event) {return event.key.code; }) 60 | , m_mouse_pressed_manager([](const sf::Event& event) {return event.mouseButton.button; }) 61 | , m_mouse_released_manager([](const sf::Event& event) {return event.mouseButton.button; }) 62 | { 63 | if (use_builtin_helpers) { 64 | // Register key events built in callbacks 65 | this->addEventCallback(sf::Event::EventType::KeyPressed, [&](const sf::Event& event) {m_key_pressed_manager.processEvent(event); }); 66 | this->addEventCallback(sf::Event::EventType::KeyReleased, [&](const sf::Event& event) {m_key_released_manager.processEvent(event); }); 67 | this->addEventCallback(sf::Event::EventType::MouseButtonPressed, [&](const sf::Event& event) {m_mouse_pressed_manager.processEvent(event); }); 68 | this->addEventCallback(sf::Event::EventType::MouseButtonReleased, [&](const sf::Event& event) {m_mouse_released_manager.processEvent(event); }); 69 | } 70 | } 71 | 72 | // Attaches new callback to an event 73 | void addEventCallback(sf::Event::EventType type, EventCallback callback) 74 | { 75 | m_events_callmap[type] = callback; 76 | } 77 | 78 | // Adds a key pressed callback 79 | void addKeyPressedCallback(sf::Keyboard::Key key_code, EventCallback callback) 80 | { 81 | m_key_pressed_manager.addCallback(key_code, callback); 82 | } 83 | 84 | // Adds a key released callback 85 | void addKeyReleasedCallback(sf::Keyboard::Key key_code, EventCallback callback) 86 | { 87 | m_key_released_manager.addCallback(key_code, callback); 88 | } 89 | 90 | // Adds a mouse pressed callback 91 | void addMousePressedCallback(sf::Mouse::Button button, EventCallback callback) 92 | { 93 | m_mouse_pressed_manager.addCallback(button, callback); 94 | } 95 | 96 | // Adds a mouse released callback 97 | void addMouseReleasedCallback(sf::Mouse::Button button, EventCallback callback) 98 | { 99 | m_mouse_released_manager.addCallback(button, callback); 100 | } 101 | 102 | // Runs the callback associated with an event 103 | void executeCallback(const sf::Event& e, EventCallback fallback = nullptr) const 104 | { 105 | auto it(m_events_callmap.find(e.type)); 106 | if (it != m_events_callmap.end()) { 107 | // Call its associated callback 108 | (it->second)(e); 109 | } else if (fallback) { 110 | fallback(e); 111 | } 112 | } 113 | 114 | // Removes a callback 115 | void removeCallback(sf::Event::EventType type) 116 | { 117 | // If event type is registred 118 | auto it(m_events_callmap.find(type)); 119 | if (it != m_events_callmap.end()) { 120 | // Remove its associated callback 121 | m_events_callmap.erase(it); 122 | } 123 | } 124 | 125 | private: 126 | SubTypeManager m_key_pressed_manager; 127 | SubTypeManager m_key_released_manager; 128 | SubTypeManager m_mouse_pressed_manager; 129 | SubTypeManager m_mouse_released_manager; 130 | EventCallbackMap m_events_callmap; 131 | }; 132 | 133 | 134 | /* 135 | This class handles any type of event and call its associated callbacks if any. 136 | To process key event in a more convenient way its using a KeyManager 137 | */ 138 | class EventManager 139 | { 140 | public: 141 | EventManager(sf::Window& window, bool use_builtin_helpers) : 142 | m_window(window), 143 | m_event_map(use_builtin_helpers) 144 | { 145 | } 146 | 147 | // Calls events' attached callbacks 148 | void processEvents(EventCallback fallback = nullptr) const 149 | { 150 | // Iterate over events 151 | sf::Event event; 152 | while (m_window.pollEvent(event)) { 153 | m_event_map.executeCallback(event, fallback); 154 | } 155 | } 156 | 157 | // Attaches new callback to an event 158 | void addEventCallback(sf::Event::EventType type, EventCallback callback) 159 | { 160 | m_event_map.addEventCallback(type, callback); 161 | } 162 | 163 | // Removes a callback 164 | void removeCallback(sf::Event::EventType type) 165 | { 166 | m_event_map.removeCallback(type); 167 | } 168 | 169 | // Adds a key pressed callback 170 | void addKeyPressedCallback(sf::Keyboard::Key key, EventCallback callback) 171 | { 172 | m_event_map.addKeyPressedCallback(key, callback); 173 | } 174 | 175 | // Adds a key released callback 176 | void addKeyReleasedCallback(sf::Keyboard::Key key, EventCallback callback) 177 | { 178 | m_event_map.addKeyReleasedCallback(key, callback); 179 | } 180 | 181 | // Adds a mouse pressed callback 182 | void addMousePressedCallback(sf::Mouse::Button button, EventCallback callback) 183 | { 184 | m_event_map.addMousePressedCallback(button, callback); 185 | } 186 | 187 | // Adds a mouse released callback 188 | void addMouseReleasedCallback(sf::Mouse::Button button, EventCallback callback) 189 | { 190 | m_event_map.addMouseReleasedCallback(button, callback); 191 | } 192 | 193 | sf::Window& getWindow() 194 | { 195 | return m_window; 196 | } 197 | 198 | sf::Vector2f getFloatMousePosition() const 199 | { 200 | const sf::Vector2i mouse_position = sf::Mouse::getPosition(m_window); 201 | return { static_cast(mouse_position.x), static_cast(mouse_position.y) }; 202 | } 203 | 204 | sf::Vector2i getMousePosition() const 205 | { 206 | return sf::Mouse::getPosition(m_window); 207 | } 208 | 209 | private: 210 | sf::Window& m_window; 211 | EventMap m_event_map; 212 | }; 213 | 214 | } // End namespace 215 | -------------------------------------------------------------------------------- /src/utils/number_generator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | class NumberGenerator 6 | { 7 | protected: 8 | std::random_device rd; 9 | std::mt19937 gen; 10 | 11 | NumberGenerator() 12 | : gen(1) 13 | {} 14 | }; 15 | 16 | 17 | template 18 | class RealNumberGenerator : public NumberGenerator 19 | { 20 | private: 21 | std::uniform_real_distribution dis; 22 | 23 | public: 24 | RealNumberGenerator() 25 | : NumberGenerator() 26 | , dis(0.0f, 1.0f) 27 | {} 28 | 29 | // random_device is not copyable 30 | RealNumberGenerator(const RealNumberGenerator& right) 31 | : NumberGenerator() 32 | , dis(right.dis) 33 | {} 34 | 35 | float get() 36 | { 37 | return dis(gen); 38 | } 39 | 40 | float getUnder(T max) 41 | { 42 | return get() * max; 43 | } 44 | 45 | float getRange(T min, T max) 46 | { 47 | return min + get() * (max - min); 48 | } 49 | 50 | float getRange(T width) 51 | { 52 | return getRange(-width * 0.5f, width * 0.5f); 53 | } 54 | }; 55 | 56 | 57 | template 58 | class RNG 59 | { 60 | private: 61 | static RealNumberGenerator gen; 62 | 63 | public: 64 | static T get() 65 | { 66 | return gen.get(); 67 | } 68 | 69 | static float getUnder(T max) 70 | { 71 | return gen.getUnder(max); 72 | } 73 | 74 | static uint64_t getUintUnder(uint64_t max) 75 | { 76 | return static_cast(gen.getUnder(static_cast(max) + 1.0f)); 77 | } 78 | 79 | static float getRange(T min, T max) 80 | { 81 | return gen.getRange(min, max); 82 | } 83 | 84 | static float getRange(T width) 85 | { 86 | return gen.getRange(width); 87 | } 88 | 89 | static float getFullRange(T width) 90 | { 91 | return gen.getRange(static_cast(2.0f) * width); 92 | } 93 | 94 | static bool proba(float threshold) 95 | { 96 | return get() < threshold; 97 | } 98 | }; 99 | 100 | using RNGf = RNG; 101 | 102 | template 103 | RealNumberGenerator RNG::gen = RealNumberGenerator(); 104 | 105 | 106 | template 107 | class IntegerNumberGenerator : public NumberGenerator 108 | { 109 | public: 110 | IntegerNumberGenerator() 111 | : NumberGenerator() 112 | {} 113 | 114 | // random_device is not copyable 115 | IntegerNumberGenerator(const IntegerNumberGenerator& right) 116 | : NumberGenerator() 117 | {} 118 | 119 | T getUnder(T max) 120 | { 121 | std::uniform_int_distribution dist(0, max); 122 | return dist(gen); 123 | } 124 | 125 | T getRange(T min, T max) 126 | { 127 | std::uniform_int_distribution dist(min, max); 128 | return dist(gen); 129 | } 130 | }; 131 | 132 | 133 | template 134 | class RNGi 135 | { 136 | private: 137 | static IntegerNumberGenerator gen; 138 | 139 | public: 140 | static T getUnder(T max) 141 | { 142 | return gen.getUnder(max); 143 | } 144 | 145 | static T getRange(T min, T max) 146 | { 147 | return gen.getRange(min, max); 148 | } 149 | }; 150 | 151 | template 152 | IntegerNumberGenerator RNGi::gen; 153 | 154 | using RNGi32 = RNGi; 155 | using RNGi64 = RNGi; 156 | using RNGu32 = RNGi; 157 | using RNGu64 = RNGi; 158 | -------------------------------------------------------------------------------- /src/utils/palette.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct Palette 5 | { 6 | struct ColorPoint 7 | { 8 | float value; 9 | sf::Vector3f rgb; 10 | }; 11 | 12 | std::vector points; 13 | 14 | void addColorPoint(float value, sf::Color color) 15 | { 16 | points.push_back({value, {static_cast(color.r), static_cast(color.g), static_cast(color.b)}}); 17 | } 18 | 19 | sf::Vector3f getColorVec(float value) 20 | { 21 | if (value >= 1.0f) { 22 | return points.back().rgb; 23 | } else if (value <= 0.0f) { 24 | return points.front().rgb; 25 | } 26 | uint32_t i{0}; 27 | for (const auto& cp : points) { 28 | if (cp.value > value) { 29 | const float range = cp.value - points[i-1].value; 30 | const float pos = value - points[i-1].value; 31 | const float ratio = pos / range; 32 | return (1.0f - ratio) * points[i-1].rgb + ratio * cp.rgb; 33 | } 34 | ++i; 35 | } 36 | return {255.0f, 255.0f, 255.0f}; 37 | } 38 | 39 | sf::Color getColor(float value) 40 | { 41 | return vec3ToColor(getColorVec(value)); 42 | } 43 | 44 | static sf::Color vec3ToColor(sf::Vector3f v) 45 | { 46 | return { 47 | static_cast(std::min(std::max(0.0f, v.x), 255.0f)), 48 | static_cast(std::min(std::max(0.0f, v.y), 255.0f)), 49 | static_cast(std::min(std::max(0.0f, v.z), 255.0f)), 50 | }; 51 | } 52 | }; -------------------------------------------------------------------------------- /src/utils/thread_pool.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | namespace tp 11 | { 12 | 13 | struct TaskQueue 14 | { 15 | std::queue> m_tasks; 16 | std::mutex m_mutex; 17 | std::atomic m_remaining_tasks = 0; 18 | 19 | template 20 | void addTask(TCallback&& callback) 21 | { 22 | addTaskNoIncrement(std::forward(callback)); 23 | m_remaining_tasks++; 24 | } 25 | 26 | template 27 | void addTaskNoIncrement(TCallback&& callback) 28 | { 29 | std::lock_guard lock_guard{m_mutex}; 30 | m_tasks.push(std::forward(callback)); 31 | } 32 | 33 | void getTask(std::function& target_callback) 34 | { 35 | { 36 | std::lock_guard lock_guard{m_mutex}; 37 | if (m_tasks.empty()) { 38 | return; 39 | } 40 | target_callback = std::move(m_tasks.front()); 41 | m_tasks.pop(); 42 | } 43 | } 44 | 45 | static void wait() 46 | { 47 | std::this_thread::yield(); 48 | } 49 | 50 | void waitForCompletion() const 51 | { 52 | while (m_remaining_tasks > 0) { 53 | wait(); 54 | } 55 | } 56 | 57 | void workDone() 58 | { 59 | m_remaining_tasks--; 60 | } 61 | }; 62 | 63 | struct Worker 64 | { 65 | uint32_t m_id = 0; 66 | std::thread m_thread; 67 | std::function m_task = nullptr; 68 | bool m_running = true; 69 | TaskQueue* m_queue = nullptr; 70 | 71 | Worker() = default; 72 | 73 | Worker(TaskQueue& queue, uint32_t id) 74 | : m_id{id} 75 | , m_queue{&queue} 76 | { 77 | m_thread = std::thread([this](){ 78 | run(); 79 | }); 80 | } 81 | 82 | void run() 83 | { 84 | while (m_running) { 85 | m_queue->getTask(m_task); 86 | if (m_task == nullptr) { 87 | TaskQueue::wait(); 88 | } else { 89 | m_task(); 90 | m_queue->workDone(); 91 | m_task = nullptr; 92 | } 93 | } 94 | } 95 | 96 | void stop() 97 | { 98 | m_running = false; 99 | m_thread.join(); 100 | } 101 | }; 102 | 103 | struct ThreadPool 104 | { 105 | uint32_t m_thread_count = 0; 106 | TaskQueue m_queue; 107 | std::vector m_workers; 108 | 109 | explicit 110 | ThreadPool(uint32_t thread_count) 111 | : m_thread_count{thread_count} 112 | { 113 | m_workers.reserve(thread_count); 114 | for (uint32_t i{thread_count}; i--;) { 115 | m_workers.emplace_back(m_queue, static_cast(m_workers.size())); 116 | } 117 | } 118 | 119 | virtual ~ThreadPool() 120 | { 121 | for (Worker& worker : m_workers) { 122 | worker.stop(); 123 | } 124 | } 125 | 126 | template 127 | void addTask(TCallback&& callback) 128 | { 129 | m_queue.addTask(std::forward(callback)); 130 | } 131 | 132 | template 133 | void addTaskNoIncrement(TCallback&& callback) 134 | { 135 | m_queue.addTaskNoIncrement(std::forward(callback)); 136 | } 137 | 138 | void waitForCompletion() const 139 | { 140 | m_queue.waitForCompletion(); 141 | } 142 | 143 | bool isDone() const 144 | { 145 | return m_queue.m_remaining_tasks == 0; 146 | } 147 | 148 | template 149 | void dispatch(uint32_t element_count, TCallback&& callback) 150 | { 151 | const uint32_t batch_size = element_count / m_thread_count; 152 | for (uint32_t i{0}; i < m_thread_count; ++i) { 153 | const uint32_t start = batch_size * i; 154 | const uint32_t end = start + batch_size; 155 | addTask([start, end, &callback](){ callback(start, end); }); 156 | } 157 | 158 | if (batch_size * m_thread_count < element_count) { 159 | const uint32_t start = batch_size * m_thread_count; 160 | callback(start, element_count); 161 | } 162 | 163 | waitForCompletion(); 164 | } 165 | }; 166 | 167 | } -------------------------------------------------------------------------------- /src/utils/to_string.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | template 8 | std::string toString(const T& v, const uint8_t decimals = 2) 9 | { 10 | std::stringstream sx; 11 | sx << std::setprecision(decimals) << std::fixed << v; 12 | return sx.str(); 13 | } -------------------------------------------------------------------------------- /src/utils/va_grid.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct VertexArrayGrid 5 | { 6 | sf::VertexArray va; 7 | sf::Vector2u size; 8 | 9 | VertexArrayGrid(uint32_t width, uint32_t height) 10 | : va{sf::PrimitiveType::Points, width * height} 11 | , size{width, height} 12 | { 13 | uint32_t idx = 0; 14 | for (uint32_t x{0}; x < width; ++x) { 15 | for (uint32_t y{0}; y < height; ++y) { 16 | va[idx++].position = sf::Vector2f{static_cast(x), static_cast(y)}; 17 | } 18 | } 19 | } 20 | 21 | void setCellColor(uint32_t x, uint32_t y, sf::Color color) 22 | { 23 | const uint32_t idx = x * size.y + y; 24 | va[idx].color = color; 25 | } 26 | }; --------------------------------------------------------------------------------