squash merge threaded-physics into main
This commit is contained in:
@ -1,10 +1,11 @@
|
|||||||
cmake_minimum_required(VERSION 3.25)
|
cmake_minimum_required(VERSION 3.25)
|
||||||
project(MassSprings)
|
project(MassSprings)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 23)
|
set(CMAKE_CXX_STANDARD 26)
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
find_package(raylib REQUIRED)
|
find_package(raylib REQUIRED)
|
||||||
|
find_package(Backward REQUIRED)
|
||||||
|
|
||||||
# Need to enable/disable this based on a variable for nix build
|
# Need to enable/disable this based on a variable for nix build
|
||||||
if(USE_TRACY)
|
if(USE_TRACY)
|
||||||
@ -43,14 +44,15 @@ add_executable(masssprings
|
|||||||
src/state.cpp
|
src/state.cpp
|
||||||
src/input.cpp
|
src/input.cpp
|
||||||
src/tracy.cpp
|
src/tracy.cpp
|
||||||
|
src/backward.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(masssprings PUBLIC ${RAYLIB_CPP_INCLUDE_DIR})
|
target_include_directories(masssprings PUBLIC ${RAYLIB_CPP_INCLUDE_DIR})
|
||||||
|
|
||||||
if(USE_TRACY)
|
if(USE_TRACY)
|
||||||
target_link_libraries(masssprings PUBLIC raylib TracyClient)
|
target_link_libraries(masssprings PUBLIC raylib Backward::Backward TracyClient)
|
||||||
else()
|
else()
|
||||||
target_link_libraries(masssprings PUBLIC raylib)
|
target_link_libraries(masssprings PUBLIC raylib Backward::Backward)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include(CheckIPOSupported)
|
include(CheckIPOSupported)
|
||||||
|
|||||||
@ -128,7 +128,7 @@ rec {
|
|||||||
# - Interpreters needed by patchShebangs for build scripts (with the --build flag), which can be the case for e.g. perl
|
# - Interpreters needed by patchShebangs for build scripts (with the --build flag), which can be the case for e.g. perl
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
# Languages:
|
# Languages:
|
||||||
# bintools
|
binutils
|
||||||
gcc
|
gcc
|
||||||
|
|
||||||
# C/C++:
|
# C/C++:
|
||||||
@ -159,6 +159,8 @@ rec {
|
|||||||
# octree # this one doesn't store center of mass per node - which I need :(
|
# octree # this one doesn't store center of mass per node - which I need :(
|
||||||
tracy-wayland
|
tracy-wayland
|
||||||
thread-pool
|
thread-pool
|
||||||
|
backward-cpp
|
||||||
|
libbfd
|
||||||
# llvmPackages.openmp # not required for compilation but for clangd to find the headers
|
# llvmPackages.openmp # not required for compilation but for clangd to find the headers
|
||||||
# raylib-cpp
|
# raylib-cpp
|
||||||
# tinyobjloader
|
# tinyobjloader
|
||||||
@ -283,7 +285,7 @@ rec {
|
|||||||
abbr -a debug "${buildDebug} && ./cmake-build-debug/masssprings"
|
abbr -a debug "${buildDebug} && ./cmake-build-debug/masssprings"
|
||||||
abbr -a release "${buildRelease} && ./cmake-build-release/masssprings"
|
abbr -a release "${buildRelease} && ./cmake-build-release/masssprings"
|
||||||
abbr -a rungdb "${buildDebug} && gdb --tui ./cmake-build-debug/masssprings"
|
abbr -a rungdb "${buildDebug} && gdb --tui ./cmake-build-debug/masssprings"
|
||||||
abbr -a runtracy "tracy -a 127.0.0.1"
|
abbr -a runtracy "tracy -a 127.0.0.1 &; ${buildRelease} && sudo -E ./cmake-build-release/masssprings"
|
||||||
'';
|
'';
|
||||||
in
|
in
|
||||||
builtins.concatStringsSep "\n" [
|
builtins.concatStringsSep "\n" [
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
|
|
||||||
#define BARNES_HUT // Use octree BH instead of uniform grid
|
|
||||||
// #define WEB // Disables multithreading
|
// #define WEB // Disables multithreading
|
||||||
|
|
||||||
// Window
|
// Window
|
||||||
@ -30,19 +29,17 @@ constexpr float ROT_SPEED = 1.0;
|
|||||||
constexpr float CAMERA_SMOOTH_SPEED = 15.0;
|
constexpr float CAMERA_SMOOTH_SPEED = 15.0;
|
||||||
|
|
||||||
// Physics Engine
|
// Physics Engine
|
||||||
constexpr float SIM_SPEED = 4.0; // How large each update should be
|
constexpr float TARGET_UPS = 90; // How often to update physics
|
||||||
constexpr float TIMESTEP = 1.0 / 90; // Do 90 physics updates per second
|
constexpr float TIMESTEP = 1.0 / TARGET_UPS; // Update interval in seconds
|
||||||
constexpr float MASS = 1.0; // Mass spring system
|
constexpr float SIM_SPEED = 4.0; // How large each update should be
|
||||||
constexpr float SPRING_CONSTANT = 5.0; // Mass spring system
|
constexpr float MASS = 1.0; // Mass spring system
|
||||||
constexpr float DAMPENING_CONSTANT = 1.0; // Mass spring system
|
constexpr float SPRING_CONSTANT = 5.0; // Mass spring system
|
||||||
constexpr float REST_LENGTH = 2.0; // Mass spring system
|
constexpr float DAMPENING_CONSTANT = 1.0; // Mass spring system
|
||||||
constexpr float VERLET_DAMPENING = 0.05; // [0, 1]
|
constexpr float REST_LENGTH = 2.0; // Mass spring system
|
||||||
constexpr float BH_FORCE = 2.0; // BH: [1.0, 3.0]
|
constexpr float VERLET_DAMPENING = 0.05; // [0, 1]
|
||||||
constexpr float THETA = 1.0; // Barnes-Hut [0.5, 1.0]
|
constexpr float BH_FORCE = 2.0; // Barnes-Hut [1.0, 3.0]
|
||||||
constexpr float SOFTENING = 0.01; // Barnes-Hut [0.01, 1.0]
|
constexpr float THETA = 0.9; // Barnes-Hut [0.5, 1.0]
|
||||||
constexpr float GRID_FORCE = 0.02; // Grid: [0.0, ~0.05]
|
constexpr float SOFTENING = 0.01; // Barnes-Hut [0.01, 1.0]
|
||||||
constexpr float REPULSION_RANGE = 5.0 * REST_LENGTH; // Grid
|
|
||||||
constexpr int REPULSION_GRID_REFRESH = 5; // Grid rebuild freq
|
|
||||||
|
|
||||||
// Graph Drawing
|
// Graph Drawing
|
||||||
constexpr float VERTEX_SIZE = 0.5;
|
constexpr float VERTEX_SIZE = 0.5;
|
||||||
|
|||||||
@ -1,13 +1,11 @@
|
|||||||
#ifndef __INPUT_HPP_
|
#ifndef __INPUT_HPP_
|
||||||
#define __INPUT_HPP_
|
#define __INPUT_HPP_
|
||||||
|
|
||||||
#include "renderer.hpp"
|
|
||||||
#include "state.hpp"
|
#include "state.hpp"
|
||||||
|
|
||||||
class InputHandler {
|
class InputHandler {
|
||||||
public:
|
public:
|
||||||
StateManager &state;
|
StateManager &state;
|
||||||
Renderer &renderer;
|
|
||||||
|
|
||||||
int hov_x;
|
int hov_x;
|
||||||
int hov_y;
|
int hov_y;
|
||||||
@ -18,10 +16,14 @@ public:
|
|||||||
int block_add_x;
|
int block_add_x;
|
||||||
int block_add_y;
|
int block_add_y;
|
||||||
|
|
||||||
|
bool mark_solutions;
|
||||||
|
bool connect_solutions;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
InputHandler(StateManager &_state, Renderer &_renderer)
|
InputHandler(StateManager &_state)
|
||||||
: state(_state), renderer(_renderer), hov_x(-1), hov_y(-1), sel_x(0),
|
: state(_state), hov_x(-1), hov_y(-1), sel_x(0), sel_y(0),
|
||||||
sel_y(0), has_block_add_xy(false), block_add_x(-1), block_add_y(-1) {}
|
has_block_add_xy(false), block_add_x(-1), block_add_y(-1),
|
||||||
|
mark_solutions(false), connect_solutions(false) {}
|
||||||
|
|
||||||
InputHandler(const InputHandler ©) = delete;
|
InputHandler(const InputHandler ©) = delete;
|
||||||
InputHandler &operator=(const InputHandler ©) = delete;
|
InputHandler &operator=(const InputHandler ©) = delete;
|
||||||
|
|||||||
@ -1,19 +1,22 @@
|
|||||||
#ifndef __PHYSICS_HPP_
|
#ifndef __PHYSICS_HPP_
|
||||||
#define __PHYSICS_HPP_
|
#define __PHYSICS_HPP_
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
#include <unordered_map>
|
#include <thread>
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "config.hpp"
|
|
||||||
#include "puzzle.hpp"
|
|
||||||
|
|
||||||
#ifdef BARNES_HUT
|
|
||||||
#include "octree.hpp"
|
#include "octree.hpp"
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef WEB
|
#ifndef WEB
|
||||||
|
#define BS_THREAD_POOL_NATIVE_EXTENSIONS
|
||||||
#include <BS_thread_pool.hpp>
|
#include <BS_thread_pool.hpp>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -41,29 +44,19 @@ public:
|
|||||||
|
|
||||||
class Spring {
|
class Spring {
|
||||||
public:
|
public:
|
||||||
int mass_a;
|
std::size_t a;
|
||||||
int mass_b;
|
std::size_t b;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Spring(int _mass_a, int _mass_b) : mass_a(_mass_a), mass_b(_mass_b) {}
|
Spring(std::size_t _a, std::size_t _b) : a(_a), b(_b) {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto CalculateSpringForce(Mass &_mass_a, Mass &_mass_b) const -> void;
|
auto CalculateSpringForce(Mass &_a, Mass &_b) const -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MassSpringSystem {
|
class MassSpringSystem {
|
||||||
private:
|
private:
|
||||||
#ifdef BARNES_HUT
|
|
||||||
// Barnes-Hut
|
|
||||||
Octree octree;
|
Octree octree;
|
||||||
#else
|
|
||||||
// Uniform grid
|
|
||||||
std::vector<int> mass_indices;
|
|
||||||
std::vector<int64_t> cell_ids;
|
|
||||||
int last_build;
|
|
||||||
int last_masses_count;
|
|
||||||
int last_springs_count;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef WEB
|
#ifndef WEB
|
||||||
BS::thread_pool<BS::tp::none> threads;
|
BS::thread_pool<BS::tp::none> threads;
|
||||||
@ -72,19 +65,13 @@ private:
|
|||||||
public:
|
public:
|
||||||
// This is the main ownership of all the states/masses/springs.
|
// This is the main ownership of all the states/masses/springs.
|
||||||
std::vector<Mass> masses;
|
std::vector<Mass> masses;
|
||||||
std::unordered_map<State, int> state_masses;
|
|
||||||
std::vector<Spring> springs;
|
std::vector<Spring> springs;
|
||||||
std::unordered_map<std::pair<State, State>, int> state_springs;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MassSpringSystem() {
|
MassSpringSystem()
|
||||||
#ifndef BARNES_HUT
|
: threads(std::thread::hardware_concurrency() - 1, SetThreadName) {
|
||||||
last_build = REPULSION_GRID_REFRESH;
|
|
||||||
std::cout << "Using uniform grid repulsion force calculation." << std::endl;
|
|
||||||
#else
|
|
||||||
std::cout << "Using Barnes-Hut + octree repulsion force calculation."
|
std::cout << "Using Barnes-Hut + octree repulsion force calculation."
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef WEB
|
#ifndef WEB
|
||||||
std::cout << "Thread-Pool: " << threads.get_thread_count() << " threads."
|
std::cout << "Thread-Pool: " << threads.get_thread_count() << " threads."
|
||||||
@ -98,21 +85,14 @@ public:
|
|||||||
MassSpringSystem &operator=(MassSpringSystem &&move) = delete;
|
MassSpringSystem &operator=(MassSpringSystem &&move) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#ifdef BARNES_HUT
|
static auto SetThreadName(std::size_t idx) -> void;
|
||||||
|
|
||||||
auto BuildOctree() -> void;
|
auto BuildOctree() -> void;
|
||||||
#else
|
|
||||||
auto BuildUniformGrid() -> void;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto AddMass(float mass, bool fixed, const State &state) -> void;
|
auto AddMass() -> void;
|
||||||
|
|
||||||
auto GetMass(const State &state) -> Mass &;
|
auto AddSpring(int a, int b) -> void;
|
||||||
|
|
||||||
auto GetMass(const State &state) const -> const Mass &;
|
|
||||||
|
|
||||||
auto AddSpring(const State &massA, const State &massB, float spring_constant,
|
|
||||||
float dampening_constant, float rest_length) -> void;
|
|
||||||
|
|
||||||
auto Clear() -> void;
|
auto Clear() -> void;
|
||||||
|
|
||||||
@ -123,10 +103,74 @@ public:
|
|||||||
auto CalculateRepulsionForces() -> void;
|
auto CalculateRepulsionForces() -> void;
|
||||||
|
|
||||||
auto VerletUpdate(float delta_time) -> void;
|
auto VerletUpdate(float delta_time) -> void;
|
||||||
|
};
|
||||||
|
|
||||||
#ifndef BARNES_HUT
|
class ThreadedPhysics {
|
||||||
auto InvalidateGrid() -> void;
|
struct AddMass {};
|
||||||
#endif
|
struct AddSpring {
|
||||||
|
std::size_t a;
|
||||||
|
std::size_t b;
|
||||||
|
};
|
||||||
|
struct ClearGraph {};
|
||||||
|
|
||||||
|
using Command = std::variant<AddMass, AddSpring, ClearGraph>;
|
||||||
|
|
||||||
|
struct PhysicsState {
|
||||||
|
TracyLockable(std::mutex, command_mtx);
|
||||||
|
std::queue<Command> pending_commands;
|
||||||
|
|
||||||
|
TracyLockable(std::mutex, data_mtx);
|
||||||
|
std::condition_variable_any data_ready_cnd;
|
||||||
|
std::condition_variable_any data_consumed_cnd;
|
||||||
|
unsigned int ups = 0;
|
||||||
|
bool data_ready = false;
|
||||||
|
bool data_consumed = true;
|
||||||
|
std::vector<Vector3> masses; // Read by renderer
|
||||||
|
std::vector<std::pair<std::size_t, std::size_t>>
|
||||||
|
springs; // Read by renderer
|
||||||
|
|
||||||
|
std::atomic<bool> running{true};
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::thread physics;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PhysicsState state;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ThreadedPhysics() : physics(PhysicsThread, std::ref(state)) {}
|
||||||
|
|
||||||
|
ThreadedPhysics(const ThreadedPhysics ©) = delete;
|
||||||
|
ThreadedPhysics &operator=(const ThreadedPhysics ©) = delete;
|
||||||
|
ThreadedPhysics(ThreadedPhysics &&move) = delete;
|
||||||
|
ThreadedPhysics &operator=(ThreadedPhysics &&move) = delete;
|
||||||
|
|
||||||
|
~ThreadedPhysics() {
|
||||||
|
state.running = false;
|
||||||
|
state.data_ready_cnd.notify_all();
|
||||||
|
state.data_consumed_cnd.notify_all();
|
||||||
|
physics.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static auto PhysicsThread(PhysicsState &state) -> void;
|
||||||
|
|
||||||
|
public:
|
||||||
|
auto AddMassCmd() -> void;
|
||||||
|
|
||||||
|
auto AddSpringCmd(std::size_t a, std::size_t b) -> void;
|
||||||
|
|
||||||
|
auto ClearCmd() -> void;
|
||||||
|
|
||||||
|
auto AddMassSpringsCmd(
|
||||||
|
std::size_t num_masses,
|
||||||
|
const std::vector<std::pair<std::size_t, std::size_t>> &springs) -> void;
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://en.cppreference.com/w/cpp/utility/variant/visit
|
||||||
|
template <class... Ts> struct overloads : Ts... {
|
||||||
|
using Ts::operator()...;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -7,7 +7,6 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
enum Direction {
|
enum Direction {
|
||||||
@ -219,8 +218,9 @@ public:
|
|||||||
|
|
||||||
auto GetNextStates() const -> std::vector<State>;
|
auto GetNextStates() const -> std::vector<State>;
|
||||||
|
|
||||||
auto Closure() const -> std::pair<std::unordered_set<State>,
|
auto Closure() const
|
||||||
std::vector<std::pair<State, State>>>;
|
-> std::pair<std::vector<State>,
|
||||||
|
std::vector<std::pair<std::size_t, std::size_t>>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Provide hash functions so we can use State and <State, State> as hash-set
|
// Provide hash functions so we can use State and <State, State> as hash-set
|
||||||
|
|||||||
@ -3,15 +3,17 @@
|
|||||||
|
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
#include "camera.hpp"
|
#include "camera.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "physics.hpp"
|
#include "input.hpp"
|
||||||
#include "puzzle.hpp"
|
#include "state.hpp"
|
||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
private:
|
private:
|
||||||
|
const StateManager &state;
|
||||||
|
const InputHandler &input;
|
||||||
|
|
||||||
const OrbitCamera3D &camera;
|
const OrbitCamera3D &camera;
|
||||||
RenderTexture render_target;
|
RenderTexture render_target;
|
||||||
RenderTexture klotski_target;
|
RenderTexture klotski_target;
|
||||||
@ -25,13 +27,10 @@ private:
|
|||||||
Shader instancing_shader;
|
Shader instancing_shader;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
bool mark_solutions;
|
Renderer(const OrbitCamera3D &_camera, const StateManager &_state,
|
||||||
bool connect_solutions;
|
const InputHandler &_input)
|
||||||
|
: state(_state), input(_input), camera(_camera), transforms_size(0),
|
||||||
public:
|
transforms(nullptr) {
|
||||||
Renderer(const OrbitCamera3D &_camera)
|
|
||||||
: camera(_camera), transforms_size(0), transforms(nullptr),
|
|
||||||
mark_solutions(false), connect_solutions(false) {
|
|
||||||
render_target = LoadRenderTexture(GetScreenWidth() / 2.0,
|
render_target = LoadRenderTexture(GetScreenWidth() / 2.0,
|
||||||
GetScreenHeight() - MENU_HEIGHT);
|
GetScreenHeight() - MENU_HEIGHT);
|
||||||
klotski_target = LoadRenderTexture(GetScreenWidth() / 2.0,
|
klotski_target = LoadRenderTexture(GetScreenWidth() / 2.0,
|
||||||
@ -61,29 +60,24 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto AllocateGraphInstancing(const MassSpringSystem &mass_springs) -> void;
|
auto AllocateGraphInstancing(std::size_t size) -> void;
|
||||||
|
|
||||||
auto
|
auto ReallocateGraphInstancingIfNecessary(std::size_t size) -> void;
|
||||||
ReallocateGraphInstancingIfNecessary(const MassSpringSystem &mass_springs)
|
|
||||||
-> void;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto UpdateTextureSizes() -> void;
|
auto UpdateTextureSizes() -> void;
|
||||||
|
|
||||||
auto DrawMassSprings(const MassSpringSystem &mass_springs,
|
auto DrawMassSprings(
|
||||||
const State ¤t_state, const State &starting_state,
|
const std::vector<Vector3> &masses,
|
||||||
const std::unordered_set<State> &winning_states,
|
const std::vector<std::pair<std::size_t, std::size_t>> &springs) -> void;
|
||||||
const std::unordered_set<State> &visited_states) -> void;
|
|
||||||
|
|
||||||
auto DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
|
auto DrawKlotski() -> void;
|
||||||
int sel_y, int block_add_x, int block_add_y,
|
|
||||||
const WinCondition win_condition) -> void;
|
|
||||||
|
|
||||||
auto DrawMenu(const MassSpringSystem &mass_springs, int current_preset,
|
auto DrawMenu(const std::vector<Vector3> &masses,
|
||||||
const State ¤t_state,
|
const std::vector<std::pair<std::size_t, std::size_t>> &springs)
|
||||||
const std::unordered_set<State> &winning_states) -> void;
|
-> void;
|
||||||
|
|
||||||
auto DrawTextures() -> void;
|
auto DrawTextures(float ups) -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,16 +1,21 @@
|
|||||||
#ifndef __STATE_HPP_
|
#ifndef __STATE_HPP_
|
||||||
#define __STATE_HPP_
|
#define __STATE_HPP_
|
||||||
|
|
||||||
#include "config.hpp"
|
|
||||||
#include "physics.hpp"
|
#include "physics.hpp"
|
||||||
#include "presets.hpp"
|
#include "presets.hpp"
|
||||||
#include "puzzle.hpp"
|
#include "puzzle.hpp"
|
||||||
|
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
class StateManager {
|
class StateManager {
|
||||||
public:
|
public:
|
||||||
MassSpringSystem &mass_springs;
|
ThreadedPhysics &physics;
|
||||||
|
|
||||||
|
std::unordered_map<State, std::size_t> states;
|
||||||
|
std::unordered_set<State> winning_states;
|
||||||
|
std::unordered_set<State> visited_states;
|
||||||
|
|
||||||
int current_preset;
|
int current_preset;
|
||||||
State starting_state;
|
State starting_state;
|
||||||
@ -19,16 +24,13 @@ public:
|
|||||||
|
|
||||||
bool edited = false;
|
bool edited = false;
|
||||||
|
|
||||||
std::unordered_set<State> winning_states;
|
|
||||||
std::unordered_set<State> visited_states;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
StateManager(MassSpringSystem &_mass_springs)
|
StateManager(ThreadedPhysics &_physics)
|
||||||
: mass_springs(_mass_springs), current_preset(0),
|
: physics(_physics), current_preset(0),
|
||||||
starting_state(generators[current_preset]()),
|
starting_state(generators[current_preset]()),
|
||||||
current_state(starting_state), previous_state(starting_state),
|
current_state(starting_state), previous_state(starting_state),
|
||||||
edited(false) {
|
edited(false) {
|
||||||
mass_springs.AddMass(MASS, false, current_state);
|
ClearGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
StateManager(const StateManager ©) = delete;
|
StateManager(const StateManager ©) = delete;
|
||||||
@ -55,9 +57,11 @@ public:
|
|||||||
|
|
||||||
auto FindWinningStates() -> void;
|
auto FindWinningStates() -> void;
|
||||||
|
|
||||||
auto CurrentGenerator() -> StateGenerator;
|
auto CurrentGenerator() const -> StateGenerator;
|
||||||
|
|
||||||
auto CurrentWinCondition() -> WinCondition;
|
auto CurrentWinCondition() const -> WinCondition;
|
||||||
|
|
||||||
|
auto CurrentMassIndex() const -> std::size_t;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
42
src/backward.cpp
Normal file
42
src/backward.cpp
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Pick your poison.
|
||||||
|
//
|
||||||
|
// On GNU/Linux, you have few choices to get the most out of your stack trace.
|
||||||
|
//
|
||||||
|
// By default you get:
|
||||||
|
// - object filename
|
||||||
|
// - function name
|
||||||
|
//
|
||||||
|
// In order to add:
|
||||||
|
// - source filename
|
||||||
|
// - line and column numbers
|
||||||
|
// - source code snippet (assuming the file is accessible)
|
||||||
|
|
||||||
|
// Install one of the following libraries then uncomment one of the macro (or
|
||||||
|
// better, add the detection of the lib and the macro definition in your build
|
||||||
|
// system)
|
||||||
|
|
||||||
|
// - apt-get install libdw-dev ...
|
||||||
|
// - g++/clang++ -ldw ...
|
||||||
|
// #define BACKWARD_HAS_DW 1
|
||||||
|
|
||||||
|
// - apt-get install binutils-dev ...
|
||||||
|
// - g++/clang++ -lbfd ...
|
||||||
|
// #define BACKWARD_HAS_BFD 1
|
||||||
|
|
||||||
|
// - apt-get install libdwarf-dev ...
|
||||||
|
// - g++/clang++ -ldwarf ...
|
||||||
|
// #define BACKWARD_HAS_DWARF 1
|
||||||
|
|
||||||
|
// Regardless of the library you choose to read the debug information,
|
||||||
|
// for potentially more detailed stack traces you can use libunwind
|
||||||
|
// - apt-get install libunwind-dev
|
||||||
|
// - g++/clang++ -lunwind
|
||||||
|
// #define BACKWARD_HAS_LIBUNWIND 1
|
||||||
|
|
||||||
|
#include "backward.hpp"
|
||||||
|
|
||||||
|
namespace backward {
|
||||||
|
|
||||||
|
backward::SignalHandling sh;
|
||||||
|
|
||||||
|
} // namespace backward
|
||||||
@ -133,9 +133,9 @@ auto InputHandler::HandleKeys() -> void {
|
|||||||
} else if (IsKeyPressed(KEY_C)) {
|
} else if (IsKeyPressed(KEY_C)) {
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
} else if (IsKeyPressed(KEY_I)) {
|
} else if (IsKeyPressed(KEY_I)) {
|
||||||
renderer.mark_solutions = !renderer.mark_solutions;
|
mark_solutions = !mark_solutions;
|
||||||
} else if (IsKeyPressed(KEY_O)) {
|
} else if (IsKeyPressed(KEY_O)) {
|
||||||
renderer.connect_solutions = !renderer.connect_solutions;
|
connect_solutions = !connect_solutions;
|
||||||
} else if (IsKeyPressed(KEY_F)) {
|
} else if (IsKeyPressed(KEY_F)) {
|
||||||
state.current_state.ToggleRestricted();
|
state.current_state.ToggleRestricted();
|
||||||
state.ClearGraph();
|
state.ClearGraph();
|
||||||
|
|||||||
78
src/main.cpp
78
src/main.cpp
@ -1,3 +1,8 @@
|
|||||||
|
#include <mutex>
|
||||||
|
#include <raylib.h>
|
||||||
|
#include <raymath.h>
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "input.hpp"
|
#include "input.hpp"
|
||||||
#include "physics.hpp"
|
#include "physics.hpp"
|
||||||
@ -5,10 +10,6 @@
|
|||||||
#include "state.hpp"
|
#include "state.hpp"
|
||||||
#include "tracy.hpp"
|
#include "tracy.hpp"
|
||||||
|
|
||||||
#include <raylib.h>
|
|
||||||
#include <raymath.h>
|
|
||||||
#include <tracy/Tracy.hpp>
|
|
||||||
|
|
||||||
// TODO: Klotski state file loading
|
// TODO: Klotski state file loading
|
||||||
// - File should contain a single state per line, multiple lines possible
|
// - File should contain a single state per line, multiple lines possible
|
||||||
// - If a file is loaded, the presets should be replaced with the states
|
// - If a file is loaded, the presets should be replaced with the states
|
||||||
@ -18,7 +19,9 @@
|
|||||||
// TODO: Graph interaction
|
// TODO: Graph interaction
|
||||||
// - Click states to display them in the board
|
// - Click states to display them in the board
|
||||||
// - Find shortest path to any winning state and mark it in the graph
|
// - Find shortest path to any winning state and mark it in the graph
|
||||||
// - Also mark the next move along the path on the board
|
// - Also mark the next move along the path on the board
|
||||||
|
// TODO: Do I have a huge memory leak or is the memory just not reclaimed from
|
||||||
|
// the C++ runtime?
|
||||||
|
|
||||||
auto main(int argc, char *argv[]) -> int {
|
auto main(int argc, char *argv[]) -> int {
|
||||||
// if (argc < 2) {
|
// if (argc < 2) {
|
||||||
@ -35,50 +38,61 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
InitWindow(INITIAL_WIDTH * 2, INITIAL_HEIGHT + MENU_HEIGHT, "MassSprings");
|
InitWindow(INITIAL_WIDTH * 2, INITIAL_HEIGHT + MENU_HEIGHT, "MassSprings");
|
||||||
|
|
||||||
// Game setup
|
// Game setup
|
||||||
|
ThreadedPhysics physics;
|
||||||
|
StateManager state(physics);
|
||||||
|
InputHandler input(state);
|
||||||
OrbitCamera3D camera;
|
OrbitCamera3D camera;
|
||||||
Renderer renderer(camera);
|
Renderer renderer(camera, state, input);
|
||||||
MassSpringSystem mass_springs;
|
|
||||||
StateManager state(mass_springs);
|
unsigned int ups;
|
||||||
InputHandler input(state, renderer);
|
std::vector<Vector3> masses; // Read from physics
|
||||||
|
std::vector<std::pair<std::size_t, std::size_t>> springs; // Read from physics
|
||||||
|
|
||||||
// Game loop
|
// Game loop
|
||||||
double timestep_accumulator = 0.0;
|
|
||||||
while (!WindowShouldClose()) {
|
while (!WindowShouldClose()) {
|
||||||
timestep_accumulator += GetFrameTime();
|
FrameMarkStart("MainThread");
|
||||||
|
|
||||||
// Input update
|
// Input update
|
||||||
state.previous_state = state.current_state;
|
state.previous_state = state.current_state;
|
||||||
input.HandleInput();
|
input.HandleInput();
|
||||||
state.UpdateGraph(); // Add state added after user input
|
state.UpdateGraph(); // Add state added after user input
|
||||||
|
|
||||||
// Physics update
|
// Read positions from physics thread
|
||||||
if (timestep_accumulator > TIMESTEP) {
|
FrameMarkStart("MainThreadConsumeLock");
|
||||||
// Do not try to catch up if we're falling behind. Frametimes would get
|
{
|
||||||
// larger, resulting in more catching up, resulting in even larger
|
std::unique_lock<LockableBase(std::mutex)> lock(physics.state.data_mtx);
|
||||||
// frametimes -> death spiral.
|
|
||||||
mass_springs.ClearForces();
|
|
||||||
mass_springs.CalculateSpringForces();
|
|
||||||
mass_springs.CalculateRepulsionForces();
|
|
||||||
mass_springs.VerletUpdate(TIMESTEP * SIM_SPEED);
|
|
||||||
|
|
||||||
timestep_accumulator -= TIMESTEP;
|
ups = physics.state.ups;
|
||||||
|
|
||||||
|
// Only copy data if any has been produced
|
||||||
|
if (physics.state.data_ready) {
|
||||||
|
masses = physics.state.masses;
|
||||||
|
springs = physics.state.springs;
|
||||||
|
|
||||||
|
physics.state.data_ready = false;
|
||||||
|
physics.state.data_consumed = true;
|
||||||
|
|
||||||
|
lock.unlock();
|
||||||
|
// Notify the physics thread that data has been consumed
|
||||||
|
physics.state.data_consumed_cnd.notify_all();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
FrameMarkEnd("MainThreadConsumeLock");
|
||||||
|
|
||||||
// Update the camera after the physics, so target lock is smooth
|
// Update the camera after the physics, so target lock is smooth
|
||||||
camera.Update(mass_springs.GetMass(state.current_state).position);
|
std::size_t current_index = state.CurrentMassIndex();
|
||||||
|
if (masses.size() > current_index) {
|
||||||
|
const Mass ¤t_mass = masses.at(current_index);
|
||||||
|
camera.Update(current_mass.position);
|
||||||
|
}
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
renderer.UpdateTextureSizes();
|
renderer.UpdateTextureSizes();
|
||||||
renderer.DrawMassSprings(mass_springs, state.current_state,
|
renderer.DrawMassSprings(masses, springs);
|
||||||
state.starting_state, state.winning_states,
|
renderer.DrawKlotski();
|
||||||
state.visited_states);
|
renderer.DrawMenu(masses, springs);
|
||||||
|
renderer.DrawTextures(ups);
|
||||||
renderer.DrawKlotski(state.current_state, input.hov_x, input.hov_y,
|
FrameMarkEnd("MainThread");
|
||||||
input.sel_x, input.sel_y, input.block_add_x,
|
|
||||||
input.block_add_y, state.CurrentWinCondition());
|
|
||||||
renderer.DrawMenu(mass_springs, state.current_preset, state.current_state,
|
|
||||||
state.winning_states);
|
|
||||||
renderer.DrawTextures();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseWindow();
|
CloseWindow();
|
||||||
|
|||||||
338
src/physics.cpp
338
src/physics.cpp
@ -2,20 +2,19 @@
|
|||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "tracy.hpp"
|
#include "tracy.hpp"
|
||||||
|
|
||||||
|
#include <BS_thread_pool.hpp>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <mutex>
|
||||||
|
#include <ratio>
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
#include <tracy/Tracy.hpp>
|
#include <tracy/Tracy.hpp>
|
||||||
#include <unordered_map>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifndef BARNES_HUT
|
|
||||||
#include <numeric>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
auto Mass::ClearForce() -> void { force = Vector3Zero(); }
|
auto Mass::ClearForce() -> void { force = Vector3Zero(); }
|
||||||
|
|
||||||
auto Mass::CalculateVelocity(const float delta_time) -> void {
|
auto Mass::CalculateVelocity(const float delta_time) -> void {
|
||||||
@ -69,58 +68,29 @@ auto Spring::CalculateSpringForce(Mass &_mass_a, Mass &_mass_b) const -> void {
|
|||||||
_mass_b.force = Vector3Add(_mass_b.force, force_b);
|
_mass_b.force = Vector3Add(_mass_b.force, force_b);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::AddMass(float mass, bool fixed, const State &state)
|
auto MassSpringSystem::AddMass() -> void { masses.emplace_back(Vector3Zero()); }
|
||||||
-> void {
|
|
||||||
if (!state_masses.contains(state)) {
|
auto MassSpringSystem::AddSpring(int a, int b) -> void {
|
||||||
masses.emplace_back(Vector3Zero());
|
Mass &mass_a = masses.at(a);
|
||||||
std::size_t idx = masses.size() - 1;
|
Mass &mass_b = masses.at(b);
|
||||||
state_masses.insert(std::make_pair(state, idx));
|
|
||||||
|
Vector3 position = mass_a.position;
|
||||||
|
Vector3 offset = Vector3(static_cast<float>(GetRandomValue(-100, 100)),
|
||||||
|
static_cast<float>(GetRandomValue(-100, 100)),
|
||||||
|
static_cast<float>(GetRandomValue(-100, 100)));
|
||||||
|
offset = Vector3Scale(Vector3Normalize(offset), REST_LENGTH);
|
||||||
|
|
||||||
|
if (mass_b.position == Vector3Zero()) {
|
||||||
|
mass_b.position = Vector3Add(position, offset);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto MassSpringSystem::GetMass(const State &state) -> Mass & {
|
springs.emplace_back(a, b);
|
||||||
return masses.at(state_masses.at(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto MassSpringSystem::GetMass(const State &state) const -> const Mass & {
|
|
||||||
return masses.at(state_masses.at(state));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto MassSpringSystem::AddSpring(const State &state_a, const State &state_b,
|
|
||||||
float spring_constant,
|
|
||||||
float dampening_constant, float rest_length)
|
|
||||||
-> void {
|
|
||||||
std::pair<State, State> key = std::make_pair(state_a, state_b);
|
|
||||||
if (!state_springs.contains(key)) {
|
|
||||||
int a = state_masses.at(state_a);
|
|
||||||
int b = state_masses.at(state_b);
|
|
||||||
const Mass &mass_a = masses.at(a);
|
|
||||||
Mass &mass_b = masses.at(b);
|
|
||||||
|
|
||||||
Vector3 position = mass_a.position;
|
|
||||||
Vector3 offset = Vector3(static_cast<float>(GetRandomValue(-100, 100)),
|
|
||||||
static_cast<float>(GetRandomValue(-100, 100)),
|
|
||||||
static_cast<float>(GetRandomValue(-100, 100)));
|
|
||||||
offset = Vector3Scale(Vector3Normalize(offset), REST_LENGTH);
|
|
||||||
|
|
||||||
if (mass_b.position == Vector3Zero()) {
|
|
||||||
mass_b.position = Vector3Add(position, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
springs.emplace_back(a, b);
|
|
||||||
int idx = springs.size() - 1;
|
|
||||||
state_springs.insert(std::make_pair(key, idx));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::Clear() -> void {
|
auto MassSpringSystem::Clear() -> void {
|
||||||
masses.clear();
|
masses.clear();
|
||||||
state_masses.clear();
|
|
||||||
springs.clear();
|
springs.clear();
|
||||||
state_springs.clear();
|
octree.nodes.clear();
|
||||||
#ifndef BARNES_HUT
|
|
||||||
InvalidateGrid();
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::ClearForces() -> void {
|
auto MassSpringSystem::ClearForces() -> void {
|
||||||
@ -135,13 +105,16 @@ auto MassSpringSystem::CalculateSpringForces() -> void {
|
|||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
|
||||||
for (const auto spring : springs) {
|
for (const auto spring : springs) {
|
||||||
Mass &a = masses.at(spring.mass_a);
|
Mass &a = masses.at(spring.a);
|
||||||
Mass &b = masses.at(spring.mass_b);
|
Mass &b = masses.at(spring.b);
|
||||||
spring.CalculateSpringForce(a, b);
|
spring.CalculateSpringForce(a, b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef BARNES_HUT
|
auto MassSpringSystem::SetThreadName(std::size_t idx) -> void {
|
||||||
|
BS::this_thread::set_os_thread_name(std::format("bh-worker-{}", idx));
|
||||||
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::BuildOctree() -> void {
|
auto MassSpringSystem::BuildOctree() -> void {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
|
||||||
@ -177,52 +150,9 @@ auto MassSpringSystem::BuildOctree() -> void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
auto MassSpringSystem::BuildUniformGrid() -> void {
|
|
||||||
// Use a vector of pointers to masses, because we can't parallelize the
|
|
||||||
// range-based for loop over the masses unordered_map using OpenMP.
|
|
||||||
mass_pointers.clear();
|
|
||||||
mass_pointers.reserve(masses.size());
|
|
||||||
for (auto &[state, mass] : masses) {
|
|
||||||
mass_pointers.push_back(&mass);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign each mass a cell_id based on its position.
|
|
||||||
auto cell_id = [&](const Vector3 &position) -> int64_t {
|
|
||||||
int x = (int)std::floor(position.x / REPULSION_RANGE);
|
|
||||||
int y = (int)std::floor(position.y / REPULSION_RANGE);
|
|
||||||
int z = (int)std::floor(position.z / REPULSION_RANGE);
|
|
||||||
// Pack into a single int64 (assumes a coordinate fits in 20 bits)
|
|
||||||
return ((int64_t)(x & 0xFFFFF) << 40) | ((int64_t)(y & 0xFFFFF) << 20) |
|
|
||||||
(int64_t)(z & 0xFFFFF);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sort mass indices by cell_id to improve cache locality and allow cell
|
|
||||||
// iteration with std::lower_bound and std::upper_bound
|
|
||||||
mass_indices.clear();
|
|
||||||
mass_indices.resize(masses.size());
|
|
||||||
std::iota(mass_indices.begin(), mass_indices.end(),
|
|
||||||
0); // Fill the indices array with ascending numbers
|
|
||||||
std::sort(mass_indices.begin(), mass_indices.end(), [&](int a, int b) {
|
|
||||||
return cell_id(mass_pointers[a]->position) <
|
|
||||||
cell_id(mass_pointers[b]->position);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build cell start/end table: maps mass index to cell_id.
|
|
||||||
// All indices of a single cell are consecutive.
|
|
||||||
cell_ids.clear();
|
|
||||||
cell_ids.resize(masses.size());
|
|
||||||
for (int i = 0; i < masses.size(); ++i) {
|
|
||||||
cell_ids[i] = cell_id(mass_pointers[mass_indices[i]]->position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
auto MassSpringSystem::CalculateRepulsionForces() -> void {
|
auto MassSpringSystem::CalculateRepulsionForces() -> void {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
|
||||||
#ifdef BARNES_HUT
|
|
||||||
BuildOctree();
|
BuildOctree();
|
||||||
|
|
||||||
auto solve_octree = [&](int i) {
|
auto solve_octree = [&](int i) {
|
||||||
@ -240,86 +170,6 @@ auto MassSpringSystem::CalculateRepulsionForces() -> void {
|
|||||||
threads.submit_loop(0, masses.size(), solve_octree, 256);
|
threads.submit_loop(0, masses.size(), solve_octree, 256);
|
||||||
loop_future.wait();
|
loop_future.wait();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
// Refresh grid if necessary
|
|
||||||
if (last_build >= REPULSION_GRID_REFRESH ||
|
|
||||||
masses.size() != last_masses_count ||
|
|
||||||
springs.size() != last_springs_count) {
|
|
||||||
BuildUniformGrid();
|
|
||||||
last_build = 0;
|
|
||||||
last_masses_count = masses.size();
|
|
||||||
last_springs_count = springs.size();
|
|
||||||
}
|
|
||||||
last_build++;
|
|
||||||
|
|
||||||
auto solve_grid = [&](int i) {
|
|
||||||
Mass *mass = mass_pointers[mass_indices[i]];
|
|
||||||
int cell_x = (int)std::floor(mass->position.x / REPULSION_RANGE);
|
|
||||||
int cell_y = (int)std::floor(mass->position.y / REPULSION_RANGE);
|
|
||||||
int cell_z = (int)std::floor(mass->position.z / REPULSION_RANGE);
|
|
||||||
|
|
||||||
Vector3 force = Vector3Zero();
|
|
||||||
|
|
||||||
// Search all 3*3*3 neighbor cells for masses
|
|
||||||
for (int dx = -1; dx <= 1; ++dx) {
|
|
||||||
for (int dy = -1; dy <= 1; ++dy) {
|
|
||||||
for (int dz = -1; dz <= 1; ++dz) {
|
|
||||||
int64_t neighbor_id = ((int64_t)((cell_x + dx) & 0xFFFFF) << 40) |
|
|
||||||
((int64_t)((cell_y + dy) & 0xFFFFF) << 20) |
|
|
||||||
(int64_t)((cell_z + dz) & 0xFFFFF);
|
|
||||||
|
|
||||||
// Find the first and last occurence of the neighbor_id (iterator).
|
|
||||||
// Because cell_ids is sorted, all elements of this cell are between
|
|
||||||
// those.
|
|
||||||
// If there is no cell, the iterators just won't do anything.
|
|
||||||
auto cell_start =
|
|
||||||
std::lower_bound(cell_ids.begin(), cell_ids.end(), neighbor_id);
|
|
||||||
auto cell_end =
|
|
||||||
std::upper_bound(cell_ids.begin(), cell_ids.end(), neighbor_id);
|
|
||||||
|
|
||||||
// For each mass, iterate through all the masses of neighboring cells
|
|
||||||
// to accumulate the repulsion forces.
|
|
||||||
// This is slow with O(n * m), where m is the number of masses in each
|
|
||||||
// neighboring cell.
|
|
||||||
for (auto it = cell_start; it != cell_end; ++it) {
|
|
||||||
Mass *neighbor = mass_pointers[mass_indices[it - cell_ids.begin()]];
|
|
||||||
if (neighbor == mass) {
|
|
||||||
// Skip ourselves
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 direction =
|
|
||||||
Vector3Subtract(mass->position, neighbor->position);
|
|
||||||
float distance = Vector3Length(direction);
|
|
||||||
if (std::abs(distance) <= 0.001f || distance >= REPULSION_RANGE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
force = Vector3Add(
|
|
||||||
force, Vector3Scale(Vector3Normalize(direction), GRID_FORCE));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mass->force = Vector3Add(mass->force, force);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Calculate forces using uniform grid
|
|
||||||
#ifdef WEB
|
|
||||||
// Search the neighboring cells for each mass to calculate repulsion forces
|
|
||||||
for (int i = 0; i < mass_pointers.size(); ++i) {
|
|
||||||
calculate_grid(i);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
BS::multi_future<void> loop_future =
|
|
||||||
threads.submit_loop(0, mass_pointers.size(), solve_grid, 512);
|
|
||||||
loop_future.wait();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::VerletUpdate(float delta_time) -> void {
|
auto MassSpringSystem::VerletUpdate(float delta_time) -> void {
|
||||||
@ -330,13 +180,131 @@ auto MassSpringSystem::VerletUpdate(float delta_time) -> void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef BARNES_HUT
|
auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state)
|
||||||
auto MassSpringSystem::InvalidateGrid() -> void {
|
-> void {
|
||||||
mass_pointers.clear();
|
BS::this_thread::set_os_thread_name("physics");
|
||||||
mass_indices.clear();
|
|
||||||
cell_ids.clear();
|
MassSpringSystem mass_springs;
|
||||||
last_build = REPULSION_GRID_REFRESH;
|
|
||||||
last_masses_count = 0;
|
const auto visitor = overloads{
|
||||||
last_springs_count = 0;
|
[&](const struct AddMass &am) { mass_springs.AddMass(); },
|
||||||
|
[&](const struct AddSpring &as) { mass_springs.AddSpring(as.a, as.b); },
|
||||||
|
[&](const struct ClearGraph &cg) { mass_springs.Clear(); },
|
||||||
|
};
|
||||||
|
|
||||||
|
std::chrono::time_point last = std::chrono::high_resolution_clock::now();
|
||||||
|
std::chrono::duration<double> accumulator(0);
|
||||||
|
std::chrono::duration<double> update_accumulator(0);
|
||||||
|
unsigned int updates = 0;
|
||||||
|
|
||||||
|
while (state.running.load()) {
|
||||||
|
FrameMarkStart("PhysicsThread");
|
||||||
|
|
||||||
|
// Time tracking
|
||||||
|
std::chrono::time_point now = std::chrono::high_resolution_clock::now();
|
||||||
|
std::chrono::duration<double> deltatime = now - last;
|
||||||
|
accumulator += deltatime;
|
||||||
|
update_accumulator += deltatime;
|
||||||
|
last = now;
|
||||||
|
|
||||||
|
// Handle queued commands
|
||||||
|
{
|
||||||
|
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
|
||||||
|
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 (accumulator.count() > TIMESTEP) {
|
||||||
|
mass_springs.ClearForces();
|
||||||
|
mass_springs.CalculateSpringForces();
|
||||||
|
mass_springs.CalculateRepulsionForces();
|
||||||
|
mass_springs.VerletUpdate(TIMESTEP * SIM_SPEED);
|
||||||
|
|
||||||
|
++updates;
|
||||||
|
accumulator -= std::chrono::duration<double>(TIMESTEP);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publish the positions for the renderer (copy)
|
||||||
|
FrameMarkStart("PhysicsThreadProduceLock");
|
||||||
|
{
|
||||||
|
std::unique_lock<LockableBase(std::mutex)> lock(state.data_mtx);
|
||||||
|
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 (update_accumulator.count() > 1.0) {
|
||||||
|
// Update each second
|
||||||
|
state.ups = updates;
|
||||||
|
updates = 0;
|
||||||
|
update_accumulator = std::chrono::duration<double>(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.masses.clear();
|
||||||
|
state.masses.reserve(mass_springs.masses.size());
|
||||||
|
for (const auto &mass : mass_springs.masses) {
|
||||||
|
state.masses.emplace_back(mass.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.springs.clear();
|
||||||
|
state.springs.reserve(mass_springs.springs.size());
|
||||||
|
for (const auto &spring : mass_springs.springs) {
|
||||||
|
state.springs.emplace_back(spring.a, spring.b);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.data_ready = true;
|
||||||
|
state.data_consumed = false;
|
||||||
|
}
|
||||||
|
// Notify the rendering thread that new data is available
|
||||||
|
state.data_ready_cnd.notify_all();
|
||||||
|
FrameMarkEnd("PhysicsThreadProduceLock");
|
||||||
|
|
||||||
|
FrameMarkEnd("PhysicsThread");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ThreadedPhysics::AddMassCmd() -> void {
|
||||||
|
{
|
||||||
|
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
|
||||||
|
state.pending_commands.push(AddMass{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ThreadedPhysics::AddSpringCmd(std::size_t a, std::size_t b) -> void {
|
||||||
|
{
|
||||||
|
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
|
||||||
|
state.pending_commands.push(AddSpring{a, b});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ThreadedPhysics::ClearCmd() -> void {
|
||||||
|
{
|
||||||
|
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
|
||||||
|
state.pending_commands.push(ClearGraph{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ThreadedPhysics::AddMassSpringsCmd(
|
||||||
|
std::size_t num_masses,
|
||||||
|
const std::vector<std::pair<std::size_t, std::size_t>> &springs) -> void {
|
||||||
|
{
|
||||||
|
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
|
||||||
|
for (std::size_t i = 0; i < num_masses; ++i) {
|
||||||
|
state.pending_commands.push(AddMass{});
|
||||||
|
}
|
||||||
|
for (const auto &[from, to] : springs) {
|
||||||
|
state.pending_commands.push(AddSpring{from, to});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
#include "puzzle.hpp"
|
#include "puzzle.hpp"
|
||||||
#include "tracy.hpp"
|
#include "tracy.hpp"
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
auto Block::Hash() const -> int {
|
auto Block::Hash() const -> int {
|
||||||
std::string s = std::format("{},{},{},{}", x, y, width, height);
|
std::string s = std::format("{},{},{},{}", x, y, width, height);
|
||||||
return std::hash<std::string>{}(s);
|
return std::hash<std::string>{}(s);
|
||||||
@ -262,11 +264,16 @@ auto State::GetNextStates() const -> std::vector<State> {
|
|||||||
return new_states;
|
return new_states;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto State::Closure() const -> std::pair<std::unordered_set<State>,
|
auto State::Closure() const
|
||||||
std::vector<std::pair<State, State>>> {
|
-> std::pair<std::vector<State>,
|
||||||
std::unordered_set<State> states;
|
std::vector<std::pair<std::size_t, std::size_t>>> {
|
||||||
std::vector<std::pair<State, State>> links;
|
std::vector<State> states;
|
||||||
|
std::vector<std::pair<std::size_t, std::size_t>> links;
|
||||||
|
|
||||||
|
// Helper to construct the links vector
|
||||||
|
std::unordered_map<State, std::size_t> state_indices;
|
||||||
|
|
||||||
|
// Buffer for all states we want to call GetNextStates() on
|
||||||
std::unordered_set<State> remaining_states;
|
std::unordered_set<State> remaining_states;
|
||||||
remaining_states.insert(*this);
|
remaining_states.insert(*this);
|
||||||
|
|
||||||
@ -274,13 +281,18 @@ auto State::Closure() const -> std::pair<std::unordered_set<State>,
|
|||||||
const State current = *remaining_states.begin();
|
const State current = *remaining_states.begin();
|
||||||
remaining_states.erase(current);
|
remaining_states.erase(current);
|
||||||
|
|
||||||
std::vector<State> new_states = current.GetNextStates();
|
if (!state_indices.contains(current)) {
|
||||||
for (const State &s : new_states) {
|
state_indices.emplace(current, states.size());
|
||||||
if (!states.contains(s)) {
|
states.push_back(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const State &s : current.GetNextStates()) {
|
||||||
|
if (!state_indices.contains(s)) {
|
||||||
remaining_states.insert(s);
|
remaining_states.insert(s);
|
||||||
states.insert(s);
|
state_indices.emplace(s, states.size());
|
||||||
|
states.push_back(s);
|
||||||
}
|
}
|
||||||
links.emplace_back(current.state, s.state);
|
links.emplace_back(state_indices.at(current), state_indices.at(s));
|
||||||
}
|
}
|
||||||
} while (remaining_states.size() > 0);
|
} while (remaining_states.size() > 0);
|
||||||
|
|
||||||
|
|||||||
194
src/renderer.cpp
194
src/renderer.cpp
@ -1,6 +1,5 @@
|
|||||||
#include "renderer.hpp"
|
#include "renderer.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "physics.hpp"
|
|
||||||
#include "puzzle.hpp"
|
#include "puzzle.hpp"
|
||||||
#include "tracy.hpp"
|
#include "tracy.hpp"
|
||||||
|
|
||||||
@ -10,7 +9,6 @@
|
|||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
#include <rlgl.h>
|
#include <rlgl.h>
|
||||||
#include <tracy/Tracy.hpp>
|
#include <tracy/Tracy.hpp>
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
#ifdef BATCHING
|
#ifdef BATCHING
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@ -33,8 +31,7 @@ auto Renderer::UpdateTextureSizes() -> void {
|
|||||||
menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT);
|
menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::AllocateGraphInstancing(const MassSpringSystem &mass_springs)
|
auto Renderer::AllocateGraphInstancing(std::size_t size) -> void {
|
||||||
-> void {
|
|
||||||
cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE);
|
cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE);
|
||||||
|
|
||||||
instancing_shader = LoadShader("shader/instancing_vertex.glsl",
|
instancing_shader = LoadShader("shader/instancing_vertex.glsl",
|
||||||
@ -48,40 +45,34 @@ auto Renderer::AllocateGraphInstancing(const MassSpringSystem &mass_springs)
|
|||||||
vertex_mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR;
|
vertex_mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR;
|
||||||
vertex_mat.shader = instancing_shader;
|
vertex_mat.shader = instancing_shader;
|
||||||
|
|
||||||
transforms = (Matrix *)MemAlloc(mass_springs.masses.size() * sizeof(Matrix));
|
transforms = (Matrix *)MemAlloc(size * sizeof(Matrix));
|
||||||
transforms_size = mass_springs.masses.size();
|
transforms_size = size;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::ReallocateGraphInstancingIfNecessary(
|
auto Renderer::ReallocateGraphInstancingIfNecessary(std::size_t size) -> void {
|
||||||
const MassSpringSystem &mass_springs) -> void {
|
if (transforms_size != size) {
|
||||||
if (transforms_size != mass_springs.masses.size()) {
|
transforms = (Matrix *)MemRealloc(transforms, size * sizeof(Matrix));
|
||||||
transforms = (Matrix *)MemRealloc(transforms, mass_springs.masses.size() *
|
transforms_size = size;
|
||||||
sizeof(Matrix));
|
|
||||||
transforms_size = mass_springs.masses.size();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs,
|
auto Renderer::DrawMassSprings(
|
||||||
const State ¤t_state,
|
const std::vector<Vector3> &masses,
|
||||||
const State &starting_state,
|
const std::vector<std::pair<std::size_t, std::size_t>> &springs) -> void {
|
||||||
const std::unordered_set<State> &winning_states,
|
|
||||||
const std::unordered_set<State> &visited_states)
|
|
||||||
-> void {
|
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
|
||||||
// Prepare cube instancing
|
// Prepare cube instancing
|
||||||
{
|
{
|
||||||
ZoneNamedN(prepare_masses, "PrepareMasses", true);
|
ZoneNamedN(prepare_masses, "PrepareMasses", true);
|
||||||
if (mass_springs.masses.size() < DRAW_VERTICES_LIMIT) {
|
if (masses.size() < DRAW_VERTICES_LIMIT) {
|
||||||
if (transforms == nullptr) {
|
if (transforms == nullptr) {
|
||||||
AllocateGraphInstancing(mass_springs);
|
AllocateGraphInstancing(masses.size());
|
||||||
}
|
}
|
||||||
ReallocateGraphInstancingIfNecessary(mass_springs);
|
ReallocateGraphInstancingIfNecessary(masses.size());
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const auto &mass : mass_springs.masses) {
|
for (const Vector3 &mass : masses) {
|
||||||
transforms[i] =
|
transforms[i] = MatrixTranslate(mass.x, mass.y, mass.z);
|
||||||
MatrixTranslate(mass.position.x, mass.position.y, mass.position.z);
|
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,13 +87,14 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs,
|
|||||||
{
|
{
|
||||||
ZoneNamedN(draw_springs, "DrawSprings", true);
|
ZoneNamedN(draw_springs, "DrawSprings", true);
|
||||||
rlBegin(RL_LINES);
|
rlBegin(RL_LINES);
|
||||||
for (const auto &spring : mass_springs.springs) {
|
for (const auto &[from, to] : springs) {
|
||||||
// We have to do a lookup of the actual mass object, which is slow :(
|
if (masses.size() > from && masses.size() > to) {
|
||||||
const Mass &a = mass_springs.masses.at(spring.mass_a);
|
const Vector3 &a = masses.at(from);
|
||||||
const Mass &b = mass_springs.masses.at(spring.mass_b);
|
const Vector3 &b = masses.at(to);
|
||||||
rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a);
|
rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a);
|
||||||
rlVertex3f(a.position.x, a.position.y, a.position.z);
|
rlVertex3f(a.x, a.y, a.z);
|
||||||
rlVertex3f(b.position.x, b.position.y, b.position.z);
|
rlVertex3f(b.x, b.y, b.z);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rlEnd();
|
rlEnd();
|
||||||
}
|
}
|
||||||
@ -110,49 +102,63 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs,
|
|||||||
// Draw masses (instanced)
|
// Draw masses (instanced)
|
||||||
{
|
{
|
||||||
ZoneNamedN(draw_masses, "DrawMasses", true);
|
ZoneNamedN(draw_masses, "DrawMasses", true);
|
||||||
if (mass_springs.masses.size() < DRAW_VERTICES_LIMIT) {
|
if (masses.size() < DRAW_VERTICES_LIMIT) {
|
||||||
// NOTE: I don't know if drawing all this inside a shader would make it
|
// NOTE: I don't know if drawing all this inside a shader would make it
|
||||||
// much faster... The amount of data sent to the GPU would be
|
// much faster... The amount of data sent to the GPU would be
|
||||||
// reduced (just positions instead of matrices), but is this
|
// reduced (just positions instead of matrices), but is this
|
||||||
// noticable for < 100000 cubes?
|
// noticable for < 100000 cubes?
|
||||||
DrawMeshInstanced(cube_instance, vertex_mat, transforms,
|
DrawMeshInstanced(cube_instance, vertex_mat, transforms, masses.size());
|
||||||
mass_springs.masses.size());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark winning states
|
// Mark winning states
|
||||||
if (mark_solutions || connect_solutions) {
|
if (input.mark_solutions || input.connect_solutions) {
|
||||||
for (const auto &state : winning_states) {
|
for (const State &_state : state.winning_states) {
|
||||||
const Mass &winning_mass = mass_springs.GetMass(state);
|
|
||||||
if (mark_solutions) {
|
|
||||||
DrawCube(winning_mass.position, 2 * VERTEX_SIZE, 2 * VERTEX_SIZE,
|
|
||||||
2 * VERTEX_SIZE, BLUE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connect_solutions) {
|
std::size_t winning_index = state.states.at(_state);
|
||||||
DrawLine3D(winning_mass.position,
|
if (masses.size() > winning_index) {
|
||||||
mass_springs.GetMass(current_state).position, PURPLE);
|
|
||||||
|
const Vector3 &winning_mass = masses.at(winning_index);
|
||||||
|
if (input.mark_solutions) {
|
||||||
|
DrawCube(winning_mass, 2 * VERTEX_SIZE, 2 * VERTEX_SIZE,
|
||||||
|
2 * VERTEX_SIZE, BLUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t current_index = state.CurrentMassIndex();
|
||||||
|
if (input.connect_solutions && masses.size() > current_index) {
|
||||||
|
const Vector3 ¤t_mass = masses.at(current_index);
|
||||||
|
DrawLine3D(winning_mass, current_mass, PURPLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark visited states
|
// Mark visited states
|
||||||
for (const auto &state : visited_states) {
|
for (const State &_state : state.visited_states) {
|
||||||
const Mass &visited_mass = mass_springs.GetMass(state);
|
std::size_t visited_index = state.states.at(_state);
|
||||||
|
|
||||||
DrawCube(visited_mass.position, VERTEX_SIZE * 1.5, VERTEX_SIZE * 1.5,
|
if (masses.size() > visited_index) {
|
||||||
VERTEX_SIZE * 1.5, PURPLE);
|
const Vector3 &visited_mass = masses.at(visited_index);
|
||||||
|
DrawCube(visited_mass, VERTEX_SIZE * 1.5, VERTEX_SIZE * 1.5,
|
||||||
|
VERTEX_SIZE * 1.5, PURPLE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark starting state
|
// Mark starting state
|
||||||
const Mass &starting_mass = mass_springs.GetMass(starting_state);
|
std::size_t starting_index = state.states.at(state.starting_state);
|
||||||
DrawCube(starting_mass.position, VERTEX_SIZE * 2, VERTEX_SIZE * 2,
|
if (masses.size() > starting_index) {
|
||||||
VERTEX_SIZE * 2, ORANGE);
|
const Vector3 &starting_mass = masses.at(starting_index);
|
||||||
|
DrawCube(starting_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2,
|
||||||
|
ORANGE);
|
||||||
|
}
|
||||||
|
|
||||||
// Mark current state
|
// Mark current state
|
||||||
const Mass ¤t_mass = mass_springs.GetMass(current_state);
|
std::size_t current_index = state.states.at(state.current_state);
|
||||||
DrawCube(current_mass.position, VERTEX_SIZE * 2, VERTEX_SIZE * 2,
|
if (masses.size() > current_index) {
|
||||||
VERTEX_SIZE * 2, RED);
|
const Vector3 ¤t_mass = masses.at(current_index);
|
||||||
|
DrawCube(current_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2,
|
||||||
|
RED);
|
||||||
|
}
|
||||||
|
|
||||||
// DrawCubeWires(current_mass.position, REPULSION_RANGE, REPULSION_RANGE,
|
// DrawCubeWires(current_mass.position, REPULSION_RANGE, REPULSION_RANGE,
|
||||||
// REPULSION_RANGE, BLACK);
|
// REPULSION_RANGE, BLACK);
|
||||||
@ -164,9 +170,7 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs,
|
|||||||
EndTextureMode();
|
EndTextureMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
|
auto Renderer::DrawKlotski() -> void {
|
||||||
int sel_y, int block_add_x, int block_add_y,
|
|
||||||
const WinCondition win_condition) -> void {
|
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
|
||||||
BeginTextureMode(klotski_target);
|
BeginTextureMode(klotski_target);
|
||||||
@ -175,22 +179,26 @@ auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
|
|||||||
// Draw Board
|
// Draw Board
|
||||||
const int board_width = GetScreenWidth() / 2 - 2 * BOARD_PADDING;
|
const int board_width = GetScreenWidth() / 2 - 2 * BOARD_PADDING;
|
||||||
const int board_height = GetScreenHeight() - MENU_HEIGHT - 2 * BOARD_PADDING;
|
const int board_height = GetScreenHeight() - MENU_HEIGHT - 2 * BOARD_PADDING;
|
||||||
int block_size =
|
int block_size = std::min(board_width / state.current_state.width,
|
||||||
std::min(board_width / state.width, board_height / state.height) -
|
board_height / state.current_state.height) -
|
||||||
2 * BLOCK_PADDING;
|
2 * BLOCK_PADDING;
|
||||||
int x_offset =
|
int x_offset = (board_width - (block_size + 2 * BLOCK_PADDING) *
|
||||||
(board_width - (block_size + 2 * BLOCK_PADDING) * state.width) / 2.0;
|
state.current_state.width) /
|
||||||
int y_offset =
|
2.0;
|
||||||
(board_height - (block_size + 2 * BLOCK_PADDING) * state.height) / 2.0;
|
int y_offset = (board_height - (block_size + 2 * BLOCK_PADDING) *
|
||||||
|
state.current_state.height) /
|
||||||
|
2.0;
|
||||||
|
|
||||||
DrawRectangle(0, 0, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT,
|
DrawRectangle(0, 0, GetScreenWidth() / 2, GetScreenHeight() - MENU_HEIGHT,
|
||||||
RAYWHITE);
|
RAYWHITE);
|
||||||
DrawRectangle(
|
DrawRectangle(x_offset, y_offset,
|
||||||
x_offset, y_offset, board_width - 2 * x_offset + 2 * BOARD_PADDING,
|
board_width - 2 * x_offset + 2 * BOARD_PADDING,
|
||||||
board_height - 2 * y_offset + 2 * BOARD_PADDING,
|
board_height - 2 * y_offset + 2 * BOARD_PADDING,
|
||||||
win_condition(state) ? GREEN : (state.restricted ? DARKGRAY : LIGHTGRAY));
|
state.CurrentWinCondition()(state.current_state)
|
||||||
for (int x = 0; x < state.width; ++x) {
|
? GREEN
|
||||||
for (int y = 0; y < state.height; ++y) {
|
: (state.current_state.restricted ? DARKGRAY : LIGHTGRAY));
|
||||||
|
for (int x = 0; x < state.current_state.width; ++x) {
|
||||||
|
for (int y = 0; y < state.current_state.height; ++y) {
|
||||||
DrawRectangle(x_offset + BOARD_PADDING + x * BLOCK_PADDING * 2 +
|
DrawRectangle(x_offset + BOARD_PADDING + x * BLOCK_PADDING * 2 +
|
||||||
BLOCK_PADDING + x * block_size,
|
BLOCK_PADDING + x * block_size,
|
||||||
y_offset + BOARD_PADDING + y * BLOCK_PADDING * 2 +
|
y_offset + BOARD_PADDING + y * BLOCK_PADDING * 2 +
|
||||||
@ -200,13 +208,13 @@ auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw Blocks
|
// Draw Blocks
|
||||||
for (Block block : state) {
|
for (Block block : state.current_state) {
|
||||||
Color c = BLOCK_COLOR;
|
Color c = BLOCK_COLOR;
|
||||||
if (block.Covers(sel_x, sel_y)) {
|
if (block.Covers(input.sel_x, input.sel_y)) {
|
||||||
c = HL_BLOCK_COLOR;
|
c = HL_BLOCK_COLOR;
|
||||||
}
|
}
|
||||||
if (block.target) {
|
if (block.target) {
|
||||||
if (block.Covers(sel_x, sel_y)) {
|
if (block.Covers(input.sel_x, input.sel_y)) {
|
||||||
c = HL_TARGET_BLOCK_COLOR;
|
c = HL_TARGET_BLOCK_COLOR;
|
||||||
} else {
|
} else {
|
||||||
c = TARGET_BLOCK_COLOR;
|
c = TARGET_BLOCK_COLOR;
|
||||||
@ -222,7 +230,7 @@ auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
|
|||||||
2 * BLOCK_PADDING,
|
2 * BLOCK_PADDING,
|
||||||
c);
|
c);
|
||||||
|
|
||||||
if (block.Covers(hov_x, hov_y)) {
|
if (block.Covers(input.hov_x, input.hov_y)) {
|
||||||
DrawRectangleLinesEx(
|
DrawRectangleLinesEx(
|
||||||
Rectangle(x_offset + BOARD_PADDING + block.x * BLOCK_PADDING * 2 +
|
Rectangle(x_offset + BOARD_PADDING + block.x * BLOCK_PADDING * 2 +
|
||||||
BLOCK_PADDING + block.x * block_size,
|
BLOCK_PADDING + block.x * block_size,
|
||||||
@ -237,12 +245,13 @@ auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw editing starting position
|
// Draw editing starting position
|
||||||
if (block_add_x >= 0 && block_add_y >= 0) {
|
if (input.block_add_x >= 0 && input.block_add_y >= 0) {
|
||||||
DrawCircle(x_offset + BOARD_PADDING + block_add_x * BLOCK_PADDING * 2 +
|
DrawCircle(
|
||||||
BLOCK_PADDING + block_add_x * block_size + block_size / 2,
|
x_offset + BOARD_PADDING + input.block_add_x * BLOCK_PADDING * 2 +
|
||||||
y_offset + BOARD_PADDING + block_add_y * BLOCK_PADDING * 2 +
|
BLOCK_PADDING + input.block_add_x * block_size + block_size / 2,
|
||||||
BLOCK_PADDING + block_add_y * block_size + block_size / 2,
|
y_offset + BOARD_PADDING + input.block_add_y * BLOCK_PADDING * 2 +
|
||||||
block_size / 10.0, Fade(BLACK, 0.5));
|
BLOCK_PADDING + input.block_add_y * block_size + block_size / 2,
|
||||||
|
block_size / 10.0, Fade(BLACK, 0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawLine(GetScreenWidth() / 2 - 1, 0, GetScreenWidth() / 2 - 1,
|
DrawLine(GetScreenWidth() / 2 - 1, 0, GetScreenWidth() / 2 - 1,
|
||||||
@ -250,10 +259,9 @@ auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
|
|||||||
EndTextureMode();
|
EndTextureMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::DrawMenu(const MassSpringSystem &mass_springs,
|
auto Renderer::DrawMenu(
|
||||||
int current_preset, const State ¤t_state,
|
const std::vector<Vector3> &masses,
|
||||||
const std::unordered_set<State> &winning_states)
|
const std::vector<std::pair<std::size_t, std::size_t>> &springs) -> void {
|
||||||
-> void {
|
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
|
||||||
BeginTextureMode(menu_target);
|
BeginTextureMode(menu_target);
|
||||||
@ -277,8 +285,8 @@ auto Renderer::DrawMenu(const MassSpringSystem &mass_springs,
|
|||||||
|
|
||||||
draw_btn(0, 0,
|
draw_btn(0, 0,
|
||||||
std::format("States: {}, Transitions: {}, Winning: {}",
|
std::format("States: {}, Transitions: {}, Winning: {}",
|
||||||
mass_springs.masses.size(), mass_springs.springs.size(),
|
masses.size(), springs.size(),
|
||||||
winning_states.size()),
|
state.winning_states.size()),
|
||||||
DARKGREEN);
|
DARKGREEN);
|
||||||
draw_btn(
|
draw_btn(
|
||||||
0, 1,
|
0, 1,
|
||||||
@ -294,21 +302,21 @@ auto Renderer::DrawMenu(const MassSpringSystem &mass_springs,
|
|||||||
draw_btn(1, 2, std::format("Print Board State to Console (P)"), DARKBLUE);
|
draw_btn(1, 2, std::format("Print Board State to Console (P)"), DARKBLUE);
|
||||||
|
|
||||||
draw_btn(2, 0,
|
draw_btn(2, 0,
|
||||||
std::format("Preset (M/N): {}, {} (F)", current_preset,
|
std::format("Preset (M/N): {}, {} (F)", state.current_preset,
|
||||||
current_state.restricted ? "Restricted" : "Free"),
|
state.current_state.restricted ? "Restricted" : "Free"),
|
||||||
DARKPURPLE);
|
DARKPURPLE);
|
||||||
draw_btn(2, 1, std::format("Populate Graph (G), Clear Graph (C)"),
|
draw_btn(2, 1, std::format("Populate Graph (G), Clear Graph (C)"),
|
||||||
DARKPURPLE);
|
DARKPURPLE);
|
||||||
draw_btn(2, 2,
|
draw_btn(2, 2,
|
||||||
std::format("Mark (I): {} / Connect (O): {}", mark_solutions,
|
std::format("Mark (I): {} / Connect (O): {}", input.mark_solutions,
|
||||||
connect_solutions),
|
input.connect_solutions),
|
||||||
DARKPURPLE);
|
DARKPURPLE);
|
||||||
|
|
||||||
DrawLine(0, MENU_HEIGHT - 1, GetScreenWidth(), MENU_HEIGHT - 1, BLACK);
|
DrawLine(0, MENU_HEIGHT - 1, GetScreenWidth(), MENU_HEIGHT - 1, BLACK);
|
||||||
EndTextureMode();
|
EndTextureMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::DrawTextures() -> void {
|
auto Renderer::DrawTextures(float ups) -> void {
|
||||||
BeginDrawing();
|
BeginDrawing();
|
||||||
DrawTextureRec(menu_target.texture,
|
DrawTextureRec(menu_target.texture,
|
||||||
Rectangle(0, 0, menu_target.texture.width,
|
Rectangle(0, 0, menu_target.texture.width,
|
||||||
@ -322,7 +330,9 @@ auto Renderer::DrawTextures() -> void {
|
|||||||
Rectangle(0, 0, render_target.texture.width,
|
Rectangle(0, 0, render_target.texture.width,
|
||||||
-1 * render_target.texture.height),
|
-1 * render_target.texture.height),
|
||||||
Vector2(GetScreenWidth() / 2.0, MENU_HEIGHT), WHITE);
|
Vector2(GetScreenWidth() / 2.0, MENU_HEIGHT), WHITE);
|
||||||
|
|
||||||
DrawFPS(GetScreenWidth() / 2 + 10, MENU_HEIGHT + 10);
|
DrawFPS(GetScreenWidth() / 2 + 10, MENU_HEIGHT + 10);
|
||||||
|
DrawText(TextFormat("%.0f UPS", ups), GetScreenWidth() / 2 + 120,
|
||||||
|
MENU_HEIGHT + 10, 20, ORANGE);
|
||||||
EndDrawing();
|
EndDrawing();
|
||||||
FrameMark;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,18 @@
|
|||||||
#include "state.hpp"
|
#include "state.hpp"
|
||||||
#include "config.hpp"
|
|
||||||
#include "presets.hpp"
|
#include "presets.hpp"
|
||||||
#include "tracy.hpp"
|
#include "tracy.hpp"
|
||||||
|
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
|
||||||
auto StateManager::LoadPreset(int preset) -> void {
|
auto StateManager::LoadPreset(int preset) -> void {
|
||||||
current_state = generators[preset]();
|
|
||||||
previous_state = current_state;
|
|
||||||
ClearGraph();
|
|
||||||
current_preset = preset;
|
current_preset = preset;
|
||||||
|
current_state = CurrentGenerator()();
|
||||||
|
ClearGraph();
|
||||||
edited = false;
|
edited = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::ResetState() -> void {
|
auto StateManager::ResetState() -> void {
|
||||||
current_state = generators[current_preset]();
|
current_state = CurrentGenerator()();
|
||||||
previous_state = current_state;
|
previous_state = current_state;
|
||||||
if (edited) {
|
if (edited) {
|
||||||
// We also need to clear the graph in case the state has been edited
|
// We also need to clear the graph in case the state has been edited
|
||||||
@ -35,69 +33,67 @@ auto StateManager::NextPreset() -> void {
|
|||||||
auto StateManager::FillGraph() -> void {
|
auto StateManager::FillGraph() -> void {
|
||||||
ClearGraph();
|
ClearGraph();
|
||||||
|
|
||||||
std::pair<std::unordered_set<State>, std::vector<std::pair<State, State>>>
|
std::pair<std::vector<State>,
|
||||||
|
std::vector<std::pair<std::size_t, std::size_t>>>
|
||||||
closure = current_state.Closure();
|
closure = current_state.Closure();
|
||||||
for (const auto &state : closure.first) {
|
|
||||||
mass_springs.AddMass(MASS, false, state);
|
physics.ClearCmd();
|
||||||
|
physics.AddMassSpringsCmd(closure.first.size(), closure.second);
|
||||||
|
for (const State &state : closure.first) {
|
||||||
|
states.insert(std::make_pair(state, states.size()));
|
||||||
}
|
}
|
||||||
for (const auto &[from, to] : closure.second) {
|
|
||||||
mass_springs.AddSpring(from, to, SPRING_CONSTANT, DAMPENING_CONSTANT,
|
|
||||||
REST_LENGTH);
|
|
||||||
}
|
|
||||||
std::cout << "Inserted " << mass_springs.masses.size() << " masses and "
|
|
||||||
<< mass_springs.springs.size() << " springs." << std::endl;
|
|
||||||
FindWinningStates();
|
FindWinningStates();
|
||||||
std::cout << "Consuming "
|
|
||||||
<< sizeof(decltype(*mass_springs.masses.begin())) *
|
|
||||||
mass_springs.masses.size()
|
|
||||||
<< " Bytes for masses." << std::endl;
|
|
||||||
std::cout << "Consuming "
|
|
||||||
<< sizeof(decltype(*mass_springs.springs.begin())) *
|
|
||||||
mass_springs.springs.size()
|
|
||||||
<< " Bytes for springs." << std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::UpdateGraph() -> void {
|
auto StateManager::UpdateGraph() -> void {
|
||||||
if (previous_state != current_state) {
|
if (previous_state == current_state) {
|
||||||
mass_springs.AddMass(MASS, false, current_state);
|
return;
|
||||||
mass_springs.AddSpring(current_state, previous_state, SPRING_CONSTANT,
|
}
|
||||||
DAMPENING_CONSTANT, REST_LENGTH);
|
|
||||||
if (win_conditions[current_preset](current_state)) {
|
if (!states.contains(current_state)) {
|
||||||
winning_states.insert(current_state);
|
states.insert(std::make_pair(current_state, states.size()));
|
||||||
}
|
physics.AddMassCmd();
|
||||||
visited_states.insert(current_state);
|
physics.AddSpringCmd(states.at(current_state), states.at(previous_state));
|
||||||
|
}
|
||||||
|
|
||||||
|
visited_states.insert(current_state);
|
||||||
|
if (win_conditions[current_preset](current_state)) {
|
||||||
|
winning_states.insert(current_state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::ClearGraph() -> void {
|
auto StateManager::ClearGraph() -> void {
|
||||||
|
states.clear();
|
||||||
winning_states.clear();
|
winning_states.clear();
|
||||||
visited_states.clear();
|
visited_states.clear();
|
||||||
mass_springs.Clear();
|
physics.ClearCmd();
|
||||||
mass_springs.AddMass(MASS, false, current_state);
|
|
||||||
|
|
||||||
// The previous_state is no longer in the graph
|
states.insert(std::make_pair(current_state, states.size()));
|
||||||
|
visited_states.insert(current_state);
|
||||||
|
physics.AddMassCmd();
|
||||||
|
|
||||||
|
// These states are no longer in the graph
|
||||||
previous_state = current_state;
|
previous_state = current_state;
|
||||||
|
|
||||||
// The starting state is no longer in the graph
|
|
||||||
starting_state = current_state;
|
starting_state = current_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::FindWinningStates() -> void {
|
auto StateManager::FindWinningStates() -> void {
|
||||||
winning_states.clear();
|
winning_states.clear();
|
||||||
for (const auto &[state, mass] : mass_springs.state_masses) {
|
for (const auto &[state, mass] : states) {
|
||||||
if (win_conditions[current_preset](state)) {
|
if (CurrentWinCondition()(state)) {
|
||||||
winning_states.insert(state);
|
winning_states.insert(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::cout << "Found " << winning_states.size() << " winning states."
|
|
||||||
<< std::endl;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::CurrentGenerator() -> StateGenerator {
|
auto StateManager::CurrentGenerator() const -> StateGenerator {
|
||||||
return generators[current_preset];
|
return generators[current_preset];
|
||||||
}
|
}
|
||||||
|
|
||||||
auto StateManager::CurrentWinCondition() -> WinCondition {
|
auto StateManager::CurrentWinCondition() const -> WinCondition {
|
||||||
return win_conditions[current_preset];
|
return win_conditions[current_preset];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto StateManager::CurrentMassIndex() const -> std::size_t {
|
||||||
|
return states.at(current_state);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user