implement klotski graph closure solving + improve camera controls (panning)
This commit is contained in:
@ -1 +1 @@
|
|||||||
./cmake-build-release/compile_commands.json
|
./cmake-build-debug/compile_commands.json
|
||||||
@ -3,23 +3,32 @@
|
|||||||
|
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
|
|
||||||
constexpr int WIDTH = 800;
|
// Window
|
||||||
constexpr int HEIGHT = 800;
|
constexpr int WIDTH = 1000;
|
||||||
|
constexpr int HEIGHT = 1000;
|
||||||
|
|
||||||
constexpr float VERTEX_SIZE = 0.1;
|
// Camera Controls
|
||||||
|
constexpr float SIM_SPEED = 4.0;
|
||||||
|
constexpr float CAMERA_DISTANCE = 4.0;
|
||||||
|
constexpr float MIN_CAMERA_DISTANCE = 2.0;
|
||||||
|
constexpr float MAX_CAMERA_DISTANCE = 50.0;
|
||||||
|
constexpr float ZOOM_SPEED = 1.0;
|
||||||
|
constexpr float PAN_SPEED = 1.0;
|
||||||
|
constexpr float ROT_SPEED = 1.0;
|
||||||
|
|
||||||
|
// Physics Engine
|
||||||
|
constexpr float SPRING_CONSTANT = 1.5;
|
||||||
|
constexpr float DAMPENING_CONSTANT = 0.8;
|
||||||
|
constexpr float REST_LENGTH = 1.0;
|
||||||
|
constexpr float REPULSION_FORCE = 0.05;
|
||||||
|
constexpr float VERLET_DAMPENING = 0.01; // [0, 1]
|
||||||
|
|
||||||
|
// Graph Drawing
|
||||||
|
constexpr float VERTEX_SIZE = 0.05;
|
||||||
constexpr Color VERTEX_COLOR = GREEN;
|
constexpr Color VERTEX_COLOR = GREEN;
|
||||||
constexpr Color EDGE_COLOR = DARKGREEN;
|
constexpr Color EDGE_COLOR = DARKGREEN;
|
||||||
|
|
||||||
constexpr float SIM_SPEED = 4.0;
|
// Klotski Drawing
|
||||||
constexpr float ROTATION_SPEED = 1.0;
|
|
||||||
constexpr float CAMERA_DISTANCE = 2.2;
|
|
||||||
constexpr float CULLING_TOLERANCE = 0.1; // percentage
|
|
||||||
|
|
||||||
constexpr float DEFAULT_SPRING_CONSTANT = 1.5;
|
|
||||||
constexpr float DEFAULT_DAMPENING_CONSTANT = 0.1;
|
|
||||||
constexpr float DEFAULT_REST_LENGTH = 0.5;
|
|
||||||
constexpr float DEFAULT_REPULSION_FORCE = 0.01;
|
|
||||||
|
|
||||||
constexpr int BOARD_PADDING = 5;
|
constexpr int BOARD_PADDING = 5;
|
||||||
constexpr int BLOCK_PADDING = 5;
|
constexpr int BLOCK_PADDING = 5;
|
||||||
constexpr Color BLOCK_COLOR = DARKGREEN;
|
constexpr Color BLOCK_COLOR = DARKGREEN;
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include <format>
|
#include <format>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
// #define DBG_PRINT
|
// #define DBG_PRINT
|
||||||
@ -109,17 +110,17 @@ public:
|
|||||||
~Block() {}
|
~Block() {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto Hash() -> int;
|
auto Hash() const -> int;
|
||||||
|
|
||||||
static auto Invalid() -> Block const;
|
static auto Invalid() -> Block;
|
||||||
|
|
||||||
auto IsValid() -> bool;
|
auto IsValid() const -> bool;
|
||||||
|
|
||||||
auto ToString() -> std::string;
|
auto ToString() const -> std::string;
|
||||||
|
|
||||||
auto Covers(int xx, int yy) -> bool;
|
auto Covers(int xx, int yy) const -> bool;
|
||||||
|
|
||||||
auto Collides(const Block &other) -> bool;
|
auto Collides(const Block &other) const -> bool;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A state is represented by a string "WxH:blocks", where W is the board width,
|
// A state is represented by a string "WxH:blocks", where W is the board width,
|
||||||
@ -130,8 +131,8 @@ public:
|
|||||||
// block's pivot being its top-left corner.
|
// block's pivot being its top-left corner.
|
||||||
class State {
|
class State {
|
||||||
public:
|
public:
|
||||||
int width;
|
const int width;
|
||||||
int height;
|
const int height;
|
||||||
std::string state;
|
std::string state;
|
||||||
|
|
||||||
// https://en.cppreference.com/w/cpp/iterator/input_iterator.html
|
// https://en.cppreference.com/w/cpp/iterator/input_iterator.html
|
||||||
@ -214,24 +215,32 @@ public:
|
|||||||
|
|
||||||
bool operator!=(const State &other) const { return !(*this == other); }
|
bool operator!=(const State &other) const { return !(*this == other); }
|
||||||
|
|
||||||
BlockIterator begin() { return BlockIterator(*this); }
|
BlockIterator begin() const { return BlockIterator(*this); }
|
||||||
|
|
||||||
BlockIterator end() { return BlockIterator(*this, width * height); }
|
BlockIterator end() const { return BlockIterator(*this, width * height); }
|
||||||
|
|
||||||
~State() {}
|
~State() {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto Hash() -> int;
|
auto Hash() const -> int;
|
||||||
|
|
||||||
auto AddBlock(Block block) -> bool;
|
auto AddBlock(Block block) -> bool;
|
||||||
|
|
||||||
auto GetBlock(int x, int y) -> Block;
|
auto GetBlock(int x, int y) const -> Block;
|
||||||
|
|
||||||
auto RemoveBlock(int x, int y) -> bool;
|
auto RemoveBlock(int x, int y) -> bool;
|
||||||
|
|
||||||
auto MoveBlockAt(int x, int y, Direction dir) -> bool;
|
auto MoveBlockAt(int x, int y, Direction dir) -> bool;
|
||||||
|
|
||||||
auto GetNextStates() -> std::vector<State>;
|
auto GetNextStates() const -> std::vector<State>;
|
||||||
|
|
||||||
|
auto Closure() const
|
||||||
|
-> std::pair<std::unordered_set<std::string>,
|
||||||
|
std::vector<std::pair<std::string, std::string>>>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <> struct std::hash<State> {
|
||||||
|
std::size_t operator()(const State &s) const noexcept { return s.Hash(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1,21 +1,19 @@
|
|||||||
#ifndef __MASS_SPRINGS_HPP_
|
#ifndef __MASS_SPRINGS_HPP_
|
||||||
#define __MASS_SPRINGS_HPP_
|
#define __MASS_SPRINGS_HPP_
|
||||||
|
|
||||||
#include <cstddef>
|
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
class Mass {
|
class Mass {
|
||||||
public:
|
public:
|
||||||
float mass;
|
const float mass;
|
||||||
Vector3 position;
|
Vector3 position;
|
||||||
Vector3 previous_position; // for verlet integration
|
Vector3 previous_position; // for verlet integration
|
||||||
Vector3 velocity;
|
Vector3 velocity;
|
||||||
Vector3 force;
|
Vector3 force;
|
||||||
bool fixed;
|
const bool fixed;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Mass(float mass, Vector3 position, bool fixed)
|
Mass(float mass, Vector3 position, bool fixed)
|
||||||
@ -48,15 +46,13 @@ public:
|
|||||||
auto VerletUpdate(const float delta_time) -> void;
|
auto VerletUpdate(const float delta_time) -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
using MassList = std::vector<Mass>;
|
|
||||||
|
|
||||||
class Spring {
|
class Spring {
|
||||||
public:
|
public:
|
||||||
Mass &massA;
|
Mass &massA;
|
||||||
Mass &massB;
|
Mass &massB;
|
||||||
float spring_constant;
|
const float spring_constant;
|
||||||
float dampening_constant;
|
const float dampening_constant;
|
||||||
float rest_length;
|
const float rest_length;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Spring(Mass &massA, Mass &massB, float spring_constant,
|
Spring(Mass &massA, Mass &massB, float spring_constant,
|
||||||
@ -83,15 +79,13 @@ public:
|
|||||||
~Spring() {}
|
~Spring() {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto CalculateSpringForce() -> void;
|
auto CalculateSpringForce() const -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
using SpringList = std::vector<Spring>;
|
|
||||||
|
|
||||||
class MassSpringSystem {
|
class MassSpringSystem {
|
||||||
public:
|
public:
|
||||||
std::unordered_map<std::string, Mass> masses;
|
std::unordered_map<std::string, Mass> masses;
|
||||||
SpringList springs;
|
std::unordered_map<std::string, Spring> springs;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MassSpringSystem() {};
|
MassSpringSystem() {};
|
||||||
@ -104,8 +98,8 @@ public:
|
|||||||
~MassSpringSystem() {};
|
~MassSpringSystem() {};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto AddMass(float mass, Vector3 position, bool fixed, std::string state)
|
auto AddMass(float mass, Vector3 position, bool fixed,
|
||||||
-> void;
|
const std::string &state) -> void;
|
||||||
|
|
||||||
auto GetMass(const std::string &state) -> Mass &;
|
auto GetMass(const std::string &state) -> Mass &;
|
||||||
|
|
||||||
@ -121,9 +115,9 @@ public:
|
|||||||
|
|
||||||
auto CalculateRepulsionForces() -> void;
|
auto CalculateRepulsionForces() -> void;
|
||||||
|
|
||||||
auto EulerUpdate(const float delta_time) -> void;
|
auto EulerUpdate(float delta_time) -> void;
|
||||||
|
|
||||||
auto VerletUpdate(const float delta_time) -> void;
|
auto VerletUpdate(float delta_time) -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -20,11 +20,13 @@ private:
|
|||||||
float angle_y;
|
float angle_y;
|
||||||
Vector2 last_mouse;
|
Vector2 last_mouse;
|
||||||
bool dragging;
|
bool dragging;
|
||||||
|
bool panning;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
OrbitCamera3D(Vector3 target, float distance)
|
OrbitCamera3D(Vector3 target, float distance)
|
||||||
: camera({0}), target(target), distance(distance), angle_x(0.0),
|
: camera({0}), target(target), distance(distance), angle_x(0.0),
|
||||||
angle_y(0.3), last_mouse(Vector2Zero()), dragging(false) {
|
angle_y(0.3), last_mouse(Vector2Zero()), dragging(false),
|
||||||
|
panning(false) {
|
||||||
camera.position = Vector3(0, 0, -1.0 * distance);
|
camera.position = Vector3(0, 0, -1.0 * distance);
|
||||||
camera.target = target;
|
camera.target = target;
|
||||||
camera.up = Vector3(0, 1.0, 0);
|
camera.up = Vector3(0, 1.0, 0);
|
||||||
@ -40,8 +42,8 @@ public:
|
|||||||
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
private:
|
private:
|
||||||
int width;
|
const int width;
|
||||||
int height;
|
const int height;
|
||||||
OrbitCamera3D camera;
|
OrbitCamera3D camera;
|
||||||
RenderTexture render_target;
|
RenderTexture render_target;
|
||||||
RenderTexture klotski_target;
|
RenderTexture klotski_target;
|
||||||
@ -69,8 +71,8 @@ public:
|
|||||||
|
|
||||||
auto DrawMassSprings(const MassSpringSystem &masssprings) -> void;
|
auto DrawMassSprings(const MassSpringSystem &masssprings) -> void;
|
||||||
|
|
||||||
auto DrawKlotski(State &state, int hov_x, int hov_y, int sel_x, int sel_y)
|
auto DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
|
||||||
-> void;
|
int sel_y) -> void;
|
||||||
|
|
||||||
auto DrawTextures() -> void;
|
auto DrawTextures() -> void;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
#include "klotski.hpp"
|
#include "klotski.hpp"
|
||||||
|
|
||||||
auto Block::Hash() -> 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Block::Invalid() -> Block const {
|
auto Block::Invalid() -> Block {
|
||||||
Block block = Block(0, 0, 1, 1, false);
|
Block block = Block(0, 0, 1, 1, false);
|
||||||
block.width = 0;
|
block.width = 0;
|
||||||
block.height = 0;
|
block.height = 0;
|
||||||
return block;
|
return block;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Block::IsValid() -> bool { return width != 0 && height != 0; }
|
auto Block::IsValid() const -> bool { return width != 0 && height != 0; }
|
||||||
|
|
||||||
auto Block::ToString() -> std::string {
|
auto Block::ToString() const -> std::string {
|
||||||
if (target) {
|
if (target) {
|
||||||
return std::format("{}{}",
|
return std::format("{}{}",
|
||||||
static_cast<char>(width + static_cast<int>('a') - 1),
|
static_cast<char>(width + static_cast<int>('a') - 1),
|
||||||
@ -24,16 +24,16 @@ auto Block::ToString() -> std::string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Block::Covers(int xx, int yy) -> bool {
|
auto Block::Covers(int xx, int yy) const -> bool {
|
||||||
return xx >= x && xx < x + width && yy >= y && yy < y + height;
|
return xx >= x && xx < x + width && yy >= y && yy < y + height;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Block::Collides(const Block &other) -> bool {
|
auto Block::Collides(const Block &other) const -> bool {
|
||||||
return x < other.x + other.width && x + width > other.x &&
|
return x < other.x + other.width && x + width > other.x &&
|
||||||
y < other.y + other.height && y + height > other.y;
|
y < other.y + other.height && y + height > other.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto State::Hash() -> int { return std::hash<std::string>{}(state); }
|
auto State::Hash() const -> int { return std::hash<std::string>{}(state); }
|
||||||
|
|
||||||
auto State::AddBlock(Block block) -> bool {
|
auto State::AddBlock(Block block) -> bool {
|
||||||
if (block.x + block.width > width || block.y + block.height > height) {
|
if (block.x + block.width > width || block.y + block.height > height) {
|
||||||
@ -52,7 +52,7 @@ auto State::AddBlock(Block block) -> bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto State::GetBlock(int x, int y) -> Block {
|
auto State::GetBlock(int x, int y) const -> Block {
|
||||||
if (x >= width || y >= height) {
|
if (x >= width || y >= height) {
|
||||||
return Block::Invalid();
|
return Block::Invalid();
|
||||||
}
|
}
|
||||||
@ -128,3 +128,60 @@ auto State::MoveBlockAt(int x, int y, Direction dir) -> bool {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto State::GetNextStates() const -> std::vector<State> {
|
||||||
|
std::vector<State> new_states;
|
||||||
|
|
||||||
|
for (const Block &b : *this) {
|
||||||
|
State north = *this;
|
||||||
|
if (north.MoveBlockAt(b.x, b.y, Direction::NOR)) {
|
||||||
|
new_states.push_back(north);
|
||||||
|
}
|
||||||
|
|
||||||
|
State east = *this;
|
||||||
|
if (east.MoveBlockAt(b.x, b.y, Direction::EAS)) {
|
||||||
|
new_states.push_back(east);
|
||||||
|
}
|
||||||
|
|
||||||
|
State south = *this;
|
||||||
|
if (south.MoveBlockAt(b.x, b.y, Direction::SOU)) {
|
||||||
|
new_states.push_back(south);
|
||||||
|
}
|
||||||
|
|
||||||
|
State west = *this;
|
||||||
|
if (west.MoveBlockAt(b.x, b.y, Direction::WES)) {
|
||||||
|
new_states.push_back(west);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_states;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto State::Closure() const
|
||||||
|
-> std::pair<std::unordered_set<std::string>,
|
||||||
|
std::vector<std::pair<std::string, std::string>>> {
|
||||||
|
std::unordered_set<std::string> states;
|
||||||
|
std::vector<std::pair<std::string, std::string>> links;
|
||||||
|
|
||||||
|
std::unordered_set<State> remaining_states;
|
||||||
|
remaining_states.insert(*this);
|
||||||
|
|
||||||
|
do {
|
||||||
|
const State current = *remaining_states.begin();
|
||||||
|
remaining_states.erase(current);
|
||||||
|
|
||||||
|
std::vector<State> new_states = current.GetNextStates();
|
||||||
|
for (State &s : new_states) {
|
||||||
|
if (!states.contains(s.state)) {
|
||||||
|
remaining_states.insert(s);
|
||||||
|
states.insert(s.state);
|
||||||
|
}
|
||||||
|
links.emplace_back(current.state, s.state);
|
||||||
|
}
|
||||||
|
} while (remaining_states.size() > 0);
|
||||||
|
|
||||||
|
std::cout << "Closure contains " << states.size() << " states with "
|
||||||
|
<< links.size() << " links." << std::endl;
|
||||||
|
|
||||||
|
return std::make_pair(states, links);
|
||||||
|
}
|
||||||
|
|||||||
59
src/main.cpp
59
src/main.cpp
@ -15,23 +15,23 @@ auto klotski_a() -> State {
|
|||||||
Block b = Block(1, 0, 2, 2, true);
|
Block b = Block(1, 0, 2, 2, true);
|
||||||
Block c = Block(3, 0, 1, 2, false);
|
Block c = Block(3, 0, 1, 2, false);
|
||||||
Block d = Block(0, 2, 1, 2, false);
|
Block d = Block(0, 2, 1, 2, false);
|
||||||
Block e = Block(1, 2, 2, 1, false);
|
// Block e = Block(1, 2, 2, 1, false);
|
||||||
Block f = Block(3, 2, 1, 2, false);
|
// Block f = Block(3, 2, 1, 2, false);
|
||||||
Block g = Block(1, 3, 1, 1, false);
|
// Block g = Block(1, 3, 1, 1, false);
|
||||||
Block h = Block(2, 3, 1, 1, false);
|
// Block h = Block(2, 3, 1, 1, false);
|
||||||
Block i = Block(0, 4, 1, 1, false);
|
// Block i = Block(0, 4, 1, 1, false);
|
||||||
Block j = Block(3, 4, 1, 1, false);
|
// Block j = Block(3, 4, 1, 1, false);
|
||||||
|
|
||||||
s.AddBlock(a);
|
s.AddBlock(a);
|
||||||
s.AddBlock(b);
|
s.AddBlock(b);
|
||||||
s.AddBlock(c);
|
s.AddBlock(c);
|
||||||
s.AddBlock(d);
|
s.AddBlock(d);
|
||||||
s.AddBlock(e);
|
// s.AddBlock(e);
|
||||||
s.AddBlock(f);
|
// s.AddBlock(f);
|
||||||
s.AddBlock(g);
|
// s.AddBlock(g);
|
||||||
s.AddBlock(h);
|
// s.AddBlock(h);
|
||||||
s.AddBlock(i);
|
// s.AddBlock(i);
|
||||||
s.AddBlock(j);
|
// s.AddBlock(j);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
@ -58,6 +58,33 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
State board = klotski_a();
|
State board = klotski_a();
|
||||||
mass_springs.AddMass(1.0, Vector3Zero(), true, board.state);
|
mass_springs.AddMass(1.0, Vector3Zero(), true, board.state);
|
||||||
|
|
||||||
|
// Closure solving
|
||||||
|
std::pair<std::unordered_set<std::string>,
|
||||||
|
std::vector<std::pair<std::string, std::string>>>
|
||||||
|
closure = board.Closure();
|
||||||
|
for (const auto &state : closure.first) {
|
||||||
|
Vector3 pos =
|
||||||
|
Vector3(static_cast<float>(GetRandomValue(-10000, 10000)) / 1000.0,
|
||||||
|
static_cast<float>(GetRandomValue(-10000, 10000)) / 1000.0,
|
||||||
|
static_cast<float>(GetRandomValue(-10000, 10000)) / 1000.0);
|
||||||
|
|
||||||
|
mass_springs.AddMass(1.0, pos, false, state);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
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;
|
||||||
|
|
||||||
// Rendering configuration
|
// Rendering configuration
|
||||||
Renderer renderer(WIDTH, HEIGHT);
|
Renderer renderer(WIDTH, HEIGHT);
|
||||||
|
|
||||||
@ -115,8 +142,9 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
if (board.MoveBlockAt(sel_x, sel_y, Direction::EAS)) {
|
if (board.MoveBlockAt(sel_x, sel_y, Direction::EAS)) {
|
||||||
sel_x++;
|
sel_x++;
|
||||||
}
|
}
|
||||||
|
} else if (IsKeyPressed(KEY_P)) {
|
||||||
|
std::cout << board.state << std::endl;
|
||||||
}
|
}
|
||||||
// TODO: Need to check for duplicate springs
|
|
||||||
if (previous_state != board.state) {
|
if (previous_state != board.state) {
|
||||||
mass_springs.AddMass(
|
mass_springs.AddMass(
|
||||||
1.0,
|
1.0,
|
||||||
@ -124,9 +152,8 @@ auto main(int argc, char *argv[]) -> int {
|
|||||||
static_cast<float>(GetRandomValue(-1000, 1000)) / 1000.0,
|
static_cast<float>(GetRandomValue(-1000, 1000)) / 1000.0,
|
||||||
static_cast<float>(GetRandomValue(-1000, 1000)) / 1000.0),
|
static_cast<float>(GetRandomValue(-1000, 1000)) / 1000.0),
|
||||||
false, board.state);
|
false, board.state);
|
||||||
mass_springs.AddSpring(board.state, previous_state,
|
mass_springs.AddSpring(board.state, previous_state, SPRING_CONSTANT,
|
||||||
DEFAULT_SPRING_CONSTANT,
|
DAMPENING_CONSTANT, REST_LENGTH);
|
||||||
DEFAULT_DAMPENING_CONSTANT, DEFAULT_REST_LENGTH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Physics update
|
// Physics update
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#include "mass_springs.hpp"
|
#include "mass_springs.hpp"
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <format>
|
||||||
#include <raymath.h>
|
#include <raymath.h>
|
||||||
|
|
||||||
auto Mass::ClearForce() -> void { force = Vector3Zero(); }
|
auto Mass::ClearForce() -> void { force = Vector3Zero(); }
|
||||||
@ -44,13 +44,13 @@ auto Mass::VerletUpdate(const float delta_time) -> void {
|
|||||||
Vector3 accel_term = Vector3Scale(acceleration, delta_time * delta_time);
|
Vector3 accel_term = Vector3Scale(acceleration, delta_time * delta_time);
|
||||||
|
|
||||||
// Minimal dampening
|
// Minimal dampening
|
||||||
displacement = Vector3Scale(displacement, 0.99);
|
displacement = Vector3Scale(displacement, 1.0 - VERLET_DAMPENING);
|
||||||
|
|
||||||
position = Vector3Add(Vector3Add(position, displacement), accel_term);
|
position = Vector3Add(Vector3Add(position, displacement), accel_term);
|
||||||
previous_position = temp_position;
|
previous_position = temp_position;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Spring::CalculateSpringForce() -> void {
|
auto Spring::CalculateSpringForce() const -> void {
|
||||||
Vector3 delta_position;
|
Vector3 delta_position;
|
||||||
float current_length;
|
float current_length;
|
||||||
Vector3 delta_velocity;
|
Vector3 delta_velocity;
|
||||||
@ -74,7 +74,7 @@ auto Spring::CalculateSpringForce() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::AddMass(float mass, Vector3 position, bool fixed,
|
auto MassSpringSystem::AddMass(float mass, Vector3 position, bool fixed,
|
||||||
std::string state) -> void {
|
const std::string &state) -> void {
|
||||||
if (!masses.contains(state)) {
|
if (!masses.contains(state)) {
|
||||||
masses.insert(std::make_pair(state, Mass(mass, position, fixed)));
|
masses.insert(std::make_pair(state, Mass(mass, position, fixed)));
|
||||||
}
|
}
|
||||||
@ -89,8 +89,18 @@ auto MassSpringSystem::AddSpring(const std::string &massA,
|
|||||||
float spring_constant,
|
float spring_constant,
|
||||||
float dampening_constant, float rest_length)
|
float dampening_constant, float rest_length)
|
||||||
-> void {
|
-> void {
|
||||||
springs.emplace_back(GetMass(massA), GetMass(massB), spring_constant,
|
std::string states;
|
||||||
dampening_constant, rest_length);
|
if (std::hash<std::string>{}(massA) < std::hash<std::string>{}(massB)) {
|
||||||
|
states = std::format("{}{}", massA, massB);
|
||||||
|
} else {
|
||||||
|
states = std::format("{}{}", massB, massA);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!springs.contains(states)) {
|
||||||
|
springs.insert(std::make_pair(
|
||||||
|
states, Spring(GetMass(massA), GetMass(massB), spring_constant,
|
||||||
|
dampening_constant, rest_length)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::Clear() -> void {
|
auto MassSpringSystem::Clear() -> void {
|
||||||
@ -105,7 +115,7 @@ auto MassSpringSystem::ClearForces() -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::CalculateSpringForces() -> void {
|
auto MassSpringSystem::CalculateSpringForces() -> void {
|
||||||
for (auto &spring : springs) {
|
for (auto &[states, spring] : springs) {
|
||||||
spring.CalculateSpringForce();
|
spring.CalculateSpringForce();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,25 +126,24 @@ auto MassSpringSystem::CalculateRepulsionForces() -> void {
|
|||||||
Vector3 dx = Vector3Subtract(mass.position, m.position);
|
Vector3 dx = Vector3Subtract(mass.position, m.position);
|
||||||
|
|
||||||
// This can be accelerated with a spatial data structure
|
// This can be accelerated with a spatial data structure
|
||||||
if (Vector3Length(dx) >= 3 * DEFAULT_REST_LENGTH) {
|
if (Vector3Length(dx) >= 3 * REST_LENGTH) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
mass.force =
|
mass.force = Vector3Add(
|
||||||
Vector3Add(mass.force, Vector3Scale(Vector3Normalize(dx),
|
mass.force, Vector3Scale(Vector3Normalize(dx), REPULSION_FORCE));
|
||||||
DEFAULT_REPULSION_FORCE));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::EulerUpdate(const float delta_time) -> void {
|
auto MassSpringSystem::EulerUpdate(float delta_time) -> void {
|
||||||
for (auto &[state, mass] : masses) {
|
for (auto &[state, mass] : masses) {
|
||||||
mass.CalculateVelocity(delta_time);
|
mass.CalculateVelocity(delta_time);
|
||||||
mass.CalculatePosition(delta_time);
|
mass.CalculatePosition(delta_time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::VerletUpdate(const float delta_time) -> void {
|
auto MassSpringSystem::VerletUpdate(float delta_time) -> void {
|
||||||
for (auto &[state, mass] : masses) {
|
for (auto &[state, mass] : masses) {
|
||||||
mass.VerletUpdate(delta_time);
|
mass.VerletUpdate(delta_time);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#include "renderer.hpp"
|
#include "renderer.hpp"
|
||||||
|
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
|
#include <raymath.h>
|
||||||
|
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "mass_springs.hpp"
|
#include "mass_springs.hpp"
|
||||||
@ -8,28 +9,50 @@
|
|||||||
auto OrbitCamera3D::Update() -> void {
|
auto OrbitCamera3D::Update() -> void {
|
||||||
Vector2 mouse = GetMousePosition();
|
Vector2 mouse = GetMousePosition();
|
||||||
|
|
||||||
if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
if (IsMouseButtonPressed(MOUSE_RIGHT_BUTTON)) {
|
||||||
dragging = true;
|
dragging = true;
|
||||||
last_mouse = mouse;
|
last_mouse = mouse;
|
||||||
|
} else if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
|
||||||
|
panning = true;
|
||||||
|
last_mouse = mouse;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
if (IsMouseButtonReleased(MOUSE_RIGHT_BUTTON)) {
|
||||||
dragging = false;
|
dragging = false;
|
||||||
}
|
}
|
||||||
|
if (IsMouseButtonReleased(MOUSE_LEFT_BUTTON)) {
|
||||||
|
panning = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (dragging) {
|
if (dragging) {
|
||||||
Vector2 dx = Vector2Subtract(mouse, last_mouse);
|
Vector2 dx = Vector2Subtract(mouse, last_mouse);
|
||||||
last_mouse = mouse;
|
last_mouse = mouse;
|
||||||
|
|
||||||
angle_x -= dx.x * 0.005;
|
angle_x -= dx.x * ROT_SPEED / 200.0;
|
||||||
angle_y += dx.y * 0.005;
|
angle_y += dx.y * ROT_SPEED / 200.0;
|
||||||
|
|
||||||
angle_y = Clamp(angle_y, -1.5, 1.5); // Prevent flipping
|
angle_y = Clamp(angle_y, -1.5, 1.5); // Prevent flipping
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (panning) {
|
||||||
|
Vector2 dx = Vector2Subtract(mouse, last_mouse);
|
||||||
|
last_mouse = mouse;
|
||||||
|
|
||||||
|
float speed = distance * PAN_SPEED / 1000.0;
|
||||||
|
Vector3 forward =
|
||||||
|
Vector3Normalize(Vector3Subtract(camera.target, camera.position));
|
||||||
|
Vector3 right = Vector3Normalize(Vector3CrossProduct(forward, camera.up));
|
||||||
|
Vector3 up = Vector3Normalize(Vector3CrossProduct(right, forward));
|
||||||
|
|
||||||
|
Vector3 offset = Vector3Add(Vector3Scale(right, -dx.x * speed),
|
||||||
|
Vector3Scale(up, dx.y * speed));
|
||||||
|
|
||||||
|
target = Vector3Add(target, offset);
|
||||||
|
}
|
||||||
|
|
||||||
float wheel = GetMouseWheelMove();
|
float wheel = GetMouseWheelMove();
|
||||||
distance -= wheel * 2.0;
|
distance -= wheel * ZOOM_SPEED;
|
||||||
distance = Clamp(distance, 2.0, 50.0);
|
distance = Clamp(distance, MIN_CAMERA_DISTANCE, MAX_CAMERA_DISTANCE);
|
||||||
|
|
||||||
float x = cos(angle_y) * sin(angle_x) * distance;
|
float x = cos(angle_y) * sin(angle_x) * distance;
|
||||||
float y = sin(angle_y) * distance;
|
float y = sin(angle_y) * distance;
|
||||||
@ -48,7 +71,7 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings) -> void {
|
|||||||
BeginMode3D(camera.camera);
|
BeginMode3D(camera.camera);
|
||||||
|
|
||||||
// Draw springs
|
// Draw springs
|
||||||
for (const auto &spring : masssprings.springs) {
|
for (const auto &[states, spring] : masssprings.springs) {
|
||||||
const Mass a = spring.massA;
|
const Mass a = spring.massA;
|
||||||
const Mass b = spring.massB;
|
const Mass b = spring.massB;
|
||||||
|
|
||||||
@ -61,7 +84,7 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings) -> void {
|
|||||||
VERTEX_COLOR);
|
VERTEX_COLOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawGrid(10, 1.0);
|
// DrawGrid(10, 1.0);
|
||||||
|
|
||||||
EndMode3D();
|
EndMode3D();
|
||||||
|
|
||||||
@ -70,7 +93,7 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings) -> void {
|
|||||||
EndTextureMode();
|
EndTextureMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::DrawKlotski(State &state, int hov_x, int hov_y, int sel_x,
|
auto Renderer::DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x,
|
||||||
int sel_y) -> void {
|
int sel_y) -> void {
|
||||||
BeginTextureMode(klotski_target);
|
BeginTextureMode(klotski_target);
|
||||||
ClearBackground(RAYWHITE);
|
ClearBackground(RAYWHITE);
|
||||||
|
|||||||
Reference in New Issue
Block a user