Compare commits
3 Commits
efficient-
...
74065b7ea2
| Author | SHA1 | Date | |
|---|---|---|---|
|
74065b7ea2
|
|||
|
550970f8a1
|
|||
|
39c0b58f3f
|
@ -1,7 +1,7 @@
|
|||||||
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)
|
||||||
|
|||||||
@ -30,7 +30,7 @@ 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 SIM_SPEED = 0.2; // How large each update should be
|
||||||
constexpr float TIMESTEP = 1.0 / 90; // Do 90 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 MASS = 1.0; // Mass spring system
|
||||||
constexpr float SPRING_CONSTANT = 5.0; // Mass spring system
|
constexpr float SPRING_CONSTANT = 5.0; // Mass spring system
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
#ifndef __PHYSICS_HPP_
|
#ifndef __PHYSICS_HPP_
|
||||||
#define __PHYSICS_HPP_
|
#define __PHYSICS_HPP_
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
#include <queue>
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
#include <thread>
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
@ -77,7 +83,7 @@ public:
|
|||||||
std::unordered_map<std::pair<State, State>, int> state_springs;
|
std::unordered_map<std::pair<State, State>, int> state_springs;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MassSpringSystem() {
|
MassSpringSystem() : threads(std::thread::hardware_concurrency() - 1) {
|
||||||
#ifndef BARNES_HUT
|
#ifndef BARNES_HUT
|
||||||
last_build = REPULSION_GRID_REFRESH;
|
last_build = REPULSION_GRID_REFRESH;
|
||||||
std::cout << "Using uniform grid repulsion force calculation." << std::endl;
|
std::cout << "Using uniform grid repulsion force calculation." << std::endl;
|
||||||
@ -105,14 +111,13 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto AddMass(float mass, bool fixed, const State &state) -> void;
|
auto AddMass(const State &state) -> void;
|
||||||
|
|
||||||
auto GetMass(const State &state) -> Mass &;
|
auto GetMass(const State &state) -> Mass &;
|
||||||
|
|
||||||
auto GetMass(const State &state) const -> const Mass &;
|
auto GetMass(const State &state) const -> const Mass &;
|
||||||
|
|
||||||
auto AddSpring(const State &massA, const State &massB, float spring_constant,
|
auto AddSpring(const State &massA, const State &massB) -> void;
|
||||||
float dampening_constant, float rest_length) -> void;
|
|
||||||
|
|
||||||
auto Clear() -> void;
|
auto Clear() -> void;
|
||||||
|
|
||||||
@ -129,4 +134,69 @@ public:
|
|||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ThreadedPhysics {
|
||||||
|
struct AddMass {
|
||||||
|
State s;
|
||||||
|
};
|
||||||
|
struct AddSpring {
|
||||||
|
State a;
|
||||||
|
State 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, pos_mtx);
|
||||||
|
std::vector<Mass> masses; // Read by renderer
|
||||||
|
std::unordered_map<State, int> state_masses; // Read by renderer
|
||||||
|
std::vector<Spring> springs; // Read by renderer
|
||||||
|
std::unordered_map<std::pair<State, State>, int>
|
||||||
|
state_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;
|
||||||
|
physics.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static auto PhysicsThread(PhysicsState &state) -> void;
|
||||||
|
|
||||||
|
public:
|
||||||
|
auto AddMassCmd(const State &_state) -> void;
|
||||||
|
|
||||||
|
auto AddSpringCmd(const State &a, const State &b) -> void;
|
||||||
|
|
||||||
|
auto ClearCmd() -> void;
|
||||||
|
|
||||||
|
auto AddMassSpringsCmd(const std::unordered_set<State> &masses,
|
||||||
|
const std::vector<std::pair<State, State>> &springs)
|
||||||
|
-> void;
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://en.cppreference.com/w/cpp/utility/variant/visit
|
||||||
|
template <class... Ts> struct overloads : Ts... {
|
||||||
|
using Ts::operator()...;
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -61,25 +61,29 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
auto AllocateGraphInstancing(const MassSpringSystem &mass_springs) -> void;
|
auto AllocateGraphInstancing(const std::vector<Mass> &masses) -> void;
|
||||||
|
|
||||||
auto
|
auto ReallocateGraphInstancingIfNecessary(const std::vector<Mass> &masses)
|
||||||
ReallocateGraphInstancingIfNecessary(const MassSpringSystem &mass_springs)
|
|
||||||
-> void;
|
-> 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<Mass> &masses,
|
||||||
const std::unordered_set<State> &winning_states,
|
const std::unordered_map<State, int> &state_masses,
|
||||||
const std::unordered_set<State> &visited_states) -> void;
|
const std::vector<Spring> &springs,
|
||||||
|
const std::unordered_map<std::pair<State, State>, int> state_springs,
|
||||||
|
const State ¤t_state, const State &starting_state,
|
||||||
|
const std::unordered_set<State> &winning_states,
|
||||||
|
const std::unordered_set<State> &visited_states) -> void;
|
||||||
|
|
||||||
auto DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
|
auto DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
|
||||||
int sel_y, int block_add_x, int block_add_y,
|
int sel_y, int block_add_x, int block_add_y,
|
||||||
const WinCondition win_condition) -> void;
|
const WinCondition win_condition) -> void;
|
||||||
|
|
||||||
auto DrawMenu(const MassSpringSystem &mass_springs, int current_preset,
|
auto DrawMenu(const std::vector<Mass> &masses,
|
||||||
|
const std::vector<Spring> &springs, int current_preset,
|
||||||
const State ¤t_state,
|
const State ¤t_state,
|
||||||
const std::unordered_set<State> &winning_states) -> void;
|
const std::unordered_set<State> &winning_states) -> void;
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
#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"
|
||||||
@ -10,7 +9,7 @@
|
|||||||
|
|
||||||
class StateManager {
|
class StateManager {
|
||||||
public:
|
public:
|
||||||
MassSpringSystem &mass_springs;
|
ThreadedPhysics &physics;
|
||||||
|
|
||||||
int current_preset;
|
int current_preset;
|
||||||
State starting_state;
|
State starting_state;
|
||||||
@ -23,12 +22,12 @@ public:
|
|||||||
std::unordered_set<State> visited_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);
|
physics.AddMassCmd(current_state);
|
||||||
}
|
}
|
||||||
|
|
||||||
StateManager(const StateManager ©) = delete;
|
StateManager(const StateManager ©) = delete;
|
||||||
|
|||||||
60
src/main.cpp
60
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,7 @@
|
|||||||
// 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
|
||||||
|
|
||||||
auto main(int argc, char *argv[]) -> int {
|
auto main(int argc, char *argv[]) -> int {
|
||||||
// if (argc < 2) {
|
// if (argc < 2) {
|
||||||
@ -37,20 +38,27 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
// Game setup
|
// Game setup
|
||||||
OrbitCamera3D camera;
|
OrbitCamera3D camera;
|
||||||
Renderer renderer(camera);
|
Renderer renderer(camera);
|
||||||
MassSpringSystem mass_springs;
|
ThreadedPhysics physics;
|
||||||
StateManager state(mass_springs);
|
StateManager state(physics); // TODO: What is this warning?
|
||||||
InputHandler input(state, renderer);
|
InputHandler input(state, renderer);
|
||||||
|
|
||||||
|
std::vector<Mass> masses; // Read from physics
|
||||||
|
std::unordered_map<State, int> state_masses; // Read from physics
|
||||||
|
std::vector<Spring> springs; // Read from physics
|
||||||
|
std::unordered_map<std::pair<State, State>, int>
|
||||||
|
state_springs; // Read from physics
|
||||||
|
|
||||||
// Game loop
|
// Game loop
|
||||||
double timestep_accumulator = 0.0;
|
// double timestep_accumulator = 0.0;
|
||||||
while (!WindowShouldClose()) {
|
while (!WindowShouldClose()) {
|
||||||
timestep_accumulator += GetFrameTime();
|
// timestep_accumulator += GetFrameTime();
|
||||||
|
|
||||||
// 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
|
// Physics update
|
||||||
if (timestep_accumulator > TIMESTEP) {
|
if (timestep_accumulator > TIMESTEP) {
|
||||||
// Do not try to catch up if we're falling behind. Frametimes would get
|
// Do not try to catch up if we're falling behind. Frametimes would get
|
||||||
@ -63,21 +71,43 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
|
|
||||||
timestep_accumulator -= TIMESTEP;
|
timestep_accumulator -= TIMESTEP;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Read positions from physics thread
|
||||||
|
// TODO: We're copying a HUGE amount of SHIT, because the state
|
||||||
|
// representations (std::string, ~100 Bytes) are copied ~3 times per
|
||||||
|
// spring: state_masses<State, int> and state_springs<<State, State>,
|
||||||
|
// int>
|
||||||
|
// TODO: Don't download each frame if nothing changed, check for update
|
||||||
|
// first
|
||||||
|
{
|
||||||
|
std::lock_guard<LockableBase(std::mutex)> lock(physics.state.pos_mtx);
|
||||||
|
masses = physics.state.masses;
|
||||||
|
state_masses = physics.state.state_masses;
|
||||||
|
springs = physics.state.springs;
|
||||||
|
state_springs = physics.state.state_springs;
|
||||||
|
}
|
||||||
|
|
||||||
// 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);
|
if (state_masses.contains(state.current_state)) {
|
||||||
|
const Mass ¤t_mass =
|
||||||
|
masses.at(state_masses.at(state.current_state));
|
||||||
|
camera.Update(current_mass.position);
|
||||||
|
}
|
||||||
|
|
||||||
// Rendering
|
// Rendering
|
||||||
renderer.UpdateTextureSizes();
|
renderer.UpdateTextureSizes();
|
||||||
renderer.DrawMassSprings(mass_springs, state.current_state,
|
if (masses.size() > 0 && state_masses.contains(state.current_state)) {
|
||||||
state.starting_state, state.winning_states,
|
renderer.DrawMassSprings(masses, state_masses, springs, state_springs,
|
||||||
state.visited_states);
|
state.current_state, state.starting_state,
|
||||||
|
state.winning_states, state.visited_states);
|
||||||
|
}
|
||||||
|
|
||||||
renderer.DrawKlotski(state.current_state, input.hov_x, input.hov_y,
|
renderer.DrawKlotski(state.current_state, input.hov_x, input.hov_y,
|
||||||
input.sel_x, input.sel_y, input.block_add_x,
|
input.sel_x, input.sel_y, input.block_add_x,
|
||||||
input.block_add_y, state.CurrentWinCondition());
|
input.block_add_y, state.CurrentWinCondition());
|
||||||
renderer.DrawMenu(mass_springs, state.current_preset, state.current_state,
|
renderer.DrawMenu(masses, springs, state.current_preset,
|
||||||
state.winning_states);
|
state.current_state, state.winning_states);
|
||||||
renderer.DrawTextures();
|
renderer.DrawTextures();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cfloat>
|
#include <cfloat>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
#include <mutex>
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
#include <tracy/Tracy.hpp>
|
#include <tracy/Tracy.hpp>
|
||||||
@ -69,8 +70,7 @@ 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(const State &state) -> void {
|
||||||
-> void {
|
|
||||||
if (!state_masses.contains(state)) {
|
if (!state_masses.contains(state)) {
|
||||||
masses.emplace_back(Vector3Zero());
|
masses.emplace_back(Vector3Zero());
|
||||||
std::size_t idx = masses.size() - 1;
|
std::size_t idx = masses.size() - 1;
|
||||||
@ -86,9 +86,7 @@ auto MassSpringSystem::GetMass(const State &state) const -> const Mass & {
|
|||||||
return masses.at(state_masses.at(state));
|
return masses.at(state_masses.at(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::AddSpring(const State &state_a, const State &state_b,
|
auto MassSpringSystem::AddSpring(const State &state_a, const State &state_b)
|
||||||
float spring_constant,
|
|
||||||
float dampening_constant, float rest_length)
|
|
||||||
-> void {
|
-> void {
|
||||||
std::pair<State, State> key = std::make_pair(state_a, state_b);
|
std::pair<State, State> key = std::make_pair(state_a, state_b);
|
||||||
if (!state_springs.contains(key)) {
|
if (!state_springs.contains(key)) {
|
||||||
@ -340,3 +338,88 @@ auto MassSpringSystem::InvalidateGrid() -> void {
|
|||||||
last_springs_count = 0;
|
last_springs_count = 0;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
auto ThreadedPhysics::PhysicsThread(ThreadedPhysics::PhysicsState &state)
|
||||||
|
-> void {
|
||||||
|
MassSpringSystem mass_springs;
|
||||||
|
|
||||||
|
const auto visitor = overloads{
|
||||||
|
[&](const struct AddMass &am) { mass_springs.AddMass(am.s); },
|
||||||
|
[&](const struct AddSpring &as) { mass_springs.AddSpring(as.a, as.b); },
|
||||||
|
[&](const struct ClearGraph &cg) { mass_springs.Clear(); },
|
||||||
|
};
|
||||||
|
|
||||||
|
while (state.running) {
|
||||||
|
// 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
|
||||||
|
// TODO: I need this thread to run at a constant rate
|
||||||
|
mass_springs.ClearForces();
|
||||||
|
mass_springs.CalculateSpringForces();
|
||||||
|
mass_springs.CalculateRepulsionForces();
|
||||||
|
mass_springs.VerletUpdate(TIMESTEP * SIM_SPEED);
|
||||||
|
|
||||||
|
// TODO: Notify the main thread of update
|
||||||
|
// TODO: Just wait here until the main thread has fetched the data?
|
||||||
|
// Then only the copying would be sequential, while the main thread
|
||||||
|
// renders this thread can already calculate the physics...
|
||||||
|
// TODO: Would std::swap help in any way?
|
||||||
|
|
||||||
|
// Publish the positions for the renderer (copy)
|
||||||
|
{
|
||||||
|
std::lock_guard<LockableBase(std::mutex)> lock(state.pos_mtx);
|
||||||
|
state.masses = mass_springs.masses;
|
||||||
|
state.state_masses = mass_springs.state_masses;
|
||||||
|
state.springs = mass_springs.springs;
|
||||||
|
state.state_springs = mass_springs.state_springs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ThreadedPhysics::AddMassCmd(const State &_state) -> void {
|
||||||
|
{
|
||||||
|
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
|
||||||
|
state.pending_commands.push(AddMass{_state});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ThreadedPhysics::AddSpringCmd(const State &a, const State &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(
|
||||||
|
const std::unordered_set<State> &masses,
|
||||||
|
const std::vector<std::pair<State, State>> &springs) -> void {
|
||||||
|
{
|
||||||
|
std::lock_guard<LockableBase(std::mutex)> lock(state.command_mtx);
|
||||||
|
for (const auto &_state : masses) {
|
||||||
|
state.pending_commands.push(AddMass{_state});
|
||||||
|
}
|
||||||
|
for (const auto &[from, to] : springs) {
|
||||||
|
state.pending_commands.push(AddSpring{from, to});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -33,7 +33,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(const std::vector<Mass> &masses)
|
||||||
-> void {
|
-> void {
|
||||||
cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE);
|
cube_instance = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE);
|
||||||
|
|
||||||
@ -48,38 +48,40 @@ 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(masses.size() * sizeof(Matrix));
|
||||||
transforms_size = mass_springs.masses.size();
|
transforms_size = masses.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::ReallocateGraphInstancingIfNecessary(
|
auto Renderer::ReallocateGraphInstancingIfNecessary(
|
||||||
const MassSpringSystem &mass_springs) -> void {
|
const std::vector<Mass> &masses) -> void {
|
||||||
if (transforms_size != mass_springs.masses.size()) {
|
if (transforms_size != masses.size()) {
|
||||||
transforms = (Matrix *)MemRealloc(transforms, mass_springs.masses.size() *
|
transforms =
|
||||||
sizeof(Matrix));
|
(Matrix *)MemRealloc(transforms, masses.size() * sizeof(Matrix));
|
||||||
transforms_size = mass_springs.masses.size();
|
transforms_size = masses.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs,
|
auto Renderer::DrawMassSprings(
|
||||||
const State ¤t_state,
|
const std::vector<Mass> &masses,
|
||||||
const State &starting_state,
|
const std::unordered_map<State, int> &state_masses,
|
||||||
const std::unordered_set<State> &winning_states,
|
const std::vector<Spring> &springs,
|
||||||
const std::unordered_set<State> &visited_states)
|
const std::unordered_map<std::pair<State, State>, int> state_springs,
|
||||||
-> void {
|
const State ¤t_state, const State &starting_state,
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
ReallocateGraphInstancingIfNecessary(mass_springs);
|
ReallocateGraphInstancingIfNecessary(masses);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const auto &mass : mass_springs.masses) {
|
for (const auto &mass : masses) {
|
||||||
transforms[i] =
|
transforms[i] =
|
||||||
MatrixTranslate(mass.position.x, mass.position.y, mass.position.z);
|
MatrixTranslate(mass.position.x, mass.position.y, mass.position.z);
|
||||||
++i;
|
++i;
|
||||||
@ -96,10 +98,10 @@ 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 &spring : springs) {
|
||||||
// We have to do a lookup of the actual mass object, which is slow :(
|
// We have to do a lookup of the actual mass object, which is slow :(
|
||||||
const Mass &a = mass_springs.masses.at(spring.mass_a);
|
const Mass &a = masses.at(spring.mass_a);
|
||||||
const Mass &b = mass_springs.masses.at(spring.mass_b);
|
const Mass &b = masses.at(spring.mass_b);
|
||||||
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.position.x, a.position.y, a.position.z);
|
||||||
rlVertex3f(b.position.x, b.position.y, b.position.z);
|
rlVertex3f(b.position.x, b.position.y, b.position.z);
|
||||||
@ -110,20 +112,19 @@ 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 (mark_solutions || connect_solutions) {
|
||||||
for (const auto &state : winning_states) {
|
for (const auto &state : winning_states) {
|
||||||
const Mass &winning_mass = mass_springs.GetMass(state);
|
const Mass &winning_mass = masses.at(state_masses.at(state));
|
||||||
if (mark_solutions) {
|
if (mark_solutions) {
|
||||||
DrawCube(winning_mass.position, 2 * VERTEX_SIZE, 2 * VERTEX_SIZE,
|
DrawCube(winning_mass.position, 2 * VERTEX_SIZE, 2 * VERTEX_SIZE,
|
||||||
2 * VERTEX_SIZE, BLUE);
|
2 * VERTEX_SIZE, BLUE);
|
||||||
@ -131,26 +132,26 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs,
|
|||||||
|
|
||||||
if (connect_solutions) {
|
if (connect_solutions) {
|
||||||
DrawLine3D(winning_mass.position,
|
DrawLine3D(winning_mass.position,
|
||||||
mass_springs.GetMass(current_state).position, PURPLE);
|
masses.at(state_masses.at(current_state)).position, PURPLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark visited states
|
// Mark visited states
|
||||||
for (const auto &state : visited_states) {
|
for (const auto &state : visited_states) {
|
||||||
const Mass &visited_mass = mass_springs.GetMass(state);
|
const Mass &visited_mass = masses.at(state_masses.at(state));
|
||||||
|
|
||||||
DrawCube(visited_mass.position, VERTEX_SIZE * 1.5, VERTEX_SIZE * 1.5,
|
DrawCube(visited_mass.position, VERTEX_SIZE * 1.5, VERTEX_SIZE * 1.5,
|
||||||
VERTEX_SIZE * 1.5, PURPLE);
|
VERTEX_SIZE * 1.5, PURPLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark starting state
|
// Mark starting state
|
||||||
const Mass &starting_mass = mass_springs.GetMass(starting_state);
|
const Mass &starting_mass = masses.at(state_masses.at(starting_state));
|
||||||
DrawCube(starting_mass.position, VERTEX_SIZE * 2, VERTEX_SIZE * 2,
|
DrawCube(starting_mass.position, VERTEX_SIZE * 2, VERTEX_SIZE * 2,
|
||||||
VERTEX_SIZE * 2, ORANGE);
|
VERTEX_SIZE * 2, ORANGE);
|
||||||
|
|
||||||
// Mark current state
|
// Mark current state
|
||||||
const Mass ¤t_mass = mass_springs.GetMass(current_state);
|
const Mass ¤t_mass = masses.at(state_masses.at(current_state));
|
||||||
DrawCube(current_mass.position, VERTEX_SIZE * 2, VERTEX_SIZE * 2,
|
DrawCube(current_mass.position, VERTEX_SIZE * 2, VERTEX_SIZE * 2,
|
||||||
VERTEX_SIZE * 2, RED);
|
VERTEX_SIZE * 2, RED);
|
||||||
|
|
||||||
@ -250,8 +251,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(const std::vector<Mass> &masses,
|
||||||
int current_preset, const State ¤t_state,
|
const std::vector<Spring> &springs, int current_preset,
|
||||||
|
const State ¤t_state,
|
||||||
const std::unordered_set<State> &winning_states)
|
const std::unordered_set<State> &winning_states)
|
||||||
-> void {
|
-> void {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
@ -277,8 +279,7 @@ 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()),
|
||||||
winning_states.size()),
|
|
||||||
DARKGREEN);
|
DARKGREEN);
|
||||||
draw_btn(
|
draw_btn(
|
||||||
0, 1,
|
0, 1,
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
#include "state.hpp"
|
#include "state.hpp"
|
||||||
#include "config.hpp"
|
|
||||||
#include "presets.hpp"
|
#include "presets.hpp"
|
||||||
#include "tracy.hpp"
|
#include "tracy.hpp"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
|
||||||
auto StateManager::LoadPreset(int preset) -> void {
|
auto StateManager::LoadPreset(int preset) -> void {
|
||||||
@ -37,31 +37,19 @@ auto StateManager::FillGraph() -> void {
|
|||||||
|
|
||||||
std::pair<std::unordered_set<State>, std::vector<std::pair<State, State>>>
|
std::pair<std::unordered_set<State>, std::vector<std::pair<State, State>>>
|
||||||
closure = current_state.Closure();
|
closure = current_state.Closure();
|
||||||
for (const auto &state : closure.first) {
|
|
||||||
mass_springs.AddMass(MASS, false, state);
|
physics.AddMassSpringsCmd(closure.first, closure.second);
|
||||||
}
|
|
||||||
for (const auto &[from, to] : closure.second) {
|
// TODO: We have only dispatched the commands, the states won't be downloaded
|
||||||
mass_springs.AddSpring(from, to, SPRING_CONSTANT, DAMPENING_CONSTANT,
|
// when calling this... Make FindWinningStates() another command?
|
||||||
REST_LENGTH);
|
// Or recalculate whenever masses.size() changes?
|
||||||
}
|
|
||||||
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);
|
physics.AddMassCmd(current_state);
|
||||||
mass_springs.AddSpring(current_state, previous_state, SPRING_CONSTANT,
|
physics.AddSpringCmd(current_state, previous_state);
|
||||||
DAMPENING_CONSTANT, REST_LENGTH);
|
|
||||||
if (win_conditions[current_preset](current_state)) {
|
if (win_conditions[current_preset](current_state)) {
|
||||||
winning_states.insert(current_state);
|
winning_states.insert(current_state);
|
||||||
}
|
}
|
||||||
@ -72,8 +60,8 @@ auto StateManager::UpdateGraph() -> void {
|
|||||||
auto StateManager::ClearGraph() -> void {
|
auto StateManager::ClearGraph() -> void {
|
||||||
winning_states.clear();
|
winning_states.clear();
|
||||||
visited_states.clear();
|
visited_states.clear();
|
||||||
mass_springs.Clear();
|
physics.ClearCmd();
|
||||||
mass_springs.AddMass(MASS, false, current_state);
|
physics.AddMassCmd(current_state);
|
||||||
|
|
||||||
// The previous_state is no longer in the graph
|
// The previous_state is no longer in the graph
|
||||||
previous_state = current_state;
|
previous_state = current_state;
|
||||||
@ -84,14 +72,16 @@ auto StateManager::ClearGraph() -> void {
|
|||||||
|
|
||||||
auto StateManager::FindWinningStates() -> void {
|
auto StateManager::FindWinningStates() -> void {
|
||||||
winning_states.clear();
|
winning_states.clear();
|
||||||
for (const auto &[state, mass] : mass_springs.state_masses) {
|
std::unordered_map<State, int> state_masses;
|
||||||
|
{
|
||||||
|
std::lock_guard<LockableBase(std::mutex)> lock(physics.state.pos_mtx);
|
||||||
|
state_masses = physics.state.state_masses;
|
||||||
|
}
|
||||||
|
for (const auto &[state, mass] : state_masses) {
|
||||||
if (win_conditions[current_preset](state)) {
|
if (win_conditions[current_preset](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() -> StateGenerator {
|
||||||
|
|||||||
Reference in New Issue
Block a user