From 9ab96c59030acc7ef9ba9ba822a8b149b2d972ee Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Sat, 28 Feb 2026 22:30:00 +0100 Subject: [PATCH] rename files based on their classes --- CMakeLists.txt | 21 +- include/config.hpp | 2 +- include/{distance.hpp => graph_distances.hpp} | 0 include/{input.hpp => input_handler.hpp} | 2 +- include/mass_spring_system.hpp | 114 +++++ include/{camera.hpp => orbit_camera.hpp} | 0 include/physics.hpp | 205 --------- include/puzzle.hpp | 12 +- include/renderer.hpp | 6 +- include/state_manager.hpp | 6 +- include/threaded_physics.hpp | 95 ++++ include/user_interface.hpp | 6 +- include/util.hpp | 10 +- src/{distance.cpp => graph_distances.cpp} | 4 +- src/{input.cpp => input_handler.cpp} | 2 +- src/main.cpp | 7 +- src/mass_spring_system.cpp | 231 ++++++++++ src/{camera.cpp => orbit_camera.cpp} | 2 +- src/physics.cpp | 411 ------------------ src/state_manager.cpp | 2 +- src/threaded_physics.cpp | 194 +++++++++ src/user_interface.cpp | 2 +- 22 files changed, 677 insertions(+), 657 deletions(-) rename include/{distance.hpp => graph_distances.hpp} (100%) rename include/{input.hpp => input_handler.hpp} (99%) create mode 100644 include/mass_spring_system.hpp rename include/{camera.hpp => orbit_camera.hpp} (100%) delete mode 100644 include/physics.hpp create mode 100644 include/threaded_physics.hpp rename src/{distance.cpp => graph_distances.cpp} (98%) rename src/{input.cpp => input_handler.cpp} (99%) create mode 100644 src/mass_spring_system.cpp rename src/{camera.cpp => orbit_camera.cpp} (98%) delete mode 100644 src/physics.cpp create mode 100644 src/threaded_physics.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b50b26d..3d015f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,17 +51,18 @@ message("-- CMAKE_CXX_FLAGS_RELEASE: ${CMAKE_CXX_FLAGS_RELEASE}") # Headers + Sources include_directories(include) set(SOURCES - src/main.cpp - src/camera.cpp - src/renderer.cpp - src/octree.cpp - src/physics.cpp - src/puzzle.cpp - src/distance.cpp - src/state_manager.cpp - src/input.cpp - src/user_interface.cpp src/backward.cpp + src/graph_distances.cpp + src/input_handler.cpp + src/main.cpp + src/mass_spring_system.cpp + src/octree.cpp + src/orbit_camera.cpp + src/threaded_physics.cpp + src/puzzle.cpp + src/renderer.cpp + src/state_manager.cpp + src/user_interface.cpp ) # Main target diff --git a/include/config.hpp b/include/config.hpp index 3df0cbb..2d867cf 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -72,4 +72,4 @@ constexpr Color BLOCK_COLOR = DARKBLUE; constexpr Color TARGET_BLOCK_COLOR = RED; constexpr Color WALL_COLOR = BLACK; -#endif +#endif \ No newline at end of file diff --git a/include/distance.hpp b/include/graph_distances.hpp similarity index 100% rename from include/distance.hpp rename to include/graph_distances.hpp diff --git a/include/input.hpp b/include/input_handler.hpp similarity index 99% rename from include/input.hpp rename to include/input_handler.hpp index a70e655..2a8441c 100644 --- a/include/input.hpp +++ b/include/input_handler.hpp @@ -1,7 +1,7 @@ #ifndef INPUT_HPP_ #define INPUT_HPP_ -#include "camera.hpp" +#include "orbit_camera.hpp" #include "state_manager.hpp" #include diff --git a/include/mass_spring_system.hpp b/include/mass_spring_system.hpp new file mode 100644 index 0000000..25dc965 --- /dev/null +++ b/include/mass_spring_system.hpp @@ -0,0 +1,114 @@ +#ifndef MASS_SPRING_SYSTEM_HPP_ +#define MASS_SPRING_SYSTEM_HPP_ + +#include "octree.hpp" +#include "util.hpp" +#include "config.hpp" + +#include +#include + +#ifdef THREADPOOL +#if defined(_WIN32) +#define NOGDI // All GDI defines and routines +#define NOUSER // All USER defines and routines +#endif +#define BS_THREAD_POOL_NATIVE_EXTENSIONS +#include +#if defined(_WIN32) // raylib uses these names as function parameters +#undef near +#undef far +#endif +#endif + +class mass_spring_system +{ +public: + class mass + { + public: + Vector3 position = Vector3Zero(); + Vector3 previous_position = Vector3Zero(); // for verlet integration + Vector3 velocity = Vector3Zero(); + Vector3 force = Vector3Zero(); + + public: + mass() = delete; + + explicit mass(const Vector3 _position) + : position(_position), previous_position(_position) {} + + public: + auto clear_force() -> void; + auto calculate_velocity(float delta_time) -> void; + auto calculate_position(float delta_time) -> void; + auto verlet_update(float delta_time) -> void; + }; + + class spring + { + public: + size_t a; + size_t b; + + public: + spring(const size_t _a, const size_t _b) + : a(_a), b(_b) {} + + public: + static auto calculate_spring_force(mass& _a, mass& _b) -> void; + }; + +private: + #ifdef THREADPOOL + BS::thread_pool<> threads; + #endif + +public: + octree tree; + + // This is the main ownership of all the states/masses/springs. + std::vector masses; + std::vector springs; + +public: + mass_spring_system() + #ifdef THREADPOOL + : threads(std::thread::hardware_concurrency() - 1, set_thread_name) + #endif + { + infoln("Using Barnes-Hut + Octree repulsion force calculation."); + + #ifdef THREADPOOL + infoln("Thread-pool: {} threads.", threads.get_thread_count()); + #else + infoln("Thread-pool: Disabled."); + #endif + } + + mass_spring_system(const mass_spring_system& copy) = delete; + auto operator=(const mass_spring_system& copy) -> mass_spring_system& = delete; + mass_spring_system(mass_spring_system& move) = delete; + auto operator=(mass_spring_system&& move) -> mass_spring_system& = delete; + +private: + #ifdef THREADPOOL + static auto set_thread_name(size_t idx) -> void; + #endif + + auto build_octree() -> void; + +public: + auto clear() -> void; + auto add_mass() -> void; + auto add_spring(size_t a, size_t b) -> void; + + auto clear_forces() -> void; + auto calculate_spring_forces() -> void; + auto calculate_repulsion_forces() -> void; + auto verlet_update(float delta_time) -> void; + + auto center_masses() -> void; +}; + +#endif \ No newline at end of file diff --git a/include/camera.hpp b/include/orbit_camera.hpp similarity index 100% rename from include/camera.hpp rename to include/orbit_camera.hpp diff --git a/include/physics.hpp b/include/physics.hpp deleted file mode 100644 index c2f8d26..0000000 --- a/include/physics.hpp +++ /dev/null @@ -1,205 +0,0 @@ -#ifndef PHYSICS_HPP_ -#define PHYSICS_HPP_ - -#include "config.hpp" -#include "octree.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "util.hpp" - -#ifdef THREADPOOL - #if defined(_WIN32) - #define NOGDI // All GDI defines and routines - #define NOUSER // All USER defines and routines - #endif - #define BS_THREAD_POOL_NATIVE_EXTENSIONS - #include - #if defined(_WIN32) // raylib uses these names as function parameters - #undef near - #undef far - #endif -#endif - -#ifdef TRACY - #include -#endif - -class mass -{ -public: - Vector3 position = Vector3Zero(); - Vector3 previous_position = Vector3Zero(); // for verlet integration - Vector3 velocity = Vector3Zero(); - Vector3 force = Vector3Zero(); - -public: - mass() = delete; - - explicit mass(const Vector3 _position) : position(_position), previous_position(_position) - {} - -public: - auto clear_force() -> void; - auto calculate_velocity(float delta_time) -> void; - auto calculate_position(float delta_time) -> void; - auto verlet_update(float delta_time) -> void; -}; - -class spring -{ -public: - size_t a; - size_t b; - -public: - spring(const size_t _a, const size_t _b) : a(_a), b(_b) - {} - -public: - static auto calculate_spring_force(mass& _a, mass& _b) -> void; -}; - -class mass_spring_system -{ -private: -#ifdef THREADPOOL - BS::thread_pool<> threads; -#endif - -public: - octree tree; - - // This is the main ownership of all the states/masses/springs. - std::vector masses; - std::vector springs; - -public: - mass_spring_system() -#ifdef THREADPOOL - : threads(std::thread::hardware_concurrency() - 1, set_thread_name) -#endif - { - infoln("Using Barnes-Hut + Octree repulsion force calculation."); - -#ifdef THREADPOOL - infoln("Thread-pool: {} threads.", threads.get_thread_count()); -#else - infoln("Thread-pool: Disabled."); -#endif - } - - mass_spring_system(const mass_spring_system& copy) = delete; - auto operator=(const mass_spring_system& copy) -> mass_spring_system& = delete; - mass_spring_system(mass_spring_system& move) = delete; - auto operator=(mass_spring_system&& move) -> mass_spring_system& = delete; - -private: -#ifdef THREADPOOL - static auto set_thread_name(size_t idx) -> void; -#endif - - auto build_octree() -> void; - -public: - auto clear() -> void; - auto add_mass() -> void; - auto add_spring(size_t a, size_t b) -> void; - - auto clear_forces() -> void; - auto calculate_spring_forces() -> void; - auto calculate_repulsion_forces() -> void; - auto verlet_update(float delta_time) -> void; - - auto center_masses() -> void; -}; - -class threaded_physics -{ - struct add_mass - {}; - - struct add_spring - { - size_t a; - size_t b; - }; - - struct clear_graph - {}; - - using command = std::variant; - - struct physics_state - { -#ifdef TRACY - TracyLockable(std::mutex, command_mtx); -#else - std::mutex command_mtx; -#endif - std::queue pending_commands; - -#ifdef TRACY - TracyLockable(std::mutex, data_mtx); -#else - std::mutex data_mtx; -#endif - std::condition_variable_any data_ready_cnd; - std::condition_variable_any data_consumed_cnd; - Vector3 mass_center = Vector3Zero(); - int ups = 0; - size_t mass_count = 0; // For debug - size_t spring_count = 0; // For debug - std::vector masses; // Read by renderer - bool data_ready = false; - bool data_consumed = true; - - std::atomic running{true}; - }; - -private: - std::thread physics; - -public: - physics_state state; - -public: - threaded_physics() : physics(physics_thread, std::ref(state)) - {} - - threaded_physics(const threaded_physics& copy) = delete; - auto operator=(const threaded_physics& copy) -> threaded_physics& = delete; - threaded_physics(threaded_physics&& move) = delete; - auto operator=(threaded_physics&& move) -> threaded_physics& = delete; - - ~threaded_physics() - { - state.running = false; - state.data_ready_cnd.notify_all(); - state.data_consumed_cnd.notify_all(); - physics.join(); - } - -private: - static auto physics_thread(physics_state& state) -> void; - -public: - auto add_mass_cmd() -> void; - - auto add_spring_cmd(size_t a, size_t b) -> void; - - auto clear_cmd() -> void; - - auto add_mass_springs_cmd(size_t num_masses, - const std::vector>& springs) -> void; -}; - -#endif diff --git a/include/puzzle.hpp b/include/puzzle.hpp index 2991e91..c9a0131 100644 --- a/include/puzzle.hpp +++ b/include/puzzle.hpp @@ -10,14 +10,6 @@ #include #include -enum direction -{ - nor = 1 << 0, - eas = 1 << 1, - sou = 1 << 2, - wes = 1 << 3, -}; - // A state is represented by a string "MWHXYblocks", where M is "R" // (restricted) or "F" (free), W is the board width, H is the board height, X // is the target block x goal, Y is the target block y goal and blocks is an @@ -52,7 +44,7 @@ public: const bool _target = false, const bool _immovable = false) : x(_x), y(_y), width(_width), height(_height), target(_target), immovable(_immovable) { - if (_x < 0 || _x + _width > 9 || _y < 0 || _y + _height > 9) { + if (_x < 0 || _x + _width > MAX_WIDTH || _y < 0 || _y + _height > MAX_HEIGHT) { errln("Block must fit in a 9x9 board!"); exit(1); } @@ -348,4 +340,4 @@ struct std::equal_to> using win_condition = std::function; -#endif +#endif \ No newline at end of file diff --git a/include/renderer.hpp b/include/renderer.hpp index 4abe1e5..0e365b2 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -1,9 +1,9 @@ #ifndef RENDERER_HPP_ #define RENDERER_HPP_ -#include "camera.hpp" +#include "orbit_camera.hpp" #include "config.hpp" -#include "input.hpp" +#include "input_handler.hpp" #include "state_manager.hpp" #include "user_interface.hpp" @@ -106,4 +106,4 @@ public: size_t spring_count) -> void; }; -#endif +#endif \ No newline at end of file diff --git a/include/state_manager.hpp b/include/state_manager.hpp index 75da808..f95c010 100644 --- a/include/state_manager.hpp +++ b/include/state_manager.hpp @@ -1,8 +1,8 @@ #ifndef STATE_MANAGER_HPP_ #define STATE_MANAGER_HPP_ -#include "distance.hpp" -#include "physics.hpp" +#include "graph_distances.hpp" +#include "threaded_physics.hpp" #include "puzzle.hpp" #include @@ -151,4 +151,4 @@ public: [[nodiscard]] auto was_edited() const -> bool; }; -#endif +#endif \ No newline at end of file diff --git a/include/threaded_physics.hpp b/include/threaded_physics.hpp new file mode 100644 index 0000000..9fa6601 --- /dev/null +++ b/include/threaded_physics.hpp @@ -0,0 +1,95 @@ +#ifndef PHYSICS_HPP_ +#define PHYSICS_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef TRACY +#include +#endif + +class threaded_physics +{ + struct add_mass {}; + + struct add_spring + { + size_t a; + size_t b; + }; + + struct clear_graph {}; + + using command = std::variant; + + struct physics_state + { + #ifdef TRACY + TracyLockable(std::mutex, command_mtx); + #else + std::mutex command_mtx; + #endif + std::queue pending_commands; + + #ifdef TRACY + TracyLockable(std::mutex, data_mtx); + #else + std::mutex data_mtx; + #endif + std::condition_variable_any data_ready_cnd; + std::condition_variable_any data_consumed_cnd; + Vector3 mass_center = Vector3Zero(); + int ups = 0; + size_t mass_count = 0; // For debug + size_t spring_count = 0; // For debug + std::vector masses; // Read by renderer + bool data_ready = false; + bool data_consumed = true; + + std::atomic running{true}; + }; + +private: + std::thread physics; + +public: + physics_state state; + +public: + threaded_physics() + : physics(physics_thread, std::ref(state)) {} + + threaded_physics(const threaded_physics& copy) = delete; + auto operator=(const threaded_physics& copy) -> threaded_physics& = delete; + threaded_physics(threaded_physics&& move) = delete; + auto operator=(threaded_physics&& move) -> threaded_physics& = delete; + + ~threaded_physics() + { + state.running = false; + state.data_ready_cnd.notify_all(); + state.data_consumed_cnd.notify_all(); + physics.join(); + } + +private: + static auto physics_thread(physics_state& state) -> void; + +public: + auto add_mass_cmd() -> void; + + auto add_spring_cmd(size_t a, size_t b) -> void; + + auto clear_cmd() -> void; + + auto add_mass_springs_cmd(size_t num_masses, const std::vector>& springs) -> void; +}; + +#endif \ No newline at end of file diff --git a/include/user_interface.hpp b/include/user_interface.hpp index 66faba7..a922e89 100644 --- a/include/user_interface.hpp +++ b/include/user_interface.hpp @@ -1,9 +1,9 @@ #ifndef GUI_HPP_ #define GUI_HPP_ -#include "camera.hpp" +#include "orbit_camera.hpp" #include "config.hpp" -#include "input.hpp" +#include "input_handler.hpp" #include "state_manager.hpp" #include @@ -183,4 +183,4 @@ public: auto draw(int fps, int ups, size_t mass_count, size_t spring_count) -> void; }; -#endif +#endif \ No newline at end of file diff --git a/include/util.hpp b/include/util.hpp index 3590319..df62772 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -24,6 +24,14 @@ struct overloads : Ts... using Ts::operator()...; }; +enum direction +{ + nor = 1 << 0, + eas = 1 << 1, + sou = 1 << 2, + wes = 1 << 3, +}; + enum ctrl { reset = 0, @@ -91,4 +99,4 @@ auto errln(std::format_string fmt, Args&&... args) -> void << std::format(fmt, std::forward(args)...) << std::endl; } -#endif +#endif \ No newline at end of file diff --git a/src/distance.cpp b/src/graph_distances.cpp similarity index 98% rename from src/distance.cpp rename to src/graph_distances.cpp index e85b096..1e36fbb 100644 --- a/src/distance.cpp +++ b/src/graph_distances.cpp @@ -1,4 +1,4 @@ -#include "distance.hpp" +#include "graph_distances.hpp" #include @@ -70,4 +70,4 @@ auto graph_distances::get_shortest_path(const size_t source) const -> std::vecto } return path; -} +} \ No newline at end of file diff --git a/src/input.cpp b/src/input_handler.cpp similarity index 99% rename from src/input.cpp rename to src/input_handler.cpp index f93ab00..7e8b084 100644 --- a/src/input.cpp +++ b/src/input_handler.cpp @@ -1,4 +1,4 @@ -#include "input.hpp" +#include "input_handler.hpp" #include "config.hpp" #include diff --git a/src/main.cpp b/src/main.cpp index 6023894..6abb953 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,8 +3,9 @@ #include #include "config.hpp" -#include "input.hpp" -#include "physics.hpp" +#include "input_handler.hpp" +#include "mass_spring_system.hpp" +#include "threaded_physics.hpp" #include "renderer.hpp" #include "state_manager.hpp" #include "user_interface.hpp" @@ -136,7 +137,7 @@ auto main(int argc, char* argv[]) -> int // Update the camera after the physics, so target lock is smooth size_t current_index = state.get_current_index(); if (masses.size() > current_index) { - const mass& current_mass = mass(masses.at(current_index)); + const mass_spring_system::mass& current_mass = mass_spring_system::mass(masses.at(current_index)); camera.update(current_mass.position, mass_center, input.camera_lock, input.camera_mass_center_lock); } diff --git a/src/mass_spring_system.cpp b/src/mass_spring_system.cpp new file mode 100644 index 0000000..bc065f6 --- /dev/null +++ b/src/mass_spring_system.cpp @@ -0,0 +1,231 @@ +#include "mass_spring_system.hpp" +#include "config.hpp" + +#include + +#ifdef TRACY +#include +#endif + +auto mass_spring_system::mass::clear_force() -> void +{ + force = Vector3Zero(); +} + +auto mass_spring_system::mass::calculate_velocity(const float delta_time) -> void +{ + const Vector3 acceleration = Vector3Scale(force, 1.0 / MASS); + const Vector3 temp = Vector3Scale(acceleration, delta_time); + velocity = Vector3Add(velocity, temp); +} + +auto mass_spring_system::mass::calculate_position(const float delta_time) -> void +{ + previous_position = position; + + const Vector3 temp = Vector3Scale(velocity, delta_time); + position = Vector3Add(position, temp); +} + +auto mass_spring_system::mass::verlet_update(const float delta_time) -> void +{ + const Vector3 acceleration = Vector3Scale(force, 1.0 / MASS); + const Vector3 temp_position = position; + + Vector3 displacement = Vector3Subtract(position, previous_position); + const Vector3 accel_term = Vector3Scale(acceleration, delta_time * delta_time); + + // Minimal dampening + displacement = Vector3Scale(displacement, 1.0 - VERLET_DAMPENING); + + position = Vector3Add(Vector3Add(position, displacement), accel_term); + previous_position = temp_position; +} + +auto mass_spring_system::spring::calculate_spring_force(mass& _a, mass& _b) -> void +{ + // TODO: Use a bungee force here instead of springs, since we already have global repulsion? + const Vector3 delta_position = Vector3Subtract(_a.position, _b.position); + const float current_length = Vector3Length(delta_position); + const Vector3 delta_velocity = Vector3Subtract(_a.velocity, _b.velocity); + + const float hooke = SPRING_CONSTANT * (current_length - REST_LENGTH); + const float dampening = DAMPENING_CONSTANT * Vector3DotProduct(delta_velocity, delta_position) / current_length; + + const Vector3 force_a = Vector3Scale(delta_position, -(hooke + dampening) / current_length); + const Vector3 force_b = Vector3Scale(force_a, -1.0); + + _a.force = Vector3Add(_a.force, force_a); + _b.force = Vector3Add(_b.force, force_b); +} + +auto mass_spring_system::clear() -> void +{ + masses.clear(); + springs.clear(); + tree.nodes.clear(); +} + +auto mass_spring_system::add_mass() -> void +{ + // Adding all positions to (0, 0, 0) breaks the octree + + // Done when adding springs + // Vector3 position{ + // static_cast(GetRandomValue(-100, 100)), static_cast(GetRandomValue(-100, + // 100)), static_cast(GetRandomValue(-100, 100)) + // }; + // position = Vector3Scale(Vector3Normalize(position), REST_LENGTH * 2.0); + + masses.emplace_back(Vector3Zero()); +} + +auto mass_spring_system::add_spring(size_t a, size_t b) -> void +{ + // Update masses to be located along a random walk when adding the springs + const mass& mass_a = masses.at(a); + mass& mass_b = masses.at(b); + + Vector3 offset{ + static_cast(GetRandomValue(-100, 100)), static_cast(GetRandomValue(-100, 100)), + static_cast(GetRandomValue(-100, 100)) + }; + offset = Vector3Normalize(offset) * REST_LENGTH; + + // If the offset moves the mass closer to the current center of mass, flip it + if (!tree.nodes.empty()) { + const Vector3 mass_center_direction = Vector3Subtract(mass_a.position, tree.nodes.at(0).mass_center); + const float mass_center_distance = Vector3Length(mass_center_direction); + + if (mass_center_distance > 0 && Vector3DotProduct(offset, mass_center_direction) < 0.0f) { + offset = Vector3Negate(offset); + } + } + + mass_b.position = mass_a.position + offset; + mass_b.previous_position = mass_b.position; + + // infoln("Adding spring: ({}, {}, {})->({}, {}, {})", mass_a.position.x, mass_a.position.y, + // mass_a.position.z, + // mass_b.position.x, mass_b.position.y, mass_b.position.z); + + springs.emplace_back(a, b); +} + +auto mass_spring_system::clear_forces() -> void +{ + #ifdef TRACY + ZoneScoped; + #endif + + for (auto& m : masses) { + m.clear_force(); + } +} + +auto mass_spring_system::calculate_spring_forces() -> void +{ + #ifdef TRACY + ZoneScoped; + #endif + + for (const auto s : springs) { + mass& a = masses.at(s.a); + mass& b = masses.at(s.b); + spring::calculate_spring_force(a, b); + } +} + +#ifdef THREADPOOL +auto mass_spring_system::set_thread_name(size_t idx) -> void +{ + BS::this_thread::set_os_thread_name(std::format("bh-worker-{}", idx)); +} +#endif + +auto mass_spring_system::build_octree() -> void +{ + #ifdef TRACY + ZoneScoped; + #endif + + tree.nodes.clear(); + tree.nodes.reserve(masses.size() * 2); + + // Compute bounding box around all masses + Vector3 min{FLT_MAX, FLT_MAX, FLT_MAX}; + Vector3 max{-FLT_MAX, -FLT_MAX, -FLT_MAX}; + for (const auto& m : masses) { + min.x = std::min(min.x, m.position.x); + max.x = std::max(max.x, m.position.x); + min.y = std::min(min.y, m.position.y); + max.y = std::max(max.y, m.position.y); + min.z = std::min(min.z, m.position.z); + max.z = std::max(max.z, m.position.z); + } + + // Pad the bounding box + constexpr float pad = 1.0; + min = Vector3Subtract(min, Vector3Scale(Vector3One(), pad)); + max = Vector3Add(max, Vector3Scale(Vector3One(), pad)); + + // Make it cubic (so subdivisions are balanced) + const float max_extent = std::max({max.x - min.x, max.y - min.y, max.z - min.z}); + max = Vector3Add(min, Vector3Scale(Vector3One(), max_extent)); + + // Root node spans the entire area + const int root = tree.create_empty_leaf(min, max); + + for (size_t i = 0; i < masses.size(); ++i) { + tree.insert(root, static_cast(i), masses[i].position, MASS, 0); + } +} + +auto mass_spring_system::calculate_repulsion_forces() -> void +{ + #ifdef TRACY + ZoneScoped; + #endif + + build_octree(); + + auto solve_octree = [&](const int i) + { + const Vector3 force = tree.calculate_force(0, masses[i].position); + masses[i].force = Vector3Add(masses[i].force, force); + }; + + // Calculate forces using Barnes-Hut + #ifdef THREADPOOL + const BS::multi_future loop_future = threads.submit_loop(0, masses.size(), solve_octree, 256); + loop_future.wait(); + #else + for (size_t i = 0; i < masses.size(); ++i) { + solve_octree(i); + } + #endif +} + +auto mass_spring_system::verlet_update(const float delta_time) -> void +{ + #ifdef TRACY + ZoneScoped; + #endif + + for (auto& m : masses) { + m.verlet_update(delta_time); + } +} + +auto mass_spring_system::center_masses() -> void +{ + Vector3 mean = Vector3Zero(); + for (const auto& m : masses) { + mean += m.position; + } + mean /= static_cast(masses.size()); + + for (auto& m : masses) { + m.position -= mean; + } +} \ No newline at end of file diff --git a/src/camera.cpp b/src/orbit_camera.cpp similarity index 98% rename from src/camera.cpp rename to src/orbit_camera.cpp index 721e935..d607d3b 100644 --- a/src/camera.cpp +++ b/src/orbit_camera.cpp @@ -1,4 +1,4 @@ -#include "camera.hpp" +#include "orbit_camera.hpp" #include "config.hpp" #include diff --git a/src/physics.cpp b/src/physics.cpp deleted file mode 100644 index b65a118..0000000 --- a/src/physics.cpp +++ /dev/null @@ -1,411 +0,0 @@ -#include "physics.hpp" -#include "config.hpp" - -#include -#include -#include -#include -#include -#include -#include - -#ifdef TRACY - #include -#endif - -auto mass::clear_force() -> void -{ - force = Vector3Zero(); -} - -auto mass::calculate_velocity(const float delta_time) -> void -{ - const Vector3 acceleration = Vector3Scale(force, 1.0 / MASS); - const Vector3 temp = Vector3Scale(acceleration, delta_time); - velocity = Vector3Add(velocity, temp); -} - -auto mass::calculate_position(const float delta_time) -> void -{ - previous_position = position; - - const Vector3 temp = Vector3Scale(velocity, delta_time); - position = Vector3Add(position, temp); -} - -auto mass::verlet_update(const float delta_time) -> void -{ - const Vector3 acceleration = Vector3Scale(force, 1.0 / MASS); - const Vector3 temp_position = position; - - Vector3 displacement = Vector3Subtract(position, previous_position); - const Vector3 accel_term = Vector3Scale(acceleration, delta_time * delta_time); - - // Minimal dampening - displacement = Vector3Scale(displacement, 1.0 - VERLET_DAMPENING); - - position = Vector3Add(Vector3Add(position, displacement), accel_term); - previous_position = temp_position; -} - -auto spring::calculate_spring_force(mass& _a, mass& _b) -> void -{ - // TODO: Use a bungee force here instead of springs, since we already have global repulsion? - const Vector3 delta_position = Vector3Subtract(_a.position, _b.position); - const float current_length = Vector3Length(delta_position); - const float inv_current_length = 1.0f / current_length; - const Vector3 delta_velocity = Vector3Subtract(_a.velocity, _b.velocity); - - const float hooke = SPRING_CONSTANT * (current_length - REST_LENGTH); - const float dampening = - DAMPENING_CONSTANT * Vector3DotProduct(delta_velocity, delta_position) * inv_current_length; - - const Vector3 force_a = Vector3Scale(delta_position, -(hooke + dampening) * inv_current_length); - const Vector3 force_b = Vector3Scale(force_a, -1.0); - - _a.force = Vector3Add(_a.force, force_a); - _b.force = Vector3Add(_b.force, force_b); -} - -auto mass_spring_system::add_mass() -> void -{ - // Adding all positions to (0, 0, 0) breaks the octree - - // Done when adding springs - // Vector3 position{ - // static_cast(GetRandomValue(-100, 100)), static_cast(GetRandomValue(-100, - // 100)), static_cast(GetRandomValue(-100, 100)) - // }; - // position = Vector3Scale(Vector3Normalize(position), REST_LENGTH * 2.0); - - masses.emplace_back(Vector3Zero()); -} - -auto mass_spring_system::add_spring(size_t a, size_t b) -> void -{ - // Update masses to be located along a random walk when adding the springs - const mass& mass_a = masses.at(a); - mass& mass_b = masses.at(b); - - Vector3 offset{static_cast(GetRandomValue(-100, 100)), - static_cast(GetRandomValue(-100, 100)), - static_cast(GetRandomValue(-100, 100))}; - offset = Vector3Normalize(offset) * REST_LENGTH; - - // If the offset moves the mass closer to the current center of mass, flip it - if (!tree.nodes.empty()) { - const Vector3 mass_center_direction = - Vector3Subtract(mass_a.position, tree.nodes.at(0).mass_center); - const float mass_center_distance = Vector3Length(mass_center_direction); - - if (mass_center_distance > 0 && Vector3DotProduct(offset, mass_center_direction) < 0.0f) { - offset = Vector3Negate(offset); - } - } - - mass_b.position = mass_a.position + offset; - mass_b.previous_position = mass_b.position; - - // infoln("Adding spring: ({}, {}, {})->({}, {}, {})", mass_a.position.x, mass_a.position.y, - // mass_a.position.z, - // mass_b.position.x, mass_b.position.y, mass_b.position.z); - - springs.emplace_back(a, b); -} - -auto mass_spring_system::clear() -> void -{ - masses.clear(); - springs.clear(); - tree.nodes.clear(); -} - -auto mass_spring_system::clear_forces() -> void -{ -#ifdef TRACY - ZoneScoped; -#endif - - for (auto& mass : masses) { - mass.clear_force(); - } -} - -auto mass_spring_system::calculate_spring_forces() -> void -{ -#ifdef TRACY - ZoneScoped; -#endif - - for (const auto s : springs) { - mass& a = masses.at(s.a); - mass& b = masses.at(s.b); - spring::calculate_spring_force(a, b); - } -} - -#ifdef THREADPOOL -auto mass_spring_system::set_thread_name(size_t idx) -> void -{ - BS::this_thread::set_os_thread_name(std::format("bh-worker-{}", idx)); -} -#endif - -auto mass_spring_system::build_octree() -> void -{ -#ifdef TRACY - ZoneScoped; -#endif - - tree.nodes.clear(); - tree.nodes.reserve(masses.size() * 2); - - // Compute bounding box around all masses - Vector3 min{FLT_MAX, FLT_MAX, FLT_MAX}; - Vector3 max{-FLT_MAX, -FLT_MAX, -FLT_MAX}; - for (const auto& mass : masses) { - min.x = std::min(min.x, mass.position.x); - max.x = std::max(max.x, mass.position.x); - min.y = std::min(min.y, mass.position.y); - max.y = std::max(max.y, mass.position.y); - min.z = std::min(min.z, mass.position.z); - max.z = std::max(max.z, mass.position.z); - } - - // Pad the bounding box - constexpr float pad = 1.0; - min = Vector3Subtract(min, Vector3Scale(Vector3One(), pad)); - max = Vector3Add(max, Vector3Scale(Vector3One(), pad)); - - // Make it cubic (so subdivisions are balanced) - const float max_extent = std::max({max.x - min.x, max.y - min.y, max.z - min.z}); - max = Vector3Add(min, Vector3Scale(Vector3One(), max_extent)); - - // Root node spans the entire area - const int root = tree.create_empty_leaf(min, max); - - for (size_t i = 0; i < masses.size(); ++i) { - tree.insert(root, static_cast(i), masses[i].position, MASS, 0); - } -} - -auto mass_spring_system::calculate_repulsion_forces() -> void -{ -#ifdef TRACY - ZoneScoped; -#endif - - build_octree(); - - auto solve_octree = [&](const int i) - { - const Vector3 force = tree.calculate_force(0, masses[i].position); - masses[i].force = Vector3Add(masses[i].force, force); - }; - -// Calculate forces using Barnes-Hut -#ifdef THREADPOOL - const BS::multi_future loop_future = - threads.submit_loop(0, masses.size(), solve_octree, 256); - loop_future.wait(); -#else - for (size_t i = 0; i < masses.size(); ++i) { - solve_octree(i); - } -#endif -} - -auto mass_spring_system::verlet_update(const float delta_time) -> void -{ -#ifdef TRACY - ZoneScoped; -#endif - - for (auto& mass : masses) { - mass.verlet_update(delta_time); - } -} - -auto mass_spring_system::center_masses() -> void -{ - Vector3 mean = Vector3Zero(); - for (const auto& mass : masses) { - mean += mass.position; - } - mean /= static_cast(masses.size()); - - for (auto& mass : masses) { - mass.position -= mean; - } -} - -auto threaded_physics::physics_thread(physics_state& state) -> void -{ -#ifdef THREADPOOL - BS::this_thread::set_os_thread_name("physics"); -#endif - - mass_spring_system mass_springs; - - const auto visitor = overloads{ - [&](const struct add_mass& am) { mass_springs.add_mass(); }, - [&](const struct add_spring& as) { mass_springs.add_spring(as.a, as.b); }, - [&](const struct clear_graph& cg) { mass_springs.clear(); }, - }; - - std::chrono::time_point last = std::chrono::high_resolution_clock::now(); - std::chrono::duration physics_accumulator(0); - std::chrono::duration ups_accumulator(0); - int loop_iterations = 0; - - while (state.running.load()) { -#ifdef TRACY - FrameMarkStart("PhysicsThread"); -#endif - - // Time tracking - std::chrono::time_point now = std::chrono::high_resolution_clock::now(); - const std::chrono::duration deltatime = now - last; - physics_accumulator += deltatime; - ups_accumulator += deltatime; - last = now; - - // Handle queued commands - { -#ifdef TRACY - std::lock_guard lock(state.command_mtx); -#else - std::lock_guard lock(state.command_mtx); -#endif - while (!state.pending_commands.empty()) { - command& cmd = state.pending_commands.front(); - cmd.visit(visitor); - state.pending_commands.pop(); - } - } - - if (mass_springs.masses.empty()) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } - - // Physics update - if (physics_accumulator.count() > TIMESTEP) { - mass_springs.clear_forces(); - mass_springs.calculate_spring_forces(); - mass_springs.calculate_repulsion_forces(); - mass_springs.verlet_update(TIMESTEP * SIM_SPEED); - - // This is only helpful if we're drawing a grid at (0, 0, 0). Otherwise, it's just - // expensive and yields no benefit since we can lock the camera to the center of mass - // cheaply. mass_springs.center_masses(); - - ++loop_iterations; - physics_accumulator -= std::chrono::duration(TIMESTEP); - } - -// Publish the positions for the renderer (copy) -#ifdef TRACY - FrameMarkStart("PhysicsThreadProduceLock"); -#endif - { -#ifdef TRACY - std::unique_lock lock(state.data_mtx); -#else - std::unique_lock lock(state.data_mtx); -#endif - state.data_consumed_cnd.wait(lock, [&] - { return state.data_consumed || !state.running.load(); }); - if (!state.running.load()) { - // Running turned false while we were waiting for the condition - break; - } - - if (ups_accumulator.count() > 1.0) { - // Update each second - state.ups = loop_iterations; - loop_iterations = 0; - ups_accumulator = std::chrono::duration(0); - } - if (mass_springs.tree.nodes.empty()) { - state.mass_center = Vector3Zero(); - } else { - state.mass_center = mass_springs.tree.nodes.at(0).mass_center; - } - - state.masses.clear(); - state.masses.reserve(mass_springs.masses.size()); - for (const auto& mass : mass_springs.masses) { - state.masses.emplace_back(mass.position); - } - - state.mass_count = mass_springs.masses.size(); - state.spring_count = mass_springs.springs.size(); - - state.data_ready = true; - state.data_consumed = false; - } - // Notify the rendering thread that new data is available - state.data_ready_cnd.notify_all(); -#ifdef TRACY - FrameMarkEnd("PhysicsThreadProduceLock"); - - FrameMarkEnd("PhysicsThread"); -#endif - } -} - -auto threaded_physics::add_mass_cmd() -> void -{ - { -#ifdef TRACY - std::lock_guard lock(state.command_mtx); -#else - std::lock_guard lock(state.command_mtx); -#endif - state.pending_commands.emplace(add_mass{}); - } -} - -auto threaded_physics::add_spring_cmd(const size_t a, const size_t b) -> void -{ - { -#ifdef TRACY - std::lock_guard lock(state.command_mtx); -#else - std::lock_guard lock(state.command_mtx); -#endif - state.pending_commands.emplace(add_spring{a, b}); - } -} - -auto threaded_physics::clear_cmd() -> void -{ - { -#ifdef TRACY - std::lock_guard lock(state.command_mtx); -#else - std::lock_guard lock(state.command_mtx); -#endif - state.pending_commands.emplace(clear_graph{}); - } -} - -auto threaded_physics::add_mass_springs_cmd(const size_t num_masses, - const std::vector>& springs) - -> void -{ - { -#ifdef TRACY - std::lock_guard lock(state.command_mtx); -#else - std::lock_guard lock(state.command_mtx); -#endif - for (size_t i = 0; i < num_masses; ++i) { - state.pending_commands.emplace(add_mass{}); - } - for (const auto& [from, to] : springs) { - state.pending_commands.emplace(add_spring{from, to}); - } - } -} diff --git a/src/state_manager.cpp b/src/state_manager.cpp index 298f6c7..b34ab84 100644 --- a/src/state_manager.cpp +++ b/src/state_manager.cpp @@ -1,5 +1,5 @@ #include "state_manager.hpp" -#include "distance.hpp" +#include "graph_distances.hpp" #include "util.hpp" #include diff --git a/src/threaded_physics.cpp b/src/threaded_physics.cpp new file mode 100644 index 0000000..e2df1b3 --- /dev/null +++ b/src/threaded_physics.cpp @@ -0,0 +1,194 @@ +#include "threaded_physics.hpp" +#include "config.hpp" +#include "mass_spring_system.hpp" + +#include +#include +#include +#include +#include + +#ifdef TRACY +#include +#endif + +auto threaded_physics::physics_thread(physics_state& state) -> void +{ + #ifdef THREADPOOL + BS::this_thread::set_os_thread_name("physics"); + #endif + + mass_spring_system mass_springs; + + const auto visitor = overloads{ + [&](const struct add_mass& am) + { + mass_springs.add_mass(); + }, + [&](const struct add_spring& as) + { + mass_springs.add_spring(as.a, as.b); + }, + [&](const struct clear_graph& cg) + { + mass_springs.clear(); + }, + }; + + std::chrono::time_point last = std::chrono::high_resolution_clock::now(); + std::chrono::duration physics_accumulator(0); + std::chrono::duration ups_accumulator(0); + int loop_iterations = 0; + + while (state.running.load()) { + #ifdef TRACY + FrameMarkStart("PhysicsThread"); + #endif + + // Time tracking + std::chrono::time_point now = std::chrono::high_resolution_clock::now(); + const std::chrono::duration deltatime = now - last; + physics_accumulator += deltatime; + ups_accumulator += deltatime; + last = now; + + // Handle queued commands + { + #ifdef TRACY + std::lock_guard lock(state.command_mtx); + #else + std::lock_guard lock(state.command_mtx); + #endif + while (!state.pending_commands.empty()) { + command& cmd = state.pending_commands.front(); + cmd.visit(visitor); + state.pending_commands.pop(); + } + } + + if (mass_springs.masses.empty()) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + + // Physics update + if (physics_accumulator.count() > TIMESTEP) { + mass_springs.clear_forces(); + mass_springs.calculate_spring_forces(); + mass_springs.calculate_repulsion_forces(); + mass_springs.verlet_update(TIMESTEP * SIM_SPEED); + + // This is only helpful if we're drawing a grid at (0, 0, 0). Otherwise, it's just + // expensive and yields no benefit since we can lock the camera to the center of mass + // cheaply. mass_springs.center_masses(); + + ++loop_iterations; + physics_accumulator -= std::chrono::duration(TIMESTEP); + } + + // Publish the positions for the renderer (copy) + #ifdef TRACY + FrameMarkStart("PhysicsThreadProduceLock"); + #endif + { + #ifdef TRACY + std::unique_lock lock(state.data_mtx); + #else + std::unique_lock lock(state.data_mtx); + #endif + state.data_consumed_cnd.wait(lock, [&] + { + return state.data_consumed || !state.running.load(); + }); + if (!state.running.load()) { + // Running turned false while we were waiting for the condition + break; + } + + if (ups_accumulator.count() > 1.0) { + // Update each second + state.ups = loop_iterations; + loop_iterations = 0; + ups_accumulator = std::chrono::duration(0); + } + if (mass_springs.tree.nodes.empty()) { + state.mass_center = Vector3Zero(); + } else { + state.mass_center = mass_springs.tree.nodes.at(0).mass_center; + } + + state.masses.clear(); + state.masses.reserve(mass_springs.masses.size()); + for (const auto& mass : mass_springs.masses) { + state.masses.emplace_back(mass.position); + } + + state.mass_count = mass_springs.masses.size(); + state.spring_count = mass_springs.springs.size(); + + state.data_ready = true; + state.data_consumed = false; + } + // Notify the rendering thread that new data is available + state.data_ready_cnd.notify_all(); + #ifdef TRACY + FrameMarkEnd("PhysicsThreadProduceLock"); + + FrameMarkEnd("PhysicsThread"); + #endif + } +} + +auto threaded_physics::add_mass_cmd() -> void +{ + { + #ifdef TRACY + std::lock_guard lock(state.command_mtx); + #else + std::lock_guard lock(state.command_mtx); + #endif + state.pending_commands.emplace(add_mass{}); + } +} + +auto threaded_physics::add_spring_cmd(const size_t a, const size_t b) -> void +{ + { + #ifdef TRACY + std::lock_guard lock(state.command_mtx); + #else + std::lock_guard lock(state.command_mtx); + #endif + state.pending_commands.emplace(add_spring{a, b}); + } +} + +auto threaded_physics::clear_cmd() -> void +{ + { + #ifdef TRACY + std::lock_guard lock(state.command_mtx); + #else + std::lock_guard lock(state.command_mtx); + #endif + state.pending_commands.emplace(clear_graph{}); + } +} + +auto threaded_physics::add_mass_springs_cmd(const size_t num_masses, + const std::vector>& springs) -> void +{ + { + #ifdef TRACY + std::lock_guard lock(state.command_mtx); + #else + std::lock_guard lock(state.command_mtx); + #endif + for (size_t i = 0; i < num_masses; ++i) { + state.pending_commands.emplace(add_mass{}); + } + for (const auto& [from, to] : springs) { + state.pending_commands.emplace(add_spring{from, to}); + } + } +} \ No newline at end of file diff --git a/src/user_interface.cpp b/src/user_interface.cpp index 5b18b51..6cbb6dc 100644 --- a/src/user_interface.cpp +++ b/src/user_interface.cpp @@ -1,6 +1,6 @@ #include "user_interface.hpp" #include "config.hpp" -#include "input.hpp" +#include "input_handler.hpp" #include