implement graph node mouse collisions (unoptimized iterating over all masses for now)
This commit is contained in:
900
src/gpu_example.cpp
Normal file
900
src/gpu_example.cpp
Normal file
@ -0,0 +1,900 @@
|
||||
// gpu_force_layout_raylib_rlgl.cpp
|
||||
//
|
||||
// Single-file GPU force-directed layout example for raylib + rlgl + OpenGL 4.3 compute shaders.
|
||||
// Includes:
|
||||
// - grid-based repulsion (GPU)
|
||||
// - Hooke springs from edge pairs (GPU)
|
||||
// - integration (GPU)
|
||||
// - point rendering directly from GPU buffer
|
||||
//
|
||||
// Requires:
|
||||
// - raylib built with GRAPHICS_API_OPENGL_43
|
||||
// - desktop OpenGL 4.3+
|
||||
// - GL_ARB_shader_atomic_float (present on RTX 4070)
|
||||
//
|
||||
// Build example:
|
||||
// g++ -std=c++2b -O2 gpu_force_layout_raylib_rlgl.cpp -lraylib -lGL -ldl -lpthread -lm -o
|
||||
// gpu_layout
|
||||
//
|
||||
// Notes:
|
||||
// - This is a practical starter architecture, not a finished engine.
|
||||
// - For very large graphs, add multilevel coarsening and a coarse second grid.
|
||||
//
|
||||
|
||||
#include "raylib.h"
|
||||
#include "raymath.h"
|
||||
#include "rlgl.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// rlgl exposes OpenGL headers on desktop builds
|
||||
#ifndef GRAPHICS_API_OPENGL_43
|
||||
// You can still compile this file, but compute shaders will not work unless raylib/rlgl
|
||||
// was built with OpenGL 4.3 support.
|
||||
#endif
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static void Require(bool cond, const char* msg)
|
||||
{
|
||||
if (!cond) throw std::runtime_error(msg);
|
||||
}
|
||||
|
||||
static bool HasExtension(const char* extName)
|
||||
{
|
||||
GLint count = 0;
|
||||
glGetIntegerv(GL_NUM_EXTENSIONS, &count);
|
||||
for (GLint i = 0; i < count; ++i) {
|
||||
const char* ext = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, (GLuint)i));
|
||||
if (ext && std::strcmp(ext, extName) == 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static unsigned int BuildComputeProgramRlgl(const char* source)
|
||||
{
|
||||
unsigned int shaderId = rlCompileShader(source, RL_COMPUTE_SHADER);
|
||||
Require(shaderId != 0, "Failed to compile compute shader");
|
||||
unsigned int programId = rlLoadComputeShaderProgram(shaderId);
|
||||
Require(programId != 0, "Failed to link compute shader program");
|
||||
rlUnloadShader(shaderId);
|
||||
return programId;
|
||||
}
|
||||
|
||||
static unsigned int BuildGraphicsProgramRlgl(const char* vs, const char* fs)
|
||||
{
|
||||
unsigned int vsId = rlCompileShader(vs, RL_VERTEX_SHADER);
|
||||
Require(vsId != 0, "Failed to compile vertex shader");
|
||||
|
||||
unsigned int fsId = rlCompileShader(fs, RL_FRAGMENT_SHADER);
|
||||
Require(fsId != 0, "Failed to compile fragment shader");
|
||||
|
||||
unsigned int progId = rlLoadShaderProgram(vsId, fsId);
|
||||
Require(progId != 0, "Failed to link graphics shader program");
|
||||
|
||||
rlUnloadShader(vsId);
|
||||
rlUnloadShader(fsId);
|
||||
return progId;
|
||||
}
|
||||
|
||||
static void Dispatch1D(unsigned int count, unsigned int localSizeX = 256)
|
||||
{
|
||||
unsigned int groupsX = (count + localSizeX - 1) / localSizeX;
|
||||
rlComputeShaderDispatch(groupsX, 1, 1);
|
||||
}
|
||||
|
||||
static Matrix MatrixFromCamera(const Camera3D& cam)
|
||||
{
|
||||
return GetCameraMatrix(cam);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// GPU data layouts (std430-friendly)
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
struct ParticleGPU
|
||||
{
|
||||
float pos_mass[4]; // xyz position, w mass
|
||||
float vel_pad[4]; // xyz velocity
|
||||
float force_pad[4]; // xyz accumulated force
|
||||
};
|
||||
static_assert(sizeof(ParticleGPU) == 48);
|
||||
|
||||
struct CellAccumGPU
|
||||
{
|
||||
float sum_mass[4]; // xyz = sum(pos * mass), w = total mass
|
||||
std::uint32_t count;
|
||||
std::uint32_t pad[3];
|
||||
};
|
||||
static_assert(sizeof(CellAccumGPU) == 32);
|
||||
|
||||
struct CellFinalGPU
|
||||
{
|
||||
float com_mass[4]; // xyz = center of mass, w = total mass
|
||||
std::uint32_t count;
|
||||
std::uint32_t pad[3];
|
||||
};
|
||||
static_assert(sizeof(CellFinalGPU) == 32);
|
||||
|
||||
struct EdgeGPU
|
||||
{
|
||||
std::uint32_t u;
|
||||
std::uint32_t v;
|
||||
float weight;
|
||||
float restLength;
|
||||
};
|
||||
static_assert(sizeof(EdgeGPU) == 16);
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Compute shaders
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static const char* CS_CLEAR_GRID = R"GLSL(
|
||||
#version 430
|
||||
layout(local_size_x = 256) in;
|
||||
|
||||
struct CellAccum {
|
||||
vec4 sum_mass;
|
||||
uvec4 count_pad;
|
||||
};
|
||||
|
||||
layout(std430, binding=0) buffer GridAccumBuffer {
|
||||
CellAccum acc[];
|
||||
};
|
||||
|
||||
uniform uint cellCount;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
if (id >= cellCount) return;
|
||||
|
||||
acc[id].sum_mass = vec4(0.0);
|
||||
acc[id].count_pad = uvec4(0u);
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
static const char* CS_ACCUM_GRID = R"GLSL(
|
||||
#version 430
|
||||
#extension GL_ARB_shader_atomic_float : enable
|
||||
layout(local_size_x = 256) in;
|
||||
|
||||
struct Particle {
|
||||
vec4 pos_mass;
|
||||
vec4 vel_pad;
|
||||
vec4 force_pad;
|
||||
};
|
||||
|
||||
struct CellAccum {
|
||||
vec4 sum_mass;
|
||||
uvec4 count_pad;
|
||||
};
|
||||
|
||||
layout(std430, binding=0) buffer ParticleBuffer {
|
||||
Particle particles[];
|
||||
};
|
||||
|
||||
layout(std430, binding=1) buffer GridAccumBuffer {
|
||||
CellAccum acc[];
|
||||
};
|
||||
|
||||
uniform uint particleCount;
|
||||
uniform vec3 gridMin;
|
||||
uniform float cellSize;
|
||||
uniform ivec3 gridDim;
|
||||
|
||||
int cellIndex(ivec3 c, ivec3 dim)
|
||||
{
|
||||
return c.x + c.y*dim.x + c.z*dim.x*dim.y;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
if (id >= particleCount) return;
|
||||
|
||||
vec3 pos = particles[id].pos_mass.xyz;
|
||||
float m = particles[id].pos_mass.w;
|
||||
|
||||
ivec3 c = ivec3(floor((pos - gridMin) / cellSize));
|
||||
c = clamp(c, ivec3(0), gridDim - ivec3(1));
|
||||
|
||||
int idx = cellIndex(c, gridDim);
|
||||
|
||||
atomicAdd(acc[idx].sum_mass.x, pos.x * m);
|
||||
atomicAdd(acc[idx].sum_mass.y, pos.y * m);
|
||||
atomicAdd(acc[idx].sum_mass.z, pos.z * m);
|
||||
atomicAdd(acc[idx].sum_mass.w, m);
|
||||
atomicAdd(acc[idx].count_pad.x, 1u);
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
static const char* CS_FINALIZE_GRID = R"GLSL(
|
||||
#version 430
|
||||
layout(local_size_x = 256) in;
|
||||
|
||||
struct CellAccum {
|
||||
vec4 sum_mass;
|
||||
uvec4 count_pad;
|
||||
};
|
||||
|
||||
struct CellFinal {
|
||||
vec4 com_mass;
|
||||
uvec4 count_pad;
|
||||
};
|
||||
|
||||
layout(std430, binding=0) buffer GridAccumBuffer {
|
||||
CellAccum acc[];
|
||||
};
|
||||
|
||||
layout(std430, binding=1) buffer GridFinalBuffer {
|
||||
CellFinal cell[];
|
||||
};
|
||||
|
||||
uniform uint cellCount;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
if (id >= cellCount) return;
|
||||
|
||||
float mass = acc[id].sum_mass.w;
|
||||
uint count = acc[id].count_pad.x;
|
||||
|
||||
vec3 com = vec3(0.0);
|
||||
if (mass > 0.0) {
|
||||
com = acc[id].sum_mass.xyz / mass;
|
||||
}
|
||||
|
||||
cell[id].com_mass = vec4(com, mass);
|
||||
cell[id].count_pad = uvec4(count, 0u, 0u, 0u);
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
static const char* CS_REPULSION = R"GLSL(
|
||||
#version 430
|
||||
layout(local_size_x = 256) in;
|
||||
|
||||
struct Particle {
|
||||
vec4 pos_mass;
|
||||
vec4 vel_pad;
|
||||
vec4 force_pad;
|
||||
};
|
||||
|
||||
struct CellFinal {
|
||||
vec4 com_mass;
|
||||
uvec4 count_pad;
|
||||
};
|
||||
|
||||
layout(std430, binding=0) buffer ParticleBuffer {
|
||||
Particle particles[];
|
||||
};
|
||||
|
||||
layout(std430, binding=1) buffer GridFinalBuffer {
|
||||
CellFinal cell[];
|
||||
};
|
||||
|
||||
uniform uint particleCount;
|
||||
uniform vec3 gridMin;
|
||||
uniform float cellSize;
|
||||
uniform ivec3 gridDim;
|
||||
|
||||
uniform int radiusCells;
|
||||
uniform float softening;
|
||||
uniform float repulsionK2;
|
||||
uniform float maxRepulsionForce;
|
||||
uniform float selfCellFactor;
|
||||
|
||||
int cellIndex(ivec3 c, ivec3 dim)
|
||||
{
|
||||
return c.x + c.y*dim.x + c.z*dim.x*dim.y;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
if (id >= particleCount) return;
|
||||
|
||||
vec3 pos = particles[id].pos_mass.xyz;
|
||||
ivec3 base = ivec3(floor((pos - gridMin) / cellSize));
|
||||
base = clamp(base, ivec3(0), gridDim - ivec3(1));
|
||||
|
||||
vec3 force = vec3(0.0);
|
||||
|
||||
for (int dz = -radiusCells; dz <= radiusCells; ++dz)
|
||||
for (int dy = -radiusCells; dy <= radiusCells; ++dy)
|
||||
for (int dx = -radiusCells; dx <= radiusCells; ++dx)
|
||||
{
|
||||
ivec3 c = base + ivec3(dx, dy, dz);
|
||||
if (any(lessThan(c, ivec3(0))) || any(greaterThanEqual(c, gridDim))) continue;
|
||||
|
||||
int idx = cellIndex(c, gridDim);
|
||||
float mass = cell[idx].com_mass.w;
|
||||
if (mass <= 0.0) continue;
|
||||
|
||||
vec3 com = cell[idx].com_mass.xyz;
|
||||
vec3 d = pos - com;
|
||||
|
||||
float r2 = dot(d, d) + softening;
|
||||
float factor = ((dx == 0) && (dy == 0) && (dz == 0)) ? selfCellFactor : 1.0;
|
||||
|
||||
// k^2 / r magnitude with direction d/r = k^2 * d / r^2
|
||||
float invR = inversesqrt(r2);
|
||||
force += factor * (repulsionK2 * mass) * d * (invR * invR);
|
||||
}
|
||||
|
||||
float len = length(force);
|
||||
if (len > maxRepulsionForce) {
|
||||
force *= (maxRepulsionForce / len);
|
||||
}
|
||||
|
||||
particles[id].force_pad.xyz = force;
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
static const char* CS_SPRINGS = R"GLSL(
|
||||
#version 430
|
||||
#extension GL_ARB_shader_atomic_float : enable
|
||||
layout(local_size_x = 256) in;
|
||||
|
||||
struct Particle {
|
||||
vec4 pos_mass;
|
||||
vec4 vel_pad;
|
||||
vec4 force_pad;
|
||||
};
|
||||
|
||||
struct Edge {
|
||||
uint u;
|
||||
uint v;
|
||||
float weight;
|
||||
float restLength;
|
||||
};
|
||||
|
||||
layout(std430, binding=0) buffer ParticleBuffer {
|
||||
Particle particles[];
|
||||
};
|
||||
|
||||
layout(std430, binding=1) buffer EdgeBuffer {
|
||||
Edge edges[];
|
||||
};
|
||||
|
||||
uniform uint edgeCount;
|
||||
uniform float springK;
|
||||
uniform float maxSpringForce;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
if (id >= edgeCount) return;
|
||||
|
||||
Edge e = edges[id];
|
||||
vec3 pu = particles[e.u].pos_mass.xyz;
|
||||
vec3 pv = particles[e.v].pos_mass.xyz;
|
||||
|
||||
vec3 d = pv - pu;
|
||||
float dist = length(d) + 1e-6;
|
||||
vec3 dir = d / dist;
|
||||
|
||||
// Hooke: F = k * (dist - rest)
|
||||
float stretch = dist - e.restLength;
|
||||
float mag = springK * e.weight * stretch;
|
||||
mag = clamp(mag, -maxSpringForce, maxSpringForce);
|
||||
|
||||
vec3 F = mag * dir;
|
||||
|
||||
atomicAdd(particles[e.u].force_pad.x, F.x);
|
||||
atomicAdd(particles[e.u].force_pad.y, F.y);
|
||||
atomicAdd(particles[e.u].force_pad.z, F.z);
|
||||
|
||||
atomicAdd(particles[e.v].force_pad.x, -F.x);
|
||||
atomicAdd(particles[e.v].force_pad.y, -F.y);
|
||||
atomicAdd(particles[e.v].force_pad.z, -F.z);
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
static const char* CS_INTEGRATE = R"GLSL(
|
||||
#version 430
|
||||
layout(local_size_x = 256) in;
|
||||
|
||||
struct Particle {
|
||||
vec4 pos_mass;
|
||||
vec4 vel_pad;
|
||||
vec4 force_pad;
|
||||
};
|
||||
|
||||
layout(std430, binding=0) buffer ParticleBuffer {
|
||||
Particle particles[];
|
||||
};
|
||||
|
||||
uniform uint particleCount;
|
||||
uniform float dt;
|
||||
uniform float damping;
|
||||
uniform float stepScale;
|
||||
|
||||
void main()
|
||||
{
|
||||
uint id = gl_GlobalInvocationID.x;
|
||||
if (id >= particleCount) return;
|
||||
|
||||
vec3 vel = particles[id].vel_pad.xyz;
|
||||
vec3 force = particles[id].force_pad.xyz;
|
||||
|
||||
vel = (vel + dt * force) * damping;
|
||||
vec3 pos = particles[id].pos_mass.xyz + (dt * stepScale) * vel;
|
||||
|
||||
particles[id].vel_pad.xyz = vel;
|
||||
particles[id].pos_mass.xyz = pos;
|
||||
particles[id].force_pad.xyz = vec3(0.0);
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rendering shaders
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
static const char* VS_POINTS = R"GLSL(
|
||||
#version 430
|
||||
layout(location=0) in vec3 aPos;
|
||||
|
||||
uniform mat4 uMVP;
|
||||
uniform float uPointSize;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = uMVP * vec4(aPos, 1.0);
|
||||
gl_PointSize = uPointSize;
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
static const char* FS_POINTS = R"GLSL(
|
||||
#version 430
|
||||
out vec4 FragColor;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 p = gl_PointCoord * 2.0 - 1.0;
|
||||
if (dot(p, p) > 1.0) discard;
|
||||
FragColor = vec4(0.90, 0.95, 1.00, 1.00);
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Main
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
int main()
|
||||
{
|
||||
SetConfigFlags(FLAG_MSAA_4X_HINT | FLAG_WINDOW_RESIZABLE);
|
||||
InitWindow(1400, 900, "raylib rlgl GPU force-directed layout");
|
||||
|
||||
TraceLog(LOG_INFO, "GL_VERSION: %s", glGetString(GL_VERSION));
|
||||
TraceLog(LOG_INFO, "GLSL_VERSION: %s", glGetString(GL_SHADING_LANGUAGE_VERSION));
|
||||
|
||||
try {
|
||||
Require(HasExtension("GL_ARB_compute_shader"),
|
||||
"Missing GL_ARB_compute_shader. Build raylib with OpenGL 4.3.");
|
||||
Require(HasExtension("GL_ARB_shader_storage_buffer_object"),
|
||||
"Missing GL_ARB_shader_storage_buffer_object.");
|
||||
Require(HasExtension("GL_ARB_shader_atomic_float"), "Missing GL_ARB_shader_atomic_float.");
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Simulation parameters
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
const std::uint32_t nodeCount = 30000; // change to 100000 on your machine
|
||||
const std::uint32_t approxEdgeCount = nodeCount * 2;
|
||||
|
||||
const int gridX = 64;
|
||||
const int gridY = 64;
|
||||
const int gridZ = 64;
|
||||
const std::uint32_t gridCellCount = std::uint32_t(gridX * gridY * gridZ);
|
||||
|
||||
Vector3 simMin{-40.0f, -40.0f, -40.0f};
|
||||
Vector3 simMax{40.0f, 40.0f, 40.0f};
|
||||
Vector3 simExtent = Vector3Subtract(simMax, simMin);
|
||||
|
||||
float cellSize = std::max(
|
||||
{simExtent.x / float(gridX), simExtent.y / float(gridY), simExtent.z / float(gridZ)});
|
||||
|
||||
float dt = 0.020f;
|
||||
float damping = 0.90f;
|
||||
float stepScale = 1.0f;
|
||||
float repulsionK2 = 0.35f;
|
||||
float springK = 0.18f;
|
||||
float springRest = 1.2f;
|
||||
float softening = 0.02f;
|
||||
float maxRepulsionForce = 60.0f;
|
||||
float maxSpringForce = 40.0f;
|
||||
float selfCellFactor = 0.15f;
|
||||
int radiusCells = 2;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// CPU-side initial data
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
std::vector<ParticleGPU> particles(nodeCount);
|
||||
for (std::uint32_t i = 0; i < nodeCount; ++i) {
|
||||
float rx = float(GetRandomValue(-10000, 10000)) / 10000.0f;
|
||||
float ry = float(GetRandomValue(-10000, 10000)) / 10000.0f;
|
||||
float rz = float(GetRandomValue(-10000, 10000)) / 10000.0f;
|
||||
|
||||
Vector3 p{simMin.x + (rx * 0.5f + 0.5f) * simExtent.x,
|
||||
simMin.y + (ry * 0.5f + 0.5f) * simExtent.y,
|
||||
simMin.z + (rz * 0.5f + 0.5f) * simExtent.z};
|
||||
|
||||
particles[i].pos_mass[0] = p.x;
|
||||
particles[i].pos_mass[1] = p.y;
|
||||
particles[i].pos_mass[2] = p.z;
|
||||
particles[i].pos_mass[3] = 1.0f;
|
||||
|
||||
particles[i].vel_pad[0] = 0.0f;
|
||||
particles[i].vel_pad[1] = 0.0f;
|
||||
particles[i].vel_pad[2] = 0.0f;
|
||||
particles[i].vel_pad[3] = 0.0f;
|
||||
|
||||
particles[i].force_pad[0] = 0.0f;
|
||||
particles[i].force_pad[1] = 0.0f;
|
||||
particles[i].force_pad[2] = 0.0f;
|
||||
particles[i].force_pad[3] = 0.0f;
|
||||
}
|
||||
|
||||
std::vector<EdgeGPU> edges;
|
||||
edges.reserve(approxEdgeCount);
|
||||
|
||||
// Example spring graph:
|
||||
// - chain edge
|
||||
// - one random edge
|
||||
// This gives a graph-like structure instead of pure gas.
|
||||
for (std::uint32_t i = 0; i + 1 < nodeCount; ++i) {
|
||||
edges.push_back(EdgeGPU{i, i + 1, 1.0f, springRest});
|
||||
}
|
||||
|
||||
for (std::uint32_t i = 0; i < nodeCount; ++i) {
|
||||
std::uint32_t j = std::uint32_t(GetRandomValue(0, int(nodeCount - 1)));
|
||||
if (j != i) {
|
||||
edges.push_back(EdgeGPU{i, j, 1.0f, springRest});
|
||||
}
|
||||
}
|
||||
|
||||
const std::uint32_t edgeCount = (std::uint32_t)edges.size();
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// GPU buffers via rlgl
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
unsigned int particleSsbo =
|
||||
rlLoadShaderBuffer((unsigned int)(particles.size() * sizeof(ParticleGPU)),
|
||||
particles.data(), RL_DYNAMIC_COPY);
|
||||
|
||||
unsigned int gridAccumSsbo = rlLoadShaderBuffer(
|
||||
gridCellCount * (unsigned int)sizeof(CellAccumGPU), nullptr, RL_DYNAMIC_COPY);
|
||||
|
||||
unsigned int gridFinalSsbo = rlLoadShaderBuffer(
|
||||
gridCellCount * (unsigned int)sizeof(CellFinalGPU), nullptr, RL_DYNAMIC_COPY);
|
||||
|
||||
unsigned int edgeSsbo = rlLoadShaderBuffer((unsigned int)(edges.size() * sizeof(EdgeGPU)),
|
||||
edges.data(), RL_STATIC_DRAW);
|
||||
|
||||
Require(particleSsbo != 0, "Failed to create particle SSBO");
|
||||
Require(gridAccumSsbo != 0, "Failed to create gridAccum SSBO");
|
||||
Require(gridFinalSsbo != 0, "Failed to create gridFinal SSBO");
|
||||
Require(edgeSsbo != 0, "Failed to create edge SSBO");
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Programs via rlgl
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
unsigned int progClear = BuildComputeProgramRlgl(CS_CLEAR_GRID);
|
||||
unsigned int progAccum = BuildComputeProgramRlgl(CS_ACCUM_GRID);
|
||||
unsigned int progFinalize = BuildComputeProgramRlgl(CS_FINALIZE_GRID);
|
||||
unsigned int progRepel = BuildComputeProgramRlgl(CS_REPULSION);
|
||||
unsigned int progSprings = BuildComputeProgramRlgl(CS_SPRINGS);
|
||||
unsigned int progIntegrate = BuildComputeProgramRlgl(CS_INTEGRATE);
|
||||
unsigned int progPoints = BuildGraphicsProgramRlgl(VS_POINTS, FS_POINTS);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Rendering state
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
GLuint vao = 0;
|
||||
glGenVertexArrays(1, &vao);
|
||||
glBindVertexArray(vao);
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, particleSsbo);
|
||||
glEnableVertexAttribArray(0);
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, (GLsizei)sizeof(ParticleGPU),
|
||||
(const void*)0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
|
||||
glBindVertexArray(0);
|
||||
|
||||
Camera3D camera{};
|
||||
camera.position = {0.0f, 0.0f, 110.0f};
|
||||
camera.target = {0.0f, 0.0f, 0.0f};
|
||||
camera.up = {0.0f, 1.0f, 0.0f};
|
||||
camera.fovy = 60.0f;
|
||||
camera.projection = CAMERA_PERSPECTIVE;
|
||||
|
||||
SetTargetFPS(60);
|
||||
|
||||
float orbit = 0.0f;
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Main loop
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
while (!WindowShouldClose()) {
|
||||
// Simple interactive tuning
|
||||
if (IsKeyPressed(KEY_ONE)) radiusCells = 1;
|
||||
if (IsKeyPressed(KEY_TWO)) radiusCells = 2;
|
||||
if (IsKeyPressed(KEY_THREE)) radiusCells = 3;
|
||||
|
||||
if (IsKeyDown(KEY_Q)) repulsionK2 *= 1.01f;
|
||||
if (IsKeyDown(KEY_A)) repulsionK2 *= 0.99f;
|
||||
|
||||
if (IsKeyDown(KEY_W)) springK *= 1.01f;
|
||||
if (IsKeyDown(KEY_S)) springK *= 0.99f;
|
||||
|
||||
if (IsKeyDown(KEY_E)) damping = std::min(0.999f, damping + 0.0005f);
|
||||
if (IsKeyDown(KEY_D)) damping = std::max(0.70f, damping - 0.0005f);
|
||||
|
||||
if (IsKeyPressed(KEY_SPACE)) stepScale = 1.0f;
|
||||
stepScale = std::max(0.10f, stepScale * 0.9994f);
|
||||
|
||||
orbit += 0.0020f;
|
||||
camera.position = {110.0f * std::sin(orbit), 35.0f, 110.0f * std::cos(orbit)};
|
||||
|
||||
// Flush raylib draw batch before raw GL / compute work
|
||||
rlDrawRenderBatchActive();
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Pass 1: clear grid accum
|
||||
// ------------------------------------------------------------
|
||||
rlEnableShader(progClear);
|
||||
rlBindShaderBuffer(gridAccumSsbo, 0);
|
||||
|
||||
{
|
||||
unsigned int u = (unsigned int)gridCellCount;
|
||||
int loc = rlGetLocationUniform(progClear, "cellCount");
|
||||
rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1);
|
||||
}
|
||||
|
||||
Dispatch1D(gridCellCount);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Pass 2: accumulate particles into grid
|
||||
// ------------------------------------------------------------
|
||||
rlEnableShader(progAccum);
|
||||
rlBindShaderBuffer(particleSsbo, 0);
|
||||
rlBindShaderBuffer(gridAccumSsbo, 1);
|
||||
|
||||
{
|
||||
unsigned int u = nodeCount;
|
||||
int loc = rlGetLocationUniform(progAccum, "particleCount");
|
||||
rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progAccum, "gridMin");
|
||||
float v[3] = {simMin.x, simMin.y, simMin.z};
|
||||
rlSetUniform(loc, v, RL_SHADER_UNIFORM_VEC3, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progAccum, "cellSize");
|
||||
rlSetUniform(loc, &cellSize, RL_SHADER_UNIFORM_FLOAT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progAccum, "gridDim");
|
||||
int v[3] = {gridX, gridY, gridZ};
|
||||
rlSetUniform(loc, v, RL_SHADER_UNIFORM_IVEC3, 1);
|
||||
}
|
||||
|
||||
Dispatch1D(nodeCount);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Pass 3: finalize cell COM
|
||||
// ------------------------------------------------------------
|
||||
rlEnableShader(progFinalize);
|
||||
rlBindShaderBuffer(gridAccumSsbo, 0);
|
||||
rlBindShaderBuffer(gridFinalSsbo, 1);
|
||||
|
||||
{
|
||||
unsigned int u = (unsigned int)gridCellCount;
|
||||
int loc = rlGetLocationUniform(progFinalize, "cellCount");
|
||||
rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1);
|
||||
}
|
||||
|
||||
Dispatch1D(gridCellCount);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Pass 4: repulsion
|
||||
// ------------------------------------------------------------
|
||||
rlEnableShader(progRepel);
|
||||
rlBindShaderBuffer(particleSsbo, 0);
|
||||
rlBindShaderBuffer(gridFinalSsbo, 1);
|
||||
|
||||
{
|
||||
unsigned int u = nodeCount;
|
||||
int loc = rlGetLocationUniform(progRepel, "particleCount");
|
||||
rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progRepel, "gridMin");
|
||||
float v[3] = {simMin.x, simMin.y, simMin.z};
|
||||
rlSetUniform(loc, v, RL_SHADER_UNIFORM_VEC3, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progRepel, "cellSize");
|
||||
rlSetUniform(loc, &cellSize, RL_SHADER_UNIFORM_FLOAT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progRepel, "gridDim");
|
||||
int v[3] = {gridX, gridY, gridZ};
|
||||
rlSetUniform(loc, v, RL_SHADER_UNIFORM_IVEC3, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progRepel, "radiusCells");
|
||||
rlSetUniform(loc, &radiusCells, RL_SHADER_UNIFORM_INT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progRepel, "softening");
|
||||
rlSetUniform(loc, &softening, RL_SHADER_UNIFORM_FLOAT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progRepel, "repulsionK2");
|
||||
rlSetUniform(loc, &repulsionK2, RL_SHADER_UNIFORM_FLOAT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progRepel, "maxRepulsionForce");
|
||||
rlSetUniform(loc, &maxRepulsionForce, RL_SHADER_UNIFORM_FLOAT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progRepel, "selfCellFactor");
|
||||
rlSetUniform(loc, &selfCellFactor, RL_SHADER_UNIFORM_FLOAT, 1);
|
||||
}
|
||||
|
||||
Dispatch1D(nodeCount);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Pass 5: springs
|
||||
// ------------------------------------------------------------
|
||||
rlEnableShader(progSprings);
|
||||
rlBindShaderBuffer(particleSsbo, 0);
|
||||
rlBindShaderBuffer(edgeSsbo, 1);
|
||||
|
||||
{
|
||||
unsigned int u = edgeCount;
|
||||
int loc = rlGetLocationUniform(progSprings, "edgeCount");
|
||||
rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progSprings, "springK");
|
||||
rlSetUniform(loc, &springK, RL_SHADER_UNIFORM_FLOAT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progSprings, "maxSpringForce");
|
||||
rlSetUniform(loc, &maxSpringForce, RL_SHADER_UNIFORM_FLOAT, 1);
|
||||
}
|
||||
|
||||
Dispatch1D(edgeCount);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Pass 6: integrate
|
||||
// ------------------------------------------------------------
|
||||
rlEnableShader(progIntegrate);
|
||||
rlBindShaderBuffer(particleSsbo, 0);
|
||||
|
||||
{
|
||||
unsigned int u = nodeCount;
|
||||
int loc = rlGetLocationUniform(progIntegrate, "particleCount");
|
||||
rlSetUniform(loc, &u, RL_SHADER_UNIFORM_UINT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progIntegrate, "dt");
|
||||
rlSetUniform(loc, &dt, RL_SHADER_UNIFORM_FLOAT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progIntegrate, "damping");
|
||||
rlSetUniform(loc, &damping, RL_SHADER_UNIFORM_FLOAT, 1);
|
||||
}
|
||||
{
|
||||
int loc = rlGetLocationUniform(progIntegrate, "stepScale");
|
||||
rlSetUniform(loc, &stepScale, RL_SHADER_UNIFORM_FLOAT, 1);
|
||||
}
|
||||
|
||||
Dispatch1D(nodeCount);
|
||||
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT | GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT);
|
||||
|
||||
rlDisableShader();
|
||||
|
||||
// ------------------------------------------------------------
|
||||
// Draw
|
||||
// ------------------------------------------------------------
|
||||
BeginDrawing();
|
||||
ClearBackground(Color{9, 11, 16, 255});
|
||||
|
||||
BeginMode3D(camera);
|
||||
DrawCubeWiresV(Vector3Scale(Vector3Add(simMin, simMax), 0.5f), simExtent,
|
||||
Fade(GRAY, 0.20f));
|
||||
|
||||
rlDrawRenderBatchActive();
|
||||
|
||||
Matrix view = MatrixFromCamera(camera);
|
||||
Matrix proj = MatrixPerspective(DEG2RAD * camera.fovy,
|
||||
(float)GetScreenWidth() / (float)GetScreenHeight(),
|
||||
0.1f, 1000.0f);
|
||||
Matrix mvp = MatrixMultiply(proj, view);
|
||||
|
||||
glUseProgram(progPoints);
|
||||
glBindVertexArray(vao);
|
||||
|
||||
GLint locMvp = glGetUniformLocation(progPoints, "uMVP");
|
||||
GLint locPt = glGetUniformLocation(progPoints, "uPointSize");
|
||||
glUniformMatrix4fv(locMvp, 1, GL_FALSE, (const float*)&mvp);
|
||||
glUniform1f(locPt, 2.0f);
|
||||
|
||||
glEnable(GL_PROGRAM_POINT_SIZE);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
glDrawArrays(GL_POINTS, 0, (GLsizei)nodeCount);
|
||||
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
|
||||
EndMode3D();
|
||||
|
||||
DrawText(TextFormat("nodes=%u edges=%u grid=%dx%dx%d radius=%d", nodeCount, edgeCount,
|
||||
gridX, gridY, gridZ, radiusCells),
|
||||
10, 10, 20, RAYWHITE);
|
||||
DrawText(TextFormat("repulsionK2=%.3f springK=%.3f damping=%.3f stepScale=%.3f",
|
||||
repulsionK2, springK, damping, stepScale),
|
||||
10, 36, 20, RAYWHITE);
|
||||
DrawText(
|
||||
"Keys: 1/2/3 radius, Q/A repulsion, W/S spring, E/D damping, SPACE reset cooling",
|
||||
10, 62, 18, Fade(RAYWHITE, 0.8f));
|
||||
|
||||
EndDrawing();
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Cleanup
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
glDeleteVertexArrays(1, &vao);
|
||||
|
||||
rlUnloadShaderProgram(progPoints);
|
||||
rlUnloadShaderProgram(progClear);
|
||||
rlUnloadShaderProgram(progAccum);
|
||||
rlUnloadShaderProgram(progFinalize);
|
||||
rlUnloadShaderProgram(progRepel);
|
||||
rlUnloadShaderProgram(progSprings);
|
||||
rlUnloadShaderProgram(progIntegrate);
|
||||
|
||||
rlUnloadShaderBuffer(edgeSsbo);
|
||||
rlUnloadShaderBuffer(gridFinalSsbo);
|
||||
rlUnloadShaderBuffer(gridAccumSsbo);
|
||||
rlUnloadShaderBuffer(particleSsbo);
|
||||
}
|
||||
catch (const std::exception& e) {
|
||||
TraceLog(LOG_ERROR, "%s", e.what());
|
||||
}
|
||||
|
||||
CloseWindow();
|
||||
return 0;
|
||||
}
|
||||
@ -18,6 +18,7 @@ auto input_handler::init_handlers() -> void
|
||||
register_mouse_pressed_handler(MOUSE_BUTTON_LEFT, &input_handler::add_block);
|
||||
register_mouse_pressed_handler(MOUSE_BUTTON_LEFT, &input_handler::start_add_block);
|
||||
register_mouse_pressed_handler(MOUSE_BUTTON_MIDDLE, &input_handler::place_goal);
|
||||
register_mouse_pressed_handler(MOUSE_BUTTON_MIDDLE, &input_handler::select_state);
|
||||
register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::camera_start_rotate);
|
||||
register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::remove_block);
|
||||
register_mouse_pressed_handler(MOUSE_BUTTON_RIGHT, &input_handler::clear_add_block);
|
||||
@ -245,7 +246,7 @@ auto input_handler::remove_block() -> void
|
||||
auto input_handler::place_goal() const -> void
|
||||
{
|
||||
const puzzle& current = state.get_current_state();
|
||||
if (!editing || !current.covers(hov_x, hov_y)) {
|
||||
if (!editing || !mouse_in_board_pane() || !current.covers(hov_x, hov_y)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -257,6 +258,16 @@ auto input_handler::place_goal() const -> void
|
||||
state.edit_starting_state(*next);
|
||||
}
|
||||
|
||||
auto input_handler::select_state() const -> void
|
||||
{
|
||||
if (!mouse_in_graph_pane() || collision_mass == static_cast<size_t>(-1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const puzzle& selected = state.get_state(collision_mass);
|
||||
state.update_current_state(selected);
|
||||
}
|
||||
|
||||
auto input_handler::toggle_camera_lock() -> void
|
||||
{
|
||||
if (!camera_lock) {
|
||||
|
||||
@ -63,6 +63,39 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
||||
transforms.clear();
|
||||
colors.clear();
|
||||
|
||||
// Collisions
|
||||
// TODO: This would benefit greatly from a spatial data structure.
|
||||
// Would it be worth to copy the octree from the physics thread?
|
||||
input.collision_mass = -1;
|
||||
if (input.mouse_in_graph_pane()) {
|
||||
const Ray ray = GetScreenToWorldRayEx(
|
||||
GetMousePosition() - Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT),
|
||||
camera.camera, graph_target.texture.width, graph_target.texture.height);
|
||||
RayCollision collision; // Ray collision hit info
|
||||
|
||||
size_t mass = 0;
|
||||
for (const auto& [x, y, z] : masses) {
|
||||
collision = GetRayCollisionBox(ray,
|
||||
BoundingBox{
|
||||
{
|
||||
x - VERTEX_SIZE / 2.0f,
|
||||
y - VERTEX_SIZE / 2.0f,
|
||||
z - VERTEX_SIZE / 2.0f
|
||||
},
|
||||
{
|
||||
x + VERTEX_SIZE / 2.0f,
|
||||
y + VERTEX_SIZE / 2.0f,
|
||||
z + VERTEX_SIZE / 2.0f
|
||||
}
|
||||
});
|
||||
if (collision.hit) {
|
||||
input.collision_mass = mass;
|
||||
break;
|
||||
}
|
||||
++mass;
|
||||
}
|
||||
}
|
||||
|
||||
// Find max distance to interpolate colors in the given [0, max] range
|
||||
int max_distance = 0;
|
||||
for (const int distance : state.get_distances()) {
|
||||
@ -105,9 +138,10 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
||||
// Visited vertex
|
||||
c = VERTEX_VISITED_COLOR;
|
||||
} else if (input.color_by_distance && distances.size() == masses.size()) {
|
||||
c = lerp_color(VERTEX_FARTHEST_COLOR,
|
||||
VERTEX_CLOSEST_COLOR,
|
||||
static_cast<float>(distances[mass]));
|
||||
c = lerp_color(VERTEX_FARTHEST_COLOR, VERTEX_CLOSEST_COLOR, static_cast<float>(distances[mass]));
|
||||
}
|
||||
if (mass == input.collision_mass) {
|
||||
c = RED;
|
||||
}
|
||||
|
||||
// Current vertex is drawn as individual cube to increase its size
|
||||
|
||||
Reference in New Issue
Block a user