store masses/springs inside vector and manage unordered_maps for a state<->index mapping
this reduces the time required to iterate over all masses/springs because data is stored in contiguous memory
This commit is contained in:
@ -19,17 +19,15 @@
|
|||||||
|
|
||||||
class Mass {
|
class Mass {
|
||||||
public:
|
public:
|
||||||
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;
|
||||||
const bool fixed;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Mass(float _mass, Vector3 _position, bool _fixed)
|
Mass(Vector3 _position)
|
||||||
: mass(_mass), position(_position), previous_position(_position),
|
: position(_position), previous_position(_position),
|
||||||
velocity(Vector3Zero()), force(Vector3Zero()), fixed(_fixed) {}
|
velocity(Vector3Zero()), force(Vector3Zero()) {}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto ClearForce() -> void;
|
auto ClearForce() -> void;
|
||||||
@ -43,26 +41,18 @@ public:
|
|||||||
|
|
||||||
class Spring {
|
class Spring {
|
||||||
public:
|
public:
|
||||||
Mass &massA;
|
int mass_a;
|
||||||
Mass &massB;
|
int mass_b;
|
||||||
const float spring_constant;
|
|
||||||
const float dampening_constant;
|
|
||||||
const float rest_length;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Spring(Mass &_massA, Mass &_massB, float _spring_constant,
|
Spring(int _mass_a, int _mass_b) : mass_a(_mass_a), mass_b(_mass_b) {}
|
||||||
float _dampening_constant, float _rest_length)
|
|
||||||
: massA(_massA), massB(_massB), spring_constant(_spring_constant),
|
|
||||||
dampening_constant(_dampening_constant), rest_length(_rest_length) {}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
auto CalculateSpringForce() const -> void;
|
auto CalculateSpringForce(Mass &_mass_a, Mass &_mass_b) const -> void;
|
||||||
};
|
};
|
||||||
|
|
||||||
class MassSpringSystem {
|
class MassSpringSystem {
|
||||||
private:
|
private:
|
||||||
std::vector<Mass *> mass_pointers;
|
|
||||||
|
|
||||||
#ifdef BARNES_HUT
|
#ifdef BARNES_HUT
|
||||||
// Barnes-Hut
|
// Barnes-Hut
|
||||||
Octree octree;
|
Octree octree;
|
||||||
@ -81,9 +71,10 @@ 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.
|
||||||
// TODO: Everything is stored multiple times but idc (currently).
|
std::vector<Mass> masses;
|
||||||
std::unordered_map<State, Mass> masses;
|
std::unordered_map<State, int> state_masses;
|
||||||
std::unordered_map<std::pair<State, State>, Spring> springs;
|
std::vector<Spring> springs;
|
||||||
|
std::unordered_map<std::pair<State, State>, int> state_springs;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MassSpringSystem() {
|
MassSpringSystem() {
|
||||||
|
|||||||
111
src/physics.cpp
111
src/physics.cpp
@ -19,23 +19,15 @@
|
|||||||
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 {
|
||||||
if (fixed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 acceleration;
|
Vector3 acceleration;
|
||||||
Vector3 temp;
|
Vector3 temp;
|
||||||
|
|
||||||
acceleration = Vector3Scale(force, 1.0 / mass);
|
acceleration = Vector3Scale(force, 1.0 / MASS);
|
||||||
temp = Vector3Scale(acceleration, delta_time);
|
temp = Vector3Scale(acceleration, delta_time);
|
||||||
velocity = Vector3Add(velocity, temp);
|
velocity = Vector3Add(velocity, temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Mass::CalculatePosition(const float delta_time) -> void {
|
auto Mass::CalculatePosition(const float delta_time) -> void {
|
||||||
if (fixed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
previous_position = position;
|
previous_position = position;
|
||||||
|
|
||||||
Vector3 temp;
|
Vector3 temp;
|
||||||
@ -45,11 +37,7 @@ auto Mass::CalculatePosition(const float delta_time) -> void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto Mass::VerletUpdate(const float delta_time) -> void {
|
auto Mass::VerletUpdate(const float delta_time) -> void {
|
||||||
if (fixed) {
|
Vector3 acceleration = Vector3Scale(force, 1.0 / MASS);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Vector3 acceleration = Vector3Scale(force, 1.0 / mass);
|
|
||||||
Vector3 temp_position = position;
|
Vector3 temp_position = position;
|
||||||
|
|
||||||
Vector3 displacement = Vector3Subtract(position, previous_position);
|
Vector3 displacement = Vector3Subtract(position, previous_position);
|
||||||
@ -62,14 +50,14 @@ auto Mass::VerletUpdate(const float delta_time) -> void {
|
|||||||
previous_position = temp_position;
|
previous_position = temp_position;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Spring::CalculateSpringForce() const -> void {
|
auto Spring::CalculateSpringForce(Mass &_mass_a, Mass &_mass_b) const -> void {
|
||||||
Vector3 delta_position = Vector3Subtract(massA.position, massB.position);
|
Vector3 delta_position = Vector3Subtract(_mass_a.position, _mass_b.position);
|
||||||
float current_length = Vector3Length(delta_position);
|
float current_length = Vector3Length(delta_position);
|
||||||
float inv_current_length = 1.0 / current_length;
|
float inv_current_length = 1.0 / current_length;
|
||||||
Vector3 delta_velocity = Vector3Subtract(massA.velocity, massB.velocity);
|
Vector3 delta_velocity = Vector3Subtract(_mass_a.velocity, _mass_b.velocity);
|
||||||
|
|
||||||
float hooke = spring_constant * (current_length - rest_length);
|
float hooke = SPRING_CONSTANT * (current_length - REST_LENGTH);
|
||||||
float dampening = dampening_constant *
|
float dampening = DAMPENING_CONSTANT *
|
||||||
Vector3DotProduct(delta_velocity, delta_position) *
|
Vector3DotProduct(delta_velocity, delta_position) *
|
||||||
inv_current_length;
|
inv_current_length;
|
||||||
|
|
||||||
@ -77,53 +65,59 @@ auto Spring::CalculateSpringForce() const -> void {
|
|||||||
Vector3Scale(delta_position, -(hooke + dampening) * inv_current_length);
|
Vector3Scale(delta_position, -(hooke + dampening) * inv_current_length);
|
||||||
Vector3 force_b = Vector3Scale(force_a, -1.0);
|
Vector3 force_b = Vector3Scale(force_a, -1.0);
|
||||||
|
|
||||||
massA.force = Vector3Add(massA.force, force_a);
|
_mass_a.force = Vector3Add(_mass_a.force, force_a);
|
||||||
massB.force = Vector3Add(massB.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(float mass, bool fixed, const State &state)
|
||||||
-> void {
|
-> void {
|
||||||
if (!masses.contains(state)) {
|
if (!state_masses.contains(state)) {
|
||||||
masses.insert(
|
masses.emplace_back(Vector3Zero());
|
||||||
std::make_pair(state.state, Mass(mass, Vector3Zero(), fixed)));
|
std::size_t idx = masses.size() - 1;
|
||||||
|
state_masses.insert(std::make_pair(state, idx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::GetMass(const State &state) -> Mass & {
|
auto MassSpringSystem::GetMass(const State &state) -> Mass & {
|
||||||
return masses.at(state);
|
return masses.at(state_masses.at(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::GetMass(const State &state) const -> const Mass & {
|
auto MassSpringSystem::GetMass(const State &state) const -> const Mass & {
|
||||||
return masses.at(state);
|
return masses.at(state_masses.at(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto MassSpringSystem::AddSpring(const State &massA, const State &massB,
|
auto MassSpringSystem::AddSpring(const State &state_a, const State &state_b,
|
||||||
float spring_constant,
|
float spring_constant,
|
||||||
float dampening_constant, float rest_length)
|
float dampening_constant, float rest_length)
|
||||||
-> void {
|
-> void {
|
||||||
std::pair<State, State> key = std::make_pair(massA, massB);
|
std::pair<State, State> key = std::make_pair(state_a, state_b);
|
||||||
if (!springs.contains(key)) {
|
if (!state_springs.contains(key)) {
|
||||||
Mass &a = GetMass(massA);
|
int a = state_masses.at(state_a);
|
||||||
Mass &b = GetMass(massB);
|
int b = state_masses.at(state_b);
|
||||||
|
const Mass &mass_a = masses.at(a);
|
||||||
|
Mass &mass_b = masses.at(b);
|
||||||
|
|
||||||
Vector3 position = a.position;
|
Vector3 position = mass_a.position;
|
||||||
Vector3 offset = Vector3(static_cast<float>(GetRandomValue(-100, 100)),
|
Vector3 offset = Vector3(static_cast<float>(GetRandomValue(-100, 100)),
|
||||||
static_cast<float>(GetRandomValue(-100, 100)),
|
static_cast<float>(GetRandomValue(-100, 100)),
|
||||||
static_cast<float>(GetRandomValue(-100, 100)));
|
static_cast<float>(GetRandomValue(-100, 100)));
|
||||||
offset = Vector3Scale(Vector3Normalize(offset), REST_LENGTH);
|
offset = Vector3Scale(Vector3Normalize(offset), REST_LENGTH);
|
||||||
|
|
||||||
if (b.position == Vector3Zero()) {
|
if (mass_b.position == Vector3Zero()) {
|
||||||
b.position = Vector3Add(position, offset);
|
mass_b.position = Vector3Add(position, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
springs.insert(std::make_pair(
|
springs.emplace_back(a, b);
|
||||||
key, Spring(a, b, spring_constant, dampening_constant, rest_length)));
|
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();
|
||||||
#ifndef BARNES_HUT
|
#ifndef BARNES_HUT
|
||||||
InvalidateGrid();
|
InvalidateGrid();
|
||||||
#endif
|
#endif
|
||||||
@ -132,7 +126,7 @@ auto MassSpringSystem::Clear() -> void {
|
|||||||
auto MassSpringSystem::ClearForces() -> void {
|
auto MassSpringSystem::ClearForces() -> void {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
|
||||||
for (auto &[state, mass] : masses) {
|
for (auto &mass : masses) {
|
||||||
mass.ClearForce();
|
mass.ClearForce();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,23 +134,11 @@ auto MassSpringSystem::ClearForces() -> void {
|
|||||||
auto MassSpringSystem::CalculateSpringForces() -> void {
|
auto MassSpringSystem::CalculateSpringForces() -> void {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
|
||||||
for (auto &[states, spring] : springs) {
|
for (const auto spring : springs) {
|
||||||
spring.CalculateSpringForce();
|
Mass &a = masses.at(spring.mass_a);
|
||||||
|
Mass &b = masses.at(spring.mass_b);
|
||||||
|
spring.CalculateSpringForce(a, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
// spring_pointers.clear();
|
|
||||||
// spring_pointers.reserve(springs.size());
|
|
||||||
// for (auto &[states, spring] : springs) {
|
|
||||||
// spring_pointers.push_back(&spring);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// auto solve_spring = [&](int i) {
|
|
||||||
// spring_pointers[i]->CalculateSpringForce();
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// BS::multi_future<void> loop_future =
|
|
||||||
// threads.submit_loop(0, spring_pointers.size(), solve_spring, 4096);
|
|
||||||
// loop_future.wait();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef BARNES_HUT
|
#ifdef BARNES_HUT
|
||||||
@ -169,7 +151,7 @@ auto MassSpringSystem::BuildOctree() -> void {
|
|||||||
// Compute bounding box around all masses
|
// Compute bounding box around all masses
|
||||||
Vector3 min = Vector3(FLT_MAX, FLT_MAX, FLT_MAX);
|
Vector3 min = Vector3(FLT_MAX, FLT_MAX, FLT_MAX);
|
||||||
Vector3 max = Vector3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
Vector3 max = Vector3(-FLT_MAX, -FLT_MAX, -FLT_MAX);
|
||||||
for (const auto &[state, mass] : masses) {
|
for (const auto &mass : masses) {
|
||||||
min.x = std::min(min.x, mass.position.x);
|
min.x = std::min(min.x, mass.position.x);
|
||||||
max.x = std::max(max.x, mass.position.x);
|
max.x = std::max(max.x, mass.position.x);
|
||||||
min.y = std::min(min.y, mass.position.y);
|
min.y = std::min(min.y, mass.position.y);
|
||||||
@ -190,15 +172,8 @@ auto MassSpringSystem::BuildOctree() -> void {
|
|||||||
// Root node spans the entire area
|
// Root node spans the entire area
|
||||||
int root = octree.CreateNode(min, max);
|
int root = octree.CreateNode(min, max);
|
||||||
|
|
||||||
// Use a vector of pointers to the masses, because we can't parallelize the
|
for (std::size_t i = 0; i < masses.size(); ++i) {
|
||||||
// range-based for loop over the masses unordered_map using OpenMP.
|
octree.Insert(root, i, masses[i].position, MASS);
|
||||||
mass_pointers.clear();
|
|
||||||
mass_pointers.reserve(masses.size());
|
|
||||||
for (auto &[state, mass] : masses) {
|
|
||||||
mass_pointers.push_back(&mass);
|
|
||||||
}
|
|
||||||
for (std::size_t i = 0; i < mass_pointers.size(); ++i) {
|
|
||||||
octree.Insert(root, i, mass_pointers[i]->position, mass_pointers[i]->mass);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,10 +226,8 @@ auto MassSpringSystem::CalculateRepulsionForces() -> void {
|
|||||||
BuildOctree();
|
BuildOctree();
|
||||||
|
|
||||||
auto solve_octree = [&](int i) {
|
auto solve_octree = [&](int i) {
|
||||||
int root = 0;
|
Vector3 force = octree.CalculateForce(0, masses[i].position);
|
||||||
Vector3 force = octree.CalculateForce(root, mass_pointers[i]->position);
|
masses[i].force = Vector3Add(masses[i].force, force);
|
||||||
|
|
||||||
mass_pointers[i]->force = Vector3Add(mass_pointers[i]->force, force);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Calculate forces using Barnes-Hut
|
// Calculate forces using Barnes-Hut
|
||||||
@ -264,7 +237,7 @@ auto MassSpringSystem::CalculateRepulsionForces() -> void {
|
|||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
BS::multi_future<void> loop_future =
|
BS::multi_future<void> loop_future =
|
||||||
threads.submit_loop(0, mass_pointers.size(), solve_octree, 256);
|
threads.submit_loop(0, masses.size(), solve_octree, 256);
|
||||||
loop_future.wait();
|
loop_future.wait();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -352,7 +325,7 @@ auto MassSpringSystem::CalculateRepulsionForces() -> void {
|
|||||||
auto MassSpringSystem::VerletUpdate(float delta_time) -> void {
|
auto MassSpringSystem::VerletUpdate(float delta_time) -> void {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
|
||||||
for (auto &[state, mass] : masses) {
|
for (auto &mass : masses) {
|
||||||
mass.VerletUpdate(delta_time);
|
mass.VerletUpdate(delta_time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -70,17 +70,20 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs,
|
|||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
|
|
||||||
// Prepare cube instancing
|
// Prepare cube instancing
|
||||||
if (mass_springs.masses.size() < DRAW_VERTICES_LIMIT) {
|
{
|
||||||
if (transforms == nullptr) {
|
ZoneNamedN(prepare_masses, "PrepareMasses", true);
|
||||||
AllocateGraphInstancing(mass_springs);
|
if (mass_springs.masses.size() < DRAW_VERTICES_LIMIT) {
|
||||||
}
|
if (transforms == nullptr) {
|
||||||
ReallocateGraphInstancingIfNecessary(mass_springs);
|
AllocateGraphInstancing(mass_springs);
|
||||||
|
}
|
||||||
|
ReallocateGraphInstancingIfNecessary(mass_springs);
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (const auto &[state, mass] : mass_springs.masses) {
|
for (const auto &mass : mass_springs.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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,24 +93,31 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs,
|
|||||||
BeginMode3D(camera.camera);
|
BeginMode3D(camera.camera);
|
||||||
|
|
||||||
// Draw springs (batched)
|
// Draw springs (batched)
|
||||||
rlBegin(RL_LINES);
|
{
|
||||||
for (const auto &[states, spring] : mass_springs.springs) {
|
ZoneNamedN(draw_springs, "DrawSprings", true);
|
||||||
rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a);
|
rlBegin(RL_LINES);
|
||||||
rlVertex3f(spring.massA.position.x, spring.massA.position.y,
|
for (const auto &spring : mass_springs.springs) {
|
||||||
spring.massA.position.z);
|
// We have to do a lookup of the actual mass object, which is slow :(
|
||||||
rlVertex3f(spring.massB.position.x, spring.massB.position.y,
|
const Mass &a = mass_springs.masses.at(spring.mass_a);
|
||||||
spring.massB.position.z);
|
const Mass &b = mass_springs.masses.at(spring.mass_b);
|
||||||
|
rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a);
|
||||||
|
rlVertex3f(a.position.x, a.position.y, a.position.z);
|
||||||
|
rlVertex3f(b.position.x, b.position.y, b.position.z);
|
||||||
|
}
|
||||||
|
rlEnd();
|
||||||
}
|
}
|
||||||
rlEnd();
|
|
||||||
|
|
||||||
// Draw masses (instanced)
|
// Draw masses (instanced)
|
||||||
if (mass_springs.masses.size() < DRAW_VERTICES_LIMIT) {
|
{
|
||||||
// NOTE: I don't know if drawing all this inside a shader would make it much
|
ZoneNamedN(draw_masses, "DrawMasses", true);
|
||||||
// faster...
|
if (mass_springs.masses.size() < DRAW_VERTICES_LIMIT) {
|
||||||
// The amount of data sent to the GPU would be reduced (just positions
|
// NOTE: I don't know if drawing all this inside a shader would make it
|
||||||
// instead of matrices), but is this noticable for < 100000 cubes?
|
// much faster... The amount of data sent to the GPU would be
|
||||||
DrawMeshInstanced(cube_instance, vertex_mat, transforms,
|
// reduced (just positions instead of matrices), but is this
|
||||||
mass_springs.masses.size());
|
// noticable for < 100000 cubes?
|
||||||
|
DrawMeshInstanced(cube_instance, vertex_mat, transforms,
|
||||||
|
mass_springs.masses.size());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark winning states
|
// Mark winning states
|
||||||
|
|||||||
@ -84,7 +84,7 @@ 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.masses) {
|
for (const auto &[state, mass] : mass_springs.state_masses) {
|
||||||
if (win_conditions[current_preset](state)) {
|
if (win_conditions[current_preset](state)) {
|
||||||
winning_states.insert(state);
|
winning_states.insert(state);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user