From 861fb34d395c8c23d382dc131906763b6df7e23a Mon Sep 17 00:00:00 2001 From: Christoph Urlacher Date: Mon, 23 Feb 2026 13:33:32 +0100 Subject: [PATCH] fix octree corruption bug because of node vector reallocation --- include/config.hpp | 4 +-- include/octree.hpp | 9 ++--- include/physics.hpp | 2 -- src/main.cpp | 14 +++++--- src/octree.cpp | 81 +++++++++++++++++++++++++++++---------------- src/physics.cpp | 25 ++++---------- src/state.cpp | 4 +-- 7 files changed, 78 insertions(+), 61 deletions(-) diff --git a/include/config.hpp b/include/config.hpp index f966ed8..46c4f6d 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -4,8 +4,8 @@ #include #define PRINT_TIMINGS -// #define WEB // Disables multithreading #define BARNES_HUT // Use octree BH instead of uniform grid +// #define WEB // Disables multithreading // Window constexpr int INITIAL_WIDTH = 800; @@ -31,7 +31,7 @@ constexpr float ROT_SPEED = 1.0; // Physics Engine constexpr float SIM_SPEED = 4.0; // How large each update should be -constexpr float TIMESTEP = 1.0 / 60; // Do 60 physics updates per second +constexpr float TIMESTEP = 1.0 / 90; // Do 90 physics updates per second constexpr float MASS = 1.0; // Mass spring system constexpr float SPRING_CONSTANT = 5.0; // Mass spring system constexpr float DAMPENING_CONSTANT = 1.0; // Mass spring system diff --git a/include/octree.hpp b/include/octree.hpp index c812ccf..296c359 100644 --- a/include/octree.hpp +++ b/include/octree.hpp @@ -20,7 +20,8 @@ public: : mass_center(Vector3Zero()), mass_total(0.0), children(-1, -1, -1, -1, -1, -1, -1, -1), mass_id(-1), leaf(true) {} - ~OctreeNode() {} +public: + auto ChildCount() const -> int; }; class Octree { @@ -35,8 +36,6 @@ public: Octree(Octree &&move) = delete; Octree &operator=(Octree &&move) = delete; - ~Octree() {} - public: auto CreateNode(const Vector3 &box_min, const Vector3 &box_max) -> int; @@ -47,7 +46,9 @@ public: auto Insert(int node_idx, int mass_id, const Vector3 &pos, float mass) -> void; - auto CalculateForce(int node_idx, const Vector3 &pos) -> Vector3; + auto CalculateForce(int node_idx, const Vector3 &pos) const -> Vector3; + + auto Print() const -> void; }; #endif diff --git a/include/physics.hpp b/include/physics.hpp index ece9442..7268dec 100644 --- a/include/physics.hpp +++ b/include/physics.hpp @@ -106,8 +106,6 @@ public: MassSpringSystem(MassSpringSystem &move) = delete; MassSpringSystem &operator=(MassSpringSystem &&move) = delete; - ~MassSpringSystem() {}; - private: #ifdef BARNES_HUT auto BuildOctree() -> void; diff --git a/src/main.cpp b/src/main.cpp index 3cce126..9b6cfbe 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,6 +23,9 @@ // - Click states to display them in the board // - Find shortest path to any winning state and mark it in the graph // - Also mark the next move along the path on the board +// TODO: Smooth camera (on target change) +// TODO: Mark the starting state +// TODO: Mark the visited states auto main(int argc, char *argv[]) -> int { // if (argc < 2) { @@ -52,10 +55,10 @@ auto main(int argc, char *argv[]) -> int { std::chrono::duration(0); std::chrono::duration render_time_accumulator = std::chrono::duration(0); - int loop_count = 0; + long loop_count = 0; #endif - float timestep_accumulator = 0.0; - int update_accumulator = 0; + double timestep_accumulator = 0.0; + long update_accumulator = 0; while (!WindowShouldClose()) { timestep_accumulator += GetFrameTime(); @@ -123,8 +126,9 @@ auto main(int argc, char *argv[]) -> int { << render_time_accumulator / loop_count << "." << std::endl; std::cout << " - Physics updates avg: " << static_cast(update_accumulator) / loop_count - << "x per frame (" << timestep_accumulator << "s remaining)." - << std::endl; + << "x per frame (" + << static_cast(timestep_accumulator / TIMESTEP) + << "x behind)." << std::endl; last_print_time = GetTime(); physics_time_accumulator = std::chrono::duration(0); render_time_accumulator = std::chrono::duration(0); diff --git a/src/octree.cpp b/src/octree.cpp index 6c5a3d2..9e6c262 100644 --- a/src/octree.cpp +++ b/src/octree.cpp @@ -1,8 +1,20 @@ #include "octree.hpp" #include "config.hpp" +#include "util.hpp" +#include #include +auto OctreeNode::ChildCount() const -> int { + int child_count = 0; + for (int child : children) { + if (child != -1) { + ++child_count; + } + } + return child_count; +} + auto Octree::CreateNode(const Vector3 &box_min, const Vector3 &box_max) -> int { OctreeNode node; node.box_min = box_min; @@ -43,8 +55,8 @@ auto Octree::GetChildBounds(int node_idx, int octant) (node.box_min.y + node.box_max.y) / 2.0, (node.box_min.z + node.box_max.z) / 2.0); - Vector3 min; - Vector3 max; + Vector3 min = Vector3Zero(); + Vector3 max = Vector3Zero(); // If (octant & 1), the octant is to the right of the node region's x-axis // (see GetOctant). This means the left bound is the x-axis and the right @@ -61,31 +73,34 @@ auto Octree::GetChildBounds(int node_idx, int octant) auto Octree::Insert(int node_idx, int mass_id, const Vector3 &pos, float mass) -> void { - OctreeNode &node = nodes[node_idx]; + // NOTE: Do not store a nodes[node_idx] reference beforehand as the nodes + // vector might reallocate during this function - if (node.leaf && node.mass_id == -1) { + if (nodes[node_idx].leaf && nodes[node_idx].mass_id == -1) { // We can place the particle in the empty leaf - node.mass_id = mass_id; - node.mass_center = pos; - node.mass_total = mass; + nodes[node_idx].mass_id = mass_id; + nodes[node_idx].mass_center = pos; + nodes[node_idx].mass_total = mass; return; } - if (node.leaf) { + if (nodes[node_idx].leaf) { // The leaf is occupied, we need to subdivide - int existing_id = node.mass_id; - Vector3 existing_pos = node.mass_center; - float existing_mass = node.mass_total; - node.mass_id = -1; - node.leaf = false; + int existing_id = nodes[node_idx].mass_id; + Vector3 existing_pos = nodes[node_idx].mass_center; + float existing_mass = nodes[node_idx].mass_total; + nodes[node_idx].mass_id = -1; + nodes[node_idx].leaf = false; + nodes[node_idx].mass_total = 0.0; - // Re-add the existing mass into a new empty leaf (see above) + // Re-insert the existing mass into a new empty leaf (see above) int oct = GetOctant(node_idx, existing_pos); - if (node.children[oct] == -1) { + if (nodes[node_idx].children[oct] == -1) { auto [min, max] = GetChildBounds(node_idx, oct); - node.children[oct] = CreateNode(min, max); + nodes[node_idx].children[oct] = CreateNode(min, max); } - Insert(node.children[oct], existing_id, existing_pos, existing_mass); + Insert(nodes[node_idx].children[oct], existing_id, existing_pos, + existing_mass); } // Insert the new mass @@ -97,23 +112,25 @@ auto Octree::Insert(int node_idx, int mass_id, const Vector3 &pos, float mass) Insert(nodes[node_idx].children[oct], mass_id, pos, mass); // Update the center of mass - node = nodes[node_idx]; - float new_mass = node.mass_total + mass; - node.mass_center.x = - (node.mass_center.x * node.mass_total + pos.x) / new_mass; - node.mass_center.y = - (node.mass_center.y * node.mass_total + pos.y) / new_mass; - node.mass_center.z = - (node.mass_center.z * node.mass_total + pos.z) / new_mass; - node.mass_total = new_mass; + float new_mass = nodes[node_idx].mass_total + mass; + nodes[node_idx].mass_center.x = + (nodes[node_idx].mass_center.x * nodes[node_idx].mass_total + pos.x) / + new_mass; + nodes[node_idx].mass_center.y = + (nodes[node_idx].mass_center.y * nodes[node_idx].mass_total + pos.y) / + new_mass; + nodes[node_idx].mass_center.z = + (nodes[node_idx].mass_center.z * nodes[node_idx].mass_total + pos.z) / + new_mass; + nodes[node_idx].mass_total = new_mass; } -auto Octree::CalculateForce(int node_idx, const Vector3 &pos) -> Vector3 { +auto Octree::CalculateForce(int node_idx, const Vector3 &pos) const -> Vector3 { if (node_idx < 0) { return Vector3Zero(); } - OctreeNode &node = nodes[node_idx]; + const OctreeNode &node = nodes[node_idx]; if (node.mass_total == 0.0f) { return Vector3Zero(); } @@ -146,3 +163,11 @@ auto Octree::CalculateForce(int node_idx, const Vector3 &pos) -> Vector3 { return force; } + +auto Octree::Print() const -> void { + std::cout << "Octree Start ===========================" << std::endl; + for (const auto &node : nodes) { + std::cout << "Center: " << node.mass_center << ", Mass: " << node.mass_total + << ", Direct Children: " << node.ChildCount() << std::endl; + } +} diff --git a/src/physics.cpp b/src/physics.cpp index 313ca36..4c846ff 100644 --- a/src/physics.cpp +++ b/src/physics.cpp @@ -1,5 +1,6 @@ #include "physics.hpp" #include "config.hpp" +#include "util.hpp" #include #include @@ -249,15 +250,9 @@ auto MassSpringSystem::CalculateRepulsionForces() -> void { solve_octree(i); } #else - threads.detach_blocks( - 0, mass_pointers.size(), - [&](int start, int end) { - for (int i = start; i < end; ++i) { - solve_octree(i); - } - }, - 256); - threads.wait(); + BS::multi_future loop_future = + threads.submit_loop(0, mass_pointers.size(), solve_octree, 256); + loop_future.wait(); #endif #else @@ -333,15 +328,9 @@ auto MassSpringSystem::CalculateRepulsionForces() -> void { calculate_grid(i); } #else - threads.detach_blocks( - 0, mass_pointers.size(), - [&](int start, int end) { - for (int i = start; i < end; ++i) { - solve_grid(i); - } - }, - 512); - threads.wait(); + BS::multi_future loop_future = + threads.submit_loop(0, mass_pointers.size(), solve_grid, 512); + loop_future.wait(); #endif #endif diff --git a/src/state.cpp b/src/state.cpp index 11f8467..3e22cb7 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -16,8 +16,8 @@ auto StateManager::ResetState() -> void { current_state = generators[current_preset](); previous_state = current_state; if (edited) { - // We also need to clear the graph, in case the state has been edited. - // Then the graph would contain states that are impossible to reach. + // We also need to clear the graph in case the state has been edited + // because the graph could contain states that are impossible to reach now. ClearGraph(); edited = false; }