├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── images └── image_1.png ├── res └── circle.png └── src ├── engine ├── common │ ├── color_utils.hpp │ ├── event_manager.hpp │ ├── grid.hpp │ ├── index_vector.hpp │ ├── math.hpp │ ├── number_generator.hpp │ ├── racc.hpp │ ├── utils.hpp │ └── vec.hpp ├── render │ └── viewport_handler.hpp └── window_context_handler.hpp ├── main.cpp ├── physics ├── collision_grid.hpp ├── physic_object.hpp └── physics.hpp ├── renderer ├── renderer.cpp └── renderer.hpp └── thread_pool └── thread_pool.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | build*/ 35 | .DS_Store 36 | .idea/ 37 | Cmake-build-*/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | project(Verlet-Multithread LANGUAGES CXX) 3 | 4 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 5 | option(BUILD_SHARED_LIBS "Build shared libraries" OFF) 6 | 7 | include(FetchContent) 8 | FetchContent_Declare(SFML 9 | GIT_REPOSITORY https://github.com/SFML/SFML.git 10 | GIT_TAG 2.6.x) 11 | FetchContent_MakeAvailable(SFML) 12 | 13 | file(GLOB_RECURSE source_files 14 | "src/*.cpp" 15 | ) 16 | 17 | set(SOURCES ${source_files}) 18 | 19 | add_executable(${PROJECT_NAME} ${SOURCES}) 20 | target_include_directories(${PROJECT_NAME} PRIVATE "src" "engine") 21 | target_link_libraries(${PROJECT_NAME} PRIVATE sfml-graphics) 22 | target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17) 23 | 24 | # Copy res dir to the binary directory 25 | add_custom_command( 26 | TARGET ${PROJECT_NAME} 27 | COMMENT "Copy Res directory" 28 | PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/res $/res 29 | VERBATIM) 30 | 31 | if(WIN32) 32 | add_custom_command( 33 | TARGET ${PROJECT_NAME} 34 | COMMENT "Copy OpenAL DLL" 35 | PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy ${SFML_SOURCE_DIR}/extlibs/bin/$,x64,x86>/openal32.dll $ 36 | VERBATIM) 37 | endif() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 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 | # Verlet Multi thread 2 | 3 | ![image](images/image_1.png) 4 | 5 | ## Compilation 6 | 7 | [SFML](https://www.sfml-dev.org/) and [CMake](https://cmake.org/) need to be installed. 8 | 9 | Create a `build` directory 10 | 11 | ```bash 12 | mkdir build 13 | cd build 14 | ``` 15 | 16 | **Configure** and **build** the project 17 | 18 | ```bash 19 | cmake .. 20 | cmake --build . 21 | ``` 22 | 23 | On **Windows** it will build in **debug** by default. To build in release you need to use this command 24 | 25 | ```bash 26 | cmake --build . --config Release 27 | ``` 28 | 29 | You will also need to add the `res` directory and the SFML dlls in the Release or Debug directory for the executable to run. 30 | 31 | -------------------------------------------------------------------------------- /images/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnBuffer/VerletSFML-Multithread/0f430a43177a9a0ebec1981228ef83c3925506a8/images/image_1.png -------------------------------------------------------------------------------- /res/circle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnBuffer/VerletSFML-Multithread/0f430a43177a9a0ebec1981228ef83c3925506a8/res/circle.png -------------------------------------------------------------------------------- /src/engine/common/color_utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "utils.hpp" 4 | #include "math.hpp" 5 | 6 | 7 | struct ColorUtils 8 | { 9 | template 10 | static sf::Color createColor(T r, T g, T b) 11 | { 12 | return { to(r), to(g), to(b) }; 13 | } 14 | 15 | template 16 | static sf::Color createColor(TVec3 vec) 17 | { 18 | return { to(vec.x), to(vec.y), to(vec.z) }; 19 | } 20 | 21 | static sf::Color interpolate(sf::Color color_1, sf::Color color_2, float ratio) 22 | { 23 | return ColorUtils::createColor( 24 | to(color_1.r) + ratio * to(color_2.r - color_1.r), 25 | to(color_1.g) + ratio * to(color_2.g - color_1.g), 26 | to(color_1.b) + ratio * to(color_2.b - color_1.b) 27 | ); 28 | } 29 | 30 | static sf::Color getRainbow(float t) 31 | { 32 | const float r = sin(t); 33 | const float g = sin(t + 0.33f * 2.0f * Math::PI); 34 | const float b = sin(t + 0.66f * 2.0f * Math::PI); 35 | return createColor(255 * r * r, 255 * g * g, 255 * b * b); 36 | } 37 | 38 | }; -------------------------------------------------------------------------------- /src/engine/common/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/engine/common/grid.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | 6 | template 7 | struct Grid 8 | { 9 | struct HitPoint 10 | { 11 | T* cell; 12 | float dist; 13 | 14 | HitPoint() 15 | : cell(nullptr) 16 | , dist(0.0f) 17 | {} 18 | }; 19 | 20 | int32_t width, height; 21 | std::vector data; 22 | 23 | Grid() 24 | : width(0) 25 | , height(0) 26 | {} 27 | 28 | Grid(int32_t width_, int32_t height_) 29 | : width(width_) 30 | , height(height_) 31 | { 32 | data.resize(width * height); 33 | } 34 | 35 | int32_t mod(int32_t dividend, int32_t divisor) const 36 | { 37 | return (dividend%divisor + divisor) % divisor; 38 | } 39 | 40 | template 41 | bool checkCoords(const Vec2Type& v) const 42 | { 43 | return checkCoords(static_cast(v.x), static_cast(v.y)); 44 | } 45 | 46 | bool checkCoords(int32_t x, int32_t y) const 47 | { 48 | return static_cast(x) > 0 && static_cast(x) < (width - 1) && 49 | static_cast(y) > 0 && static_cast(y) < (height - 1); 50 | } 51 | 52 | const T& get(int32_t x, int32_t y) const 53 | { 54 | return data[y * width + x]; 55 | } 56 | 57 | template 58 | T& get(const Vec2Type& v) 59 | { 60 | return get(static_cast(v.x), static_cast(v.y)); 61 | } 62 | 63 | template 64 | const T& get(const Vec2Type& v) const 65 | { 66 | return get(static_cast(v.x), static_cast(v.y)); 67 | } 68 | 69 | template 70 | const T& getWrap(Vec2Type v) const 71 | { 72 | return getWrap(v.x, v.y); 73 | } 74 | 75 | const T& getWrap(int32_t x, int32_t y) const 76 | { 77 | return get(mod(x, width), mod(y, height)); 78 | } 79 | 80 | T& get(int32_t x, int32_t y) 81 | { 82 | return data[y * width + x]; 83 | } 84 | 85 | template 86 | void set(const Vec2Type& v, const T& obj) 87 | { 88 | set(v.x, v.y, obj); 89 | } 90 | 91 | void set(int32_t x, int32_t y, const T& obj) 92 | { 93 | data[y * width + x] = obj; 94 | } 95 | }; 96 | -------------------------------------------------------------------------------- /src/engine/common/index_vector.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | 6 | namespace civ 7 | { 8 | 9 | using ID = uint64_t; 10 | 11 | template 12 | struct Ref; 13 | 14 | template 15 | struct PRef; 16 | 17 | struct Slot 18 | { 19 | ID id; 20 | ID data_id; 21 | }; 22 | 23 | 24 | template 25 | struct ObjectSlot 26 | { 27 | ObjectSlot(ID id_, T* object_) 28 | : id(id_) 29 | , object(object_) 30 | {} 31 | 32 | ID id; 33 | T* object; 34 | }; 35 | 36 | 37 | struct GenericProvider 38 | { 39 | virtual ~GenericProvider() 40 | { 41 | 42 | } 43 | 44 | virtual void* get(civ::ID id) = 0; 45 | 46 | [[nodiscard]] 47 | virtual bool isValid(civ::ID, uint64_t validity_id) const = 0; 48 | }; 49 | 50 | 51 | template 52 | struct ObjectSlotConst 53 | { 54 | ObjectSlotConst(ID id_, const T* object_) 55 | : id(id_) 56 | , object(object_) 57 | {} 58 | 59 | ID id; 60 | const T* object; 61 | }; 62 | 63 | 64 | struct SlotMetadata 65 | { 66 | ID rid; 67 | ID op_id; 68 | }; 69 | 70 | 71 | template 72 | struct Vector : public GenericProvider 73 | { 74 | Vector() 75 | : data_size(0) 76 | , op_count(0) 77 | {} 78 | 79 | ~Vector() override 80 | { 81 | // Since we already explicitly destroyed objects >= data_size index 82 | // the compiler will complain when double freeing these objects. 83 | // The quick fix for now is to fill these places with default initialized objects 84 | const uint64_t capacity = data.size(); 85 | for (uint64_t i{data_size}; i 92 | ID emplace_back(Args&&... args); 93 | ID push_back(const T& obj); 94 | [[nodiscard]] 95 | ID getNextID() const; 96 | void erase(ID id); 97 | template 98 | void remove_if(TPredicate&& f); 99 | void clear(); 100 | // Data access by ID 101 | T& operator[](ID id); 102 | const T& operator[](ID id) const; 103 | // Returns a standalone object allowing access to the underlying data 104 | Ref getRef(ID id); 105 | template 106 | PRef getPRef(ID id); 107 | // Returns the data at a specific place in the data vector (not an ID) 108 | T& getDataAt(uint64_t i); 109 | // Check if the data behind the pointer is the same 110 | [[nodiscard]] 111 | bool isValid(ID id, ID validity) const override; 112 | [[nodiscard]] 113 | uint64_t getOperationID(ID id) const; 114 | // Returns the ith object and global_id 115 | ObjectSlot getSlotAt(uint64_t i); 116 | ObjectSlotConst getSlotAt(uint64_t i) const; 117 | // Iterators 118 | typename std::vector::iterator begin(); 119 | typename std::vector::iterator end(); 120 | typename std::vector::const_iterator begin() const; 121 | typename std::vector::const_iterator end() const; 122 | // Number of objects in the provider 123 | [[nodiscard]] 124 | uint64_t size() const; 125 | 126 | [[nodiscard]] 127 | ID getValidityID(ID id) const; 128 | 129 | public: 130 | std::vector data; 131 | std::vector ids; 132 | std::vector metadata; 133 | uint64_t data_size; 134 | uint64_t op_count; 135 | 136 | [[nodiscard]] 137 | bool isFull() const; 138 | // Returns the ID of the ith element of the data provider 139 | [[nodiscard]] 140 | ID getID(uint64_t i) const; 141 | // Returns the data emplacement of an ID 142 | [[nodiscard]] 143 | uint64_t getDataID(ID id) const; 144 | Slot createNewSlot(); 145 | Slot getFreeSlot(); 146 | Slot getSlot(); 147 | SlotMetadata& getMetadataAt(ID id); 148 | const T& getAt(ID id) const; 149 | [[nodiscard]] 150 | void* get(civ::ID id) override; 151 | 152 | template 153 | void foreach(TCallback&& callback); 154 | 155 | template friend struct PRef; 156 | }; 157 | 158 | template 159 | template 160 | inline uint64_t Vector::emplace_back(Args&& ...args) 161 | { 162 | const Slot slot = getSlot(); 163 | new(&data[slot.data_id]) T(std::forward(args)...); 164 | return slot.id; 165 | } 166 | 167 | template 168 | inline uint64_t Vector::push_back(const T& obj) 169 | { 170 | const Slot slot = getSlot(); 171 | data[slot.data_id] = obj; 172 | return slot.id; 173 | } 174 | 175 | template 176 | inline void Vector::erase(ID id) 177 | { 178 | // Retrieve the object position in data 179 | const uint64_t data_index = ids[id]; 180 | // Check if the object has been already erased 181 | if (data_index >= data_size) { return; } 182 | // Destroy the object 183 | data[data_index].~T(); 184 | // Swap the object at the end 185 | --data_size; 186 | const uint64_t last_id = metadata[data_size].rid; 187 | std::swap(data[data_size], data[data_index]); 188 | std::swap(metadata[data_size], metadata[data_index]); 189 | std::swap(ids[last_id], ids[id]); 190 | // Invalidate the operation ID 191 | metadata[data_size].op_id = ++op_count; 192 | } 193 | 194 | template 195 | inline T& Vector::operator[](ID id) 196 | { 197 | return const_cast(getAt(id)); 198 | } 199 | 200 | template 201 | inline const T& Vector::operator[](ID id) const 202 | { 203 | return getAt(id); 204 | } 205 | 206 | template 207 | inline ObjectSlot Vector::getSlotAt(uint64_t i) 208 | { 209 | return ObjectSlot(metadata[i].rid, &data[i]); 210 | } 211 | 212 | template 213 | inline ObjectSlotConst Vector::getSlotAt(uint64_t i) const 214 | { 215 | return ObjectSlotConst(metadata[i].rid, &data[i]); 216 | } 217 | 218 | template 219 | inline Ref Vector::getRef(ID id) 220 | { 221 | return Ref(id, this, metadata[ids[id]].op_id); 222 | } 223 | 224 | template 225 | template 226 | PRef Vector::getPRef(ID id) { 227 | return PRef{id, this, metadata[ids[id]].op_id}; 228 | } 229 | 230 | template 231 | inline T& Vector::getDataAt(uint64_t i) 232 | { 233 | return data[i]; 234 | } 235 | 236 | template 237 | inline uint64_t Vector::getID(uint64_t i) const 238 | { 239 | return metadata[i].rid; 240 | } 241 | 242 | template 243 | inline uint64_t Vector::size() const 244 | { 245 | return data_size; 246 | } 247 | 248 | template 249 | inline typename std::vector::iterator Vector::begin() 250 | { 251 | return data.begin(); 252 | } 253 | 254 | template 255 | inline typename std::vector::iterator Vector::end() 256 | { 257 | return data.begin() + data_size; 258 | } 259 | 260 | template 261 | inline typename std::vector::const_iterator Vector::begin() const 262 | { 263 | return data.begin(); 264 | } 265 | 266 | template 267 | inline typename std::vector::const_iterator Vector::end() const 268 | { 269 | return data.begin() + data_size; 270 | } 271 | 272 | template 273 | inline bool Vector::isFull() const 274 | { 275 | return data_size == data.size(); 276 | } 277 | 278 | template 279 | inline Slot Vector::createNewSlot() 280 | { 281 | data.emplace_back(); 282 | ids.push_back(data_size); 283 | metadata.push_back({data_size, op_count++}); 284 | return { data_size, data_size }; 285 | } 286 | 287 | template 288 | inline Slot Vector::getFreeSlot() 289 | { 290 | const uint64_t reuse_id = metadata[data_size].rid; 291 | metadata[data_size].op_id = op_count++; 292 | return { reuse_id, data_size }; 293 | } 294 | 295 | template 296 | inline Slot Vector::getSlot() 297 | { 298 | const Slot slot = isFull() ? createNewSlot() : getFreeSlot(); 299 | ++data_size; 300 | return slot; 301 | } 302 | 303 | template 304 | inline SlotMetadata& Vector::getMetadataAt(ID id) 305 | { 306 | return metadata[getDataID(id)]; 307 | } 308 | 309 | template 310 | inline uint64_t Vector::getDataID(ID id) const 311 | { 312 | return ids[id]; 313 | } 314 | 315 | template 316 | inline const T& Vector::getAt(ID id) const 317 | { 318 | return data[getDataID(id)]; 319 | } 320 | 321 | template 322 | inline bool Vector::isValid(ID id, ID validity) const 323 | { 324 | return validity == metadata[getDataID(id)].op_id; 325 | } 326 | 327 | template 328 | inline uint64_t Vector::getOperationID(ID id) const 329 | { 330 | return metadata[getDataID(id)].op_id; 331 | } 332 | 333 | template 334 | template 335 | void Vector::remove_if(TPredicate&& f) 336 | { 337 | for (uint64_t data_index{ 0 }; data_index < data_size;) { 338 | if (f(data[data_index])) { 339 | erase(metadata[data_index].rid); 340 | } 341 | else { 342 | data_index++; 343 | } 344 | } 345 | } 346 | 347 | template 348 | ID Vector::getNextID() const { 349 | return isFull() ? data_size : metadata[data_size].rid; 350 | } 351 | 352 | template 353 | void *Vector::get(civ::ID id) 354 | { 355 | return static_cast(&data[ids[id]]); 356 | } 357 | 358 | template 359 | void Vector::clear() 360 | { 361 | ids.clear(); 362 | data.clear(); 363 | metadata.clear(); 364 | for (SlotMetadata& slm : metadata) { 365 | slm.rid = 0; 366 | slm.op_id = ++op_count; 367 | } 368 | data_size = 0; 369 | } 370 | 371 | template 372 | template 373 | void Vector::foreach(TCallback &&callback) { 374 | // Use index based for to allow data creation during iteration 375 | const uint64_t current_size = data_size; 376 | for (uint64_t i{0}; i 382 | ID Vector::getValidityID(ID id) const 383 | { 384 | return metadata[ids[id]].op_id; 385 | } 386 | 387 | template 388 | struct Ref 389 | { 390 | Ref() 391 | : id(0) 392 | , array(nullptr) 393 | , validity_id(0) 394 | {} 395 | 396 | Ref(ID id_, Vector* a, ID vid) 397 | : id(id_) 398 | , array(a) 399 | , validity_id(vid) 400 | {} 401 | 402 | T* operator->() 403 | { 404 | return &(*array)[id]; 405 | } 406 | 407 | const T* operator->() const 408 | { 409 | return &(*array)[id]; 410 | } 411 | 412 | T& operator*() 413 | { 414 | return (*array)[id]; 415 | } 416 | 417 | const T& operator*() const 418 | { 419 | return (*array)[id]; 420 | } 421 | 422 | civ::ID getID() const 423 | { 424 | return id; 425 | } 426 | 427 | explicit 428 | operator bool() const 429 | { 430 | return array && array->isValid(id, validity_id); 431 | } 432 | 433 | public: 434 | ID id; 435 | Vector* array; 436 | ID validity_id; 437 | }; 438 | 439 | 440 | template 441 | struct PRef 442 | { 443 | using ProviderCallback = T*(*)(ID, GenericProvider*); 444 | 445 | PRef() 446 | : id(0) 447 | , provider_callback(nullptr) 448 | , provider(nullptr) 449 | , validity_id(0) 450 | {} 451 | 452 | template 453 | PRef(ID index, Vector* a, ID vid) 454 | : id(index) 455 | , provider_callback{PRef::get} 456 | , provider(a) 457 | , validity_id(vid) 458 | {} 459 | 460 | template 461 | PRef(const PRef& other) 462 | : id(other.id) 463 | , provider_callback{PRef::get} 464 | , provider(other.provider) 465 | , validity_id(other.validity_id) 466 | { 467 | } 468 | 469 | template 470 | static T* get(ID index, GenericProvider* provider) 471 | { 472 | return dynamic_cast(static_cast(provider->get(index))); 473 | } 474 | 475 | T* operator->() 476 | { 477 | return provider_callback(id, provider); 478 | } 479 | 480 | T& operator*() 481 | { 482 | return *provider_callback(id, provider); 483 | } 484 | 485 | const T& operator*() const 486 | { 487 | return *provider_callback(id, provider); 488 | } 489 | 490 | civ::ID getID() const 491 | { 492 | return id; 493 | } 494 | 495 | explicit 496 | operator bool() const 497 | { 498 | return provider && provider->isValid(id, validity_id); 499 | } 500 | 501 | private: 502 | ID id; 503 | ProviderCallback provider_callback; 504 | GenericProvider* provider; 505 | uint64_t validity_id; 506 | 507 | template friend struct PRef; 508 | template friend struct Vector; 509 | }; 510 | 511 | } 512 | -------------------------------------------------------------------------------- /src/engine/common/math.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | #undef min 6 | #undef max 7 | 8 | struct Math 9 | { 10 | static constexpr float PI = 3.141592653f; 11 | static constexpr float TwoPI = 2.0f * PI; 12 | 13 | static float pow(float v, const uint32_t p) 14 | { 15 | float res = 1; 16 | for (uint32_t i{p}; i--;) { 17 | res *= v; 18 | } 19 | return res; 20 | } 21 | 22 | template 23 | static T sign(T v) 24 | { 25 | return v < 0.0f ? -1.0f : 1.0f; 26 | } 27 | 28 | static float sigm(float x) 29 | { 30 | return 1.0f / (1.0f + exp(-x)); 31 | } 32 | 33 | static float sigm_0(float x) 34 | { 35 | return sigm(x) - 0.5f; 36 | } 37 | 38 | static float radToDeg(float r) 39 | { 40 | constexpr float radian_to_deg = 180.0f / PI; 41 | return r * radian_to_deg; 42 | } 43 | 44 | static float clamp(float v, float min, float max) 45 | { 46 | return std::min(std::max(min, v), max); 47 | } 48 | 49 | static float gaussian(float x, float a, float b, float c) 50 | { 51 | const float n = x-b; 52 | return a * exp(-(n * n)/(2.0f * c * c)); 53 | } 54 | }; 55 | 56 | 57 | struct MathVec2 58 | { 59 | template 60 | static float length2(TVec2 v) 61 | { 62 | return v.x * v.x + v.y * v.y; 63 | } 64 | 65 | template 66 | static float length(TVec2 v) 67 | { 68 | return sqrt(length2(v)); 69 | } 70 | 71 | template 72 | static float angle(TVec2 v_1, TVec2 v_2 = {1.0f, 0.0f}) 73 | { 74 | const float dot = v_1.x * v_2.x + v_1.y * v_2.y; 75 | const float det = v_1.x * v_2.y - v_1.y * v_2.x; 76 | return atan2(det, dot); 77 | } 78 | 79 | template 80 | static float dot(TVec2 v1, TVec2 v2) 81 | { 82 | return v1.x * v2.x + v1.y * v2.y; 83 | } 84 | 85 | template 86 | static float cross(TVec2 v1, TVec2 v2) 87 | { 88 | return v1.x * v2.y - v1.y * v2.x; 89 | } 90 | 91 | template 92 | static Vec2Type normal(const Vec2Type& v) 93 | { 94 | return {-v.y, v.x}; 95 | } 96 | 97 | template 98 | static Vec2Type rotate(const Vec2Type& v, float angle) 99 | { 100 | const float ca = cos(angle); 101 | const float sa = sin(angle); 102 | return {ca * v.x - sa * v.y, sa * v.x + ca * v.y}; 103 | } 104 | 105 | template 106 | static Vec2Type rotateDir(const Vec2Type& v, const Vec2Type& dir) 107 | { 108 | return { dir.x * v.x - dir.y * v.y, dir.y * v.x + dir.x * v.y }; 109 | } 110 | 111 | template 112 | static Vec2Type normalize(const Vec2Type& v) 113 | { 114 | return v / length(v); 115 | } 116 | 117 | template class Vec2Type, typename T> 118 | static Vec2Type reflect(const Vec2Type& v, const Vec2Type& n) 119 | { 120 | return v - n * (MathVec2::dot(v, n) * 2.0f); 121 | } 122 | }; 123 | 124 | -------------------------------------------------------------------------------- /src/engine/common/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&) 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/engine/common/racc.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | template 6 | struct RAccBase 7 | { 8 | uint32_t max_values_count; 9 | std::vector values; 10 | uint32_t current_index; 11 | T pop_value; 12 | 13 | RAccBase(uint32_t max_size=8) 14 | : max_values_count(max_size) 15 | , values(max_size, 0.0f) 16 | , current_index(0) 17 | , pop_value(0.0f) 18 | { 19 | } 20 | 21 | bool addValueBase(T val) 22 | { 23 | const bool pop = current_index >= max_values_count; 24 | const uint32_t i = getIndex(); 25 | pop_value = values[i]; 26 | values[i] = val; 27 | ++current_index; 28 | return pop; 29 | } 30 | 31 | uint32_t getCount() const 32 | { 33 | return std::min(current_index + 1, max_values_count); 34 | } 35 | 36 | virtual T get() const = 0; 37 | 38 | operator T() const 39 | { 40 | return get(); 41 | } 42 | 43 | protected: 44 | uint32_t getIndex(int32_t offset = 0) const 45 | { 46 | return (current_index + offset) % max_values_count; 47 | } 48 | }; 49 | 50 | 51 | template 52 | struct RMean : public RAccBase 53 | { 54 | T sum; 55 | 56 | RMean(uint32_t max_size=8) 57 | : RAccBase(max_size) 58 | , sum(0.0f) 59 | { 60 | } 61 | 62 | void addValue(T v) 63 | { 64 | sum += v - float(RAccBase::addValueBase(v)) * RAccBase::pop_value; 65 | } 66 | 67 | T get() const override 68 | { 69 | return sum / float(RAccBase::getCount()); 70 | } 71 | }; 72 | 73 | 74 | template 75 | struct RDiff : public RAccBase 76 | { 77 | RDiff(uint32_t max_size = 8) 78 | : RAccBase(max_size) 79 | { 80 | } 81 | 82 | void addValue(T v) 83 | { 84 | RAccBase::addValueBase(v); 85 | } 86 | 87 | T get() const override 88 | { 89 | return RAccBase::values[RAccBase::getIndex(-1)] - RAccBase::values[RAccBase::getIndex()]; 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /src/engine/common/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "index_vector.hpp" 3 | #include 4 | 5 | 6 | template 7 | U to(const T& v) 8 | { 9 | return static_cast(v); 10 | } 11 | 12 | 13 | template 14 | using CIVector = civ::Vector; 15 | 16 | 17 | template 18 | T sign(T v) 19 | { 20 | return v < 0.0f ? -1.0f : 1.0f; 21 | } 22 | 23 | 24 | template 25 | static std::string toString(T value) 26 | { 27 | std::stringstream sx; 28 | sx << value; 29 | return sx.str(); 30 | } 31 | 32 | 33 | template 34 | sf::Vector2f toVector2f(sf::Vector2 v) 35 | { 36 | return {to(v.x), to(v.y)}; 37 | } 38 | -------------------------------------------------------------------------------- /src/engine/common/vec.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | using Vec2 = sf::Vector2f; 5 | using IVec2 = sf::Vector2i; 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/engine/render/viewport_handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | 5 | struct ViewportHandler 6 | { 7 | struct State 8 | { 9 | sf::Vector2f center; 10 | sf::Vector2f offset; 11 | float zoom; 12 | bool clicking; 13 | sf::Vector2f mouse_position; 14 | sf::Vector2f mouse_world_position; 15 | sf::Transform transform; 16 | 17 | State(sf::Vector2f render_size, const float base_zoom = 1.0f) 18 | : center(render_size.x * 0.5f, render_size.y * 0.5f) 19 | , offset(center / base_zoom) 20 | , zoom(base_zoom) 21 | , clicking(false) 22 | {} 23 | 24 | void updateState() 25 | { 26 | const float z = zoom; 27 | transform = sf::Transform::Identity; 28 | transform.translate(center); 29 | transform.scale(z, z); 30 | transform.translate(-offset); 31 | } 32 | 33 | void updateMousePosition(sf::Vector2f new_position) 34 | { 35 | mouse_position = new_position; 36 | const sf::Vector2f pos(static_cast(new_position.x), static_cast(new_position.y)); 37 | mouse_world_position = offset + (pos - center) / zoom; 38 | } 39 | }; 40 | 41 | State state; 42 | 43 | ViewportHandler(sf::Vector2f size) 44 | : state(size) 45 | { 46 | state.updateState(); 47 | } 48 | 49 | void addOffset(sf::Vector2f v) 50 | { 51 | state.offset += v / state.zoom; 52 | state.updateState(); 53 | } 54 | 55 | void zoom(float f) 56 | { 57 | state.zoom *= f; 58 | state.updateState(); 59 | } 60 | 61 | void wheelZoom(float w) 62 | { 63 | if (w) { 64 | const float zoom_amount = 1.2f; 65 | const float delta = w > 0 ? zoom_amount : 1.0f / zoom_amount; 66 | zoom(delta); 67 | } 68 | } 69 | 70 | void reset() 71 | { 72 | state.zoom = 1.0f; 73 | setFocus(state.center); 74 | } 75 | 76 | const sf::Transform& getTransform() const 77 | { 78 | return state.transform; 79 | } 80 | 81 | void click(sf::Vector2f relative_click_position) 82 | { 83 | state.mouse_position = relative_click_position; 84 | state.clicking = true; 85 | } 86 | 87 | void unclick() 88 | { 89 | state.clicking = false; 90 | } 91 | 92 | void setMousePosition(sf::Vector2f new_mouse_position) 93 | { 94 | if (state.clicking) { 95 | addOffset(state.mouse_position - new_mouse_position); 96 | } 97 | state.updateMousePosition(new_mouse_position); 98 | } 99 | 100 | void setFocus(sf::Vector2f focus_position) 101 | { 102 | state.offset = focus_position; 103 | state.updateState(); 104 | } 105 | 106 | void setZoom(float zoom) 107 | { 108 | state.zoom = zoom; 109 | state.updateState(); 110 | } 111 | 112 | sf::Vector2f getMouseWorldPosition() const 113 | { 114 | return state.mouse_world_position; 115 | } 116 | 117 | sf::Vector2f getScreenCoords(sf::Vector2f world_pos) const 118 | { 119 | return state.transform.transformPoint(world_pos); 120 | } 121 | }; 122 | -------------------------------------------------------------------------------- /src/engine/window_context_handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "render/viewport_handler.hpp" 4 | #include "common/event_manager.hpp" 5 | #include "common/utils.hpp" 6 | 7 | 8 | class WindowContextHandler; 9 | 10 | 11 | class RenderContext 12 | { 13 | public: 14 | explicit 15 | RenderContext(sf::RenderWindow& window) 16 | : m_window(window) 17 | , m_viewport_handler(toVector2f(window.getSize())) 18 | { 19 | } 20 | 21 | void setFocus(sf::Vector2f focus) 22 | { 23 | m_viewport_handler.setFocus(focus); 24 | } 25 | 26 | void setZoom(float zoom) 27 | { 28 | m_viewport_handler.setZoom(zoom); 29 | } 30 | 31 | void registerCallbacks(sfev::EventManager& event_manager) 32 | { 33 | event_manager.addEventCallback(sf::Event::Closed, [&](sfev::CstEv) { m_window.close(); }); 34 | event_manager.addKeyPressedCallback(sf::Keyboard::Escape, [&](sfev::CstEv) { m_window.close(); }); 35 | event_manager.addMousePressedCallback(sf::Mouse::Left, [&](sfev::CstEv) { 36 | m_viewport_handler.click(event_manager.getFloatMousePosition()); 37 | }); 38 | event_manager.addMouseReleasedCallback(sf::Mouse::Left, [&](sfev::CstEv) { 39 | m_viewport_handler.unclick(); 40 | }); 41 | event_manager.addEventCallback(sf::Event::MouseMoved, [&](sfev::CstEv) { 42 | m_viewport_handler.setMousePosition(event_manager.getFloatMousePosition()); 43 | }); 44 | event_manager.addEventCallback(sf::Event::MouseWheelScrolled, [&](sfev::CstEv e) { 45 | m_viewport_handler.wheelZoom(e.mouseWheelScroll.delta); 46 | }); 47 | } 48 | 49 | void drawDirect(const sf::Drawable& drawable) 50 | { 51 | m_window.draw(drawable); 52 | } 53 | 54 | void draw(const sf::Drawable& drawable, sf::RenderStates render_states = {}) 55 | { 56 | render_states.transform = m_viewport_handler.getTransform(); 57 | m_window.draw(drawable, render_states); 58 | } 59 | 60 | void clear(sf::Color color = sf::Color::Black) 61 | { 62 | m_window.clear(color); 63 | } 64 | 65 | void display() 66 | { 67 | m_window.display(); 68 | } 69 | 70 | private: 71 | sf::RenderWindow& m_window; 72 | ViewportHandler m_viewport_handler; 73 | 74 | friend class WindowContextHandler; 75 | }; 76 | 77 | 78 | class WindowContextHandler 79 | { 80 | public: 81 | WindowContextHandler(const std::string& window_name, 82 | sf::Vector2u window_size, 83 | int32_t window_style = sf::Style::Default) 84 | : m_window(sf::VideoMode(window_size.x, window_size.y), window_name, window_style) 85 | , m_event_manager(m_window, true) 86 | , m_render_context(m_window) 87 | { 88 | m_window.setFramerateLimit(60); 89 | m_render_context.registerCallbacks(m_event_manager); 90 | } 91 | 92 | [[nodiscard]] 93 | sf::Vector2u getWindowSize() const 94 | { 95 | return m_window.getSize(); 96 | } 97 | 98 | void processEvents() 99 | { 100 | m_event_manager.processEvents(); 101 | } 102 | 103 | bool isRunning() const 104 | { 105 | return m_window.isOpen(); 106 | } 107 | 108 | bool run() 109 | { 110 | processEvents(); 111 | return isRunning(); 112 | } 113 | 114 | sfev::EventManager& getEventManager() 115 | { 116 | return m_event_manager; 117 | } 118 | 119 | RenderContext& getRenderContext() 120 | { 121 | return m_render_context; 122 | } 123 | 124 | sf::Vector2f getWorldMousePosition() const 125 | { 126 | return m_render_context.m_viewport_handler.getMouseWorldPosition(); 127 | } 128 | 129 | void setFramerateLimit(uint32_t framerate) 130 | { 131 | m_window.setFramerateLimit(framerate); 132 | } 133 | 134 | private: 135 | sf::RenderWindow m_window; 136 | sfev::EventManager m_event_manager; 137 | RenderContext m_render_context; 138 | }; 139 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "engine/window_context_handler.hpp" 4 | #include "engine/common/color_utils.hpp" 5 | 6 | #include "physics/physics.hpp" 7 | #include "thread_pool/thread_pool.hpp" 8 | #include "renderer/renderer.hpp" 9 | 10 | 11 | int main() 12 | { 13 | const uint32_t window_width = 1920; 14 | const uint32_t window_height = 1080; 15 | WindowContextHandler app("Verlet-MultiThread", sf::Vector2u(window_width, window_height), sf::Style::Default); 16 | RenderContext& render_context = app.getRenderContext(); 17 | // Initialize solver and renderer 18 | 19 | tp::ThreadPool thread_pool(10); 20 | const IVec2 world_size{300, 300}; 21 | PhysicSolver solver{world_size, thread_pool}; 22 | Renderer renderer(solver, thread_pool); 23 | 24 | const float margin = 20.0f; 25 | const auto zoom = static_cast(window_height - margin) / static_cast(world_size.y); 26 | render_context.setZoom(zoom); 27 | render_context.setFocus({world_size.x * 0.5f, world_size.y * 0.5f}); 28 | 29 | bool emit = true; 30 | app.getEventManager().addKeyPressedCallback(sf::Keyboard::Space, [&](sfev::CstEv) { 31 | emit = !emit; 32 | }); 33 | 34 | constexpr uint32_t fps_cap = 60; 35 | int32_t target_fps = fps_cap; 36 | app.getEventManager().addKeyPressedCallback(sf::Keyboard::S, [&](sfev::CstEv) { 37 | target_fps = target_fps ? 0 : fps_cap; 38 | app.setFramerateLimit(target_fps); 39 | }); 40 | 41 | // Main loop 42 | const float dt = 1.0f / static_cast(fps_cap); 43 | while (app.run()) { 44 | if (solver.objects.size() < 80000 && emit) { 45 | for (uint32_t i{20}; i--;) { 46 | const auto id = solver.createObject({2.0f, 10.0f + 1.1f * i}); 47 | solver.objects[id].last_position.x -= 0.2f; 48 | solver.objects[id].color = ColorUtils::getRainbow(id * 0.0001f); 49 | } 50 | } 51 | 52 | solver.update(dt); 53 | 54 | render_context.clear(); 55 | renderer.render(render_context); 56 | render_context.display(); 57 | } 58 | 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /src/physics/collision_grid.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "engine/common/vec.hpp" 4 | #include "engine/common/grid.hpp" 5 | 6 | 7 | struct CollisionCell 8 | { 9 | static constexpr uint8_t cell_capacity = 4; 10 | static constexpr uint8_t max_cell_idx = cell_capacity - 1; 11 | 12 | // Overlap workaround 13 | uint32_t objects_count = 0; 14 | uint32_t objects[cell_capacity] = {}; 15 | 16 | CollisionCell() = default; 17 | 18 | void addAtom(uint32_t id) 19 | { 20 | objects[objects_count] = id; 21 | objects_count += objects_count < max_cell_idx; 22 | } 23 | 24 | void clear() 25 | { 26 | objects_count = 0u; 27 | } 28 | 29 | void remove(uint32_t id) 30 | { 31 | for (uint32_t i{0}; i < objects_count; ++i) { 32 | if (objects[i] == id) { 33 | // Swap pop 34 | objects[i] = objects[objects_count - 1]; 35 | --objects_count; 36 | return; 37 | } 38 | } 39 | 40 | //std::cout << "Problem" << std::endl; 41 | } 42 | }; 43 | 44 | struct CollisionGrid : public Grid 45 | { 46 | CollisionGrid() 47 | : Grid() 48 | {} 49 | 50 | CollisionGrid(int32_t width, int32_t height) 51 | : Grid(width, height) 52 | {} 53 | 54 | bool addAtom(uint32_t x, uint32_t y, uint32_t atom) 55 | { 56 | const uint32_t id = x * height + y; 57 | // Add to grid 58 | data[id].addAtom(atom); 59 | return true; 60 | } 61 | 62 | void clear() 63 | { 64 | for (auto& c : data) { 65 | c.objects_count = 0; 66 | } 67 | } 68 | }; 69 | -------------------------------------------------------------------------------- /src/physics/physic_object.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "collision_grid.hpp" 3 | #include "engine/common/utils.hpp" 4 | #include "engine/common/math.hpp" 5 | 6 | 7 | struct PhysicObject 8 | { 9 | // Verlet 10 | Vec2 position = {0.0f, 0.0f}; 11 | Vec2 last_position = {0.0f, 0.0f}; 12 | Vec2 acceleration = {0.0f, 0.0f}; 13 | sf::Color color; 14 | 15 | PhysicObject() = default; 16 | 17 | explicit 18 | PhysicObject(Vec2 position_) 19 | : position(position_) 20 | , last_position(position_) 21 | {} 22 | 23 | void setPosition(Vec2 pos) 24 | { 25 | position = pos; 26 | last_position = pos; 27 | } 28 | 29 | void update(float dt) 30 | { 31 | const Vec2 last_update_move = position - last_position; 32 | 33 | const float VELOCITY_DAMPING = 40.0f; // arbitrary, approximating air friction 34 | 35 | const Vec2 new_position = position + last_update_move + (acceleration - last_update_move * VELOCITY_DAMPING) * (dt * dt); 36 | last_position = position; 37 | position = new_position; 38 | acceleration = {0.0f, 0.0f}; 39 | } 40 | 41 | void stop() 42 | { 43 | last_position = position; 44 | } 45 | 46 | void slowdown(float ratio) 47 | { 48 | last_position = last_position + ratio * (position - last_position); 49 | } 50 | 51 | [[nodiscard]] 52 | float getSpeed() const 53 | { 54 | return MathVec2::length(position - last_position); 55 | } 56 | 57 | [[nodiscard]] 58 | Vec2 getVelocity() const 59 | { 60 | return position - last_position; 61 | } 62 | 63 | void addVelocity(Vec2 v) 64 | { 65 | last_position -= v; 66 | } 67 | 68 | void setPositionSameSpeed(Vec2 new_position) 69 | { 70 | const Vec2 to_last = last_position - position; 71 | position = new_position; 72 | last_position = position + to_last; 73 | } 74 | 75 | void move(Vec2 v) 76 | { 77 | position += v; 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /src/physics/physics.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "collision_grid.hpp" 3 | #include "physic_object.hpp" 4 | #include "engine/common/utils.hpp" 5 | #include "engine/common/index_vector.hpp" 6 | #include "thread_pool/thread_pool.hpp" 7 | 8 | 9 | struct PhysicSolver 10 | { 11 | CIVector objects; 12 | CollisionGrid grid; 13 | Vec2 world_size; 14 | Vec2 gravity = {0.0f, 20.0f}; 15 | 16 | // Simulation solving pass count 17 | uint32_t sub_steps; 18 | tp::ThreadPool& thread_pool; 19 | 20 | PhysicSolver(IVec2 size, tp::ThreadPool& tp) 21 | : grid{size.x, size.y} 22 | , world_size{to(size.x), to(size.y)} 23 | , sub_steps{8} 24 | , thread_pool{tp} 25 | { 26 | grid.clear(); 27 | } 28 | 29 | // Checks if two atoms are colliding and if so create a new contact 30 | void solveContact(uint32_t atom_1_idx, uint32_t atom_2_idx) 31 | { 32 | constexpr float response_coef = 1.0f; 33 | constexpr float eps = 0.0001f; 34 | PhysicObject& obj_1 = objects.data[atom_1_idx]; 35 | PhysicObject& obj_2 = objects.data[atom_2_idx]; 36 | const Vec2 o2_o1 = obj_1.position - obj_2.position; 37 | const float dist2 = o2_o1.x * o2_o1.x + o2_o1.y * o2_o1.y; 38 | if (dist2 < 1.0f && dist2 > eps) { 39 | const float dist = sqrt(dist2); 40 | // Radius are all equal to 1.0f 41 | const float delta = response_coef * 0.5f * (1.0f - dist); 42 | const Vec2 col_vec = (o2_o1 / dist) * delta; 43 | obj_1.position += col_vec; 44 | obj_2.position -= col_vec; 45 | } 46 | } 47 | 48 | void checkAtomCellCollisions(uint32_t atom_idx, const CollisionCell& c) 49 | { 50 | for (uint32_t i{0}; i < c.objects_count; ++i) { 51 | solveContact(atom_idx, c.objects[i]); 52 | } 53 | } 54 | 55 | void processCell(const CollisionCell& c, uint32_t index) 56 | { 57 | for (uint32_t i{0}; i < c.objects_count; ++i) { 58 | const uint32_t atom_idx = c.objects[i]; 59 | checkAtomCellCollisions(atom_idx, grid.data[index - 1]); 60 | checkAtomCellCollisions(atom_idx, grid.data[index]); 61 | checkAtomCellCollisions(atom_idx, grid.data[index + 1]); 62 | checkAtomCellCollisions(atom_idx, grid.data[index + grid.height - 1]); 63 | checkAtomCellCollisions(atom_idx, grid.data[index + grid.height ]); 64 | checkAtomCellCollisions(atom_idx, grid.data[index + grid.height + 1]); 65 | checkAtomCellCollisions(atom_idx, grid.data[index - grid.height - 1]); 66 | checkAtomCellCollisions(atom_idx, grid.data[index - grid.height ]); 67 | checkAtomCellCollisions(atom_idx, grid.data[index - grid.height + 1]); 68 | } 69 | } 70 | 71 | void solveCollisionThreaded(uint32_t start, uint32_t end) 72 | { 73 | for (uint32_t idx{start}; idx < end; ++idx) { 74 | processCell(grid.data[idx], idx); 75 | } 76 | } 77 | 78 | // Find colliding atoms 79 | void solveCollisions() 80 | { 81 | // Multi-thread grid 82 | const uint32_t thread_count = thread_pool.m_thread_count; 83 | const uint32_t slice_count = thread_count * 2; 84 | const uint32_t slice_size = (grid.width / slice_count) * grid.height; 85 | const uint32_t last_cell = (2 * (thread_count - 1) + 2) * slice_size; 86 | // Find collisions in two passes to avoid data races 87 | 88 | // First collision pass 89 | for (uint32_t i{0}; i < thread_count; ++i) { 90 | thread_pool.addTask([this, i, slice_size]{ 91 | uint32_t const start{2 * i * slice_size}; 92 | uint32_t const end {start + slice_size}; 93 | solveCollisionThreaded(start, end); 94 | }); 95 | } 96 | // Eventually process rest if the world is not divisible by the thread count 97 | if (last_cell < grid.data.size()) { 98 | thread_pool.addTask([this, last_cell]{ 99 | solveCollisionThreaded(last_cell, to(grid.data.size())); 100 | }); 101 | } 102 | thread_pool.waitForCompletion(); 103 | // Second collision pass 104 | for (uint32_t i{0}; i < thread_count; ++i) { 105 | thread_pool.addTask([this, i, slice_size]{ 106 | uint32_t const start{(2 * i + 1) * slice_size}; 107 | uint32_t const end {start + slice_size}; 108 | solveCollisionThreaded(start, end); 109 | }); 110 | } 111 | thread_pool.waitForCompletion(); 112 | } 113 | 114 | // Add a new object to the solver 115 | uint64_t addObject(const PhysicObject& object) 116 | { 117 | return objects.push_back(object); 118 | } 119 | 120 | // Add a new object to the solver 121 | uint64_t createObject(Vec2 pos) 122 | { 123 | return objects.emplace_back(pos); 124 | } 125 | 126 | void update(float dt) 127 | { 128 | // Perform the sub steps 129 | const float sub_dt = dt / static_cast(sub_steps); 130 | for (uint32_t i(sub_steps); i--;) { 131 | addObjectsToGrid(); 132 | solveCollisions(); 133 | updateObjects_multi(sub_dt); 134 | } 135 | } 136 | 137 | void addObjectsToGrid() 138 | { 139 | grid.clear(); 140 | // Safety border to avoid adding object outside the grid 141 | uint32_t i{0}; 142 | for (const PhysicObject& obj : objects.data) { 143 | if (obj.position.x > 1.0f && obj.position.x < world_size.x - 1.0f && 144 | obj.position.y > 1.0f && obj.position.y < world_size.y - 1.0f) { 145 | grid.addAtom(to(obj.position.x), to(obj.position.y), i); 146 | } 147 | ++i; 148 | } 149 | } 150 | 151 | void updateObjects_multi(float dt) 152 | { 153 | thread_pool.dispatch(to(objects.size()), [&](uint32_t start, uint32_t end){ 154 | for (uint32_t i{start}; i < end; ++i) { 155 | PhysicObject& obj = objects.data[i]; 156 | // Add gravity 157 | obj.acceleration += gravity; 158 | // Apply Verlet integration 159 | obj.update(dt); 160 | // Apply map borders collisions 161 | const float margin = 2.0f; 162 | if (obj.position.x > world_size.x - margin) { 163 | obj.position.x = world_size.x - margin; 164 | } else if (obj.position.x < margin) { 165 | obj.position.x = margin; 166 | } 167 | if (obj.position.y > world_size.y - margin) { 168 | obj.position.y = world_size.y - margin; 169 | } else if (obj.position.y < margin) { 170 | obj.position.y = margin; 171 | } 172 | } 173 | }); 174 | } 175 | }; 176 | -------------------------------------------------------------------------------- /src/renderer/renderer.cpp: -------------------------------------------------------------------------------- 1 | #include "renderer.hpp" 2 | 3 | 4 | Renderer::Renderer(PhysicSolver& solver_, tp::ThreadPool& tp) 5 | : solver{solver_} 6 | , world_va{sf::Quads, 4} 7 | , objects_va{sf::Quads} 8 | , thread_pool{tp} 9 | { 10 | initializeWorldVA(); 11 | 12 | object_texture.loadFromFile("res/circle.png"); 13 | object_texture.generateMipmap(); 14 | object_texture.setSmooth(true); 15 | } 16 | 17 | void Renderer::render(RenderContext& context) 18 | { 19 | renderHUD(context); 20 | context.draw(world_va); 21 | 22 | sf::RenderStates states; 23 | states.texture = &object_texture; 24 | context.draw(world_va, states); 25 | // Particles 26 | updateParticlesVA(); 27 | context.draw(objects_va, states); 28 | } 29 | 30 | void Renderer::initializeWorldVA() 31 | { 32 | world_va[0].position = {0.0f , 0.0f}; 33 | world_va[1].position = {solver.world_size.x, 0.0f}; 34 | world_va[2].position = {solver.world_size.x, solver.world_size.y}; 35 | world_va[3].position = {0.0f , solver.world_size.y}; 36 | 37 | const uint8_t level = 50; 38 | const sf::Color background_color{level, level, level}; 39 | world_va[0].color = background_color; 40 | world_va[1].color = background_color; 41 | world_va[2].color = background_color; 42 | world_va[3].color = background_color; 43 | } 44 | 45 | void Renderer::updateParticlesVA() 46 | { 47 | objects_va.resize(solver.objects.size() * 4); 48 | 49 | const float texture_size = 1024.0f; 50 | const float radius = 0.5f; 51 | thread_pool.dispatch(to(solver.objects.size()), [&](uint32_t start, uint32_t end) { 52 | for (uint32_t i{start}; i < end; ++i) { 53 | const PhysicObject& object = solver.objects.data[i]; 54 | const uint32_t idx = i << 2; 55 | objects_va[idx + 0].position = object.position + Vec2{-radius, -radius}; 56 | objects_va[idx + 1].position = object.position + Vec2{ radius, -radius}; 57 | objects_va[idx + 2].position = object.position + Vec2{ radius, radius}; 58 | objects_va[idx + 3].position = object.position + Vec2{-radius, radius}; 59 | objects_va[idx + 0].texCoords = {0.0f , 0.0f}; 60 | objects_va[idx + 1].texCoords = {texture_size, 0.0f}; 61 | objects_va[idx + 2].texCoords = {texture_size, texture_size}; 62 | objects_va[idx + 3].texCoords = {0.0f , texture_size}; 63 | 64 | const sf::Color color = object.color; 65 | objects_va[idx + 0].color = color; 66 | objects_va[idx + 1].color = color; 67 | objects_va[idx + 2].color = color; 68 | objects_va[idx + 3].color = color; 69 | } 70 | }); 71 | } 72 | 73 | void Renderer::renderHUD(RenderContext&) 74 | { 75 | // HUD 76 | /*const float margin = 20.0f; 77 | float current_y = margin; 78 | text_time.setString("Simulation time: " + toString(phys_time.get()) + "ms"); 79 | text_time.setPosition({margin, current_y}); 80 | current_y += text_time.getBounds().y + 0.5f * margin; 81 | context.renderToHUD(text_time, RenderContext::Mode::Normal); 82 | 83 | text_objects.setString("Objects: " + toString(simulation.solver.objects.size())); 84 | text_objects.setPosition({margin, current_y}); 85 | current_y += text_objects.getBounds().y + 0.5f * margin; 86 | context.renderToHUD(text_objects, RenderContext::Mode::Normal);*/ 87 | } 88 | -------------------------------------------------------------------------------- /src/renderer/renderer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "physics/physics.hpp" 4 | #include "engine/window_context_handler.hpp" 5 | 6 | 7 | struct Renderer 8 | { 9 | PhysicSolver& solver; 10 | 11 | sf::VertexArray world_va; 12 | sf::VertexArray objects_va; 13 | sf::Texture object_texture; 14 | 15 | tp::ThreadPool& thread_pool; 16 | 17 | explicit 18 | Renderer(PhysicSolver& solver_, tp::ThreadPool& tp); 19 | 20 | void render(RenderContext& context); 21 | 22 | void initializeWorldVA(); 23 | 24 | void updateParticlesVA(); 25 | 26 | void renderHUD(RenderContext& context); 27 | }; 28 | -------------------------------------------------------------------------------- /src/thread_pool/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 | std::lock_guard lock_guard{m_mutex}; 23 | m_tasks.push(std::forward(callback)); 24 | m_remaining_tasks++; 25 | } 26 | 27 | void getTask(std::function& target_callback) 28 | { 29 | { 30 | std::lock_guard lock_guard{m_mutex}; 31 | if (m_tasks.empty()) { 32 | return; 33 | } 34 | target_callback = std::move(m_tasks.front()); 35 | m_tasks.pop(); 36 | } 37 | } 38 | 39 | static void wait() 40 | { 41 | std::this_thread::yield(); 42 | } 43 | 44 | void waitForCompletion() const 45 | { 46 | while (m_remaining_tasks > 0) { 47 | wait(); 48 | } 49 | } 50 | 51 | void workDone() 52 | { 53 | m_remaining_tasks--; 54 | } 55 | }; 56 | 57 | struct Worker 58 | { 59 | uint32_t m_id = 0; 60 | std::thread m_thread; 61 | std::function m_task = nullptr; 62 | bool m_running = true; 63 | TaskQueue* m_queue = nullptr; 64 | 65 | Worker() = default; 66 | 67 | Worker(TaskQueue& queue, uint32_t id) 68 | : m_id{id} 69 | , m_queue{&queue} 70 | { 71 | m_thread = std::thread([this](){ 72 | run(); 73 | }); 74 | } 75 | 76 | void run() 77 | { 78 | while (m_running) { 79 | m_queue->getTask(m_task); 80 | if (m_task == nullptr) { 81 | TaskQueue::wait(); 82 | } else { 83 | m_task(); 84 | m_queue->workDone(); 85 | m_task = nullptr; 86 | } 87 | } 88 | } 89 | 90 | void stop() 91 | { 92 | m_running = false; 93 | m_thread.join(); 94 | } 95 | }; 96 | 97 | struct ThreadPool 98 | { 99 | uint32_t m_thread_count = 0; 100 | TaskQueue m_queue; 101 | std::vector m_workers; 102 | 103 | explicit 104 | ThreadPool(uint32_t thread_count) 105 | : m_thread_count{thread_count} 106 | { 107 | m_workers.reserve(thread_count); 108 | for (uint32_t i{thread_count}; i--;) { 109 | m_workers.emplace_back(m_queue, static_cast(m_workers.size())); 110 | } 111 | } 112 | 113 | virtual ~ThreadPool() 114 | { 115 | for (Worker& worker : m_workers) { 116 | worker.stop(); 117 | } 118 | } 119 | 120 | template 121 | void addTask(TCallback&& callback) 122 | { 123 | m_queue.addTask(std::forward(callback)); 124 | } 125 | 126 | void waitForCompletion() const 127 | { 128 | m_queue.waitForCompletion(); 129 | } 130 | 131 | template 132 | void dispatch(uint32_t element_count, TCallback&& callback) 133 | { 134 | const uint32_t batch_size = element_count / m_thread_count; 135 | for (uint32_t i{0}; i < m_thread_count; ++i) { 136 | const uint32_t start = batch_size * i; 137 | const uint32_t end = start + batch_size; 138 | addTask([start, end, &callback](){ callback(start, end); }); 139 | } 140 | 141 | if (batch_size * m_thread_count < element_count) { 142 | const uint32_t start = batch_size * m_thread_count; 143 | callback(start, element_count); 144 | } 145 | 146 | waitForCompletion(); 147 | } 148 | }; 149 | 150 | } 151 | --------------------------------------------------------------------------------