enable asynchronous octree building (somehow this is stable with morton octree)
This commit is contained in:
@ -3,8 +3,9 @@
|
|||||||
|
|
||||||
#include <raylib.h>
|
#include <raylib.h>
|
||||||
|
|
||||||
// TODO: Using the octree from the last frame completely breaks the physics :/
|
// Calculate the octree parallel to the layout calculation.
|
||||||
// #define ASYNC_OCTREE
|
// Layout uses the octree from last frame.
|
||||||
|
#define ASYNC_OCTREE
|
||||||
|
|
||||||
// Gets set by CMake
|
// Gets set by CMake
|
||||||
// #define THREADPOOL // Enable physics threadpool
|
// #define THREADPOOL // Enable physics threadpool
|
||||||
|
|||||||
@ -44,10 +44,10 @@ private:
|
|||||||
public:
|
public:
|
||||||
octree() = default;
|
octree() = default;
|
||||||
|
|
||||||
octree(const octree& copy) = delete;
|
// octree(const octree& copy) = delete;
|
||||||
auto operator=(const octree& copy) -> octree& = delete;
|
// auto operator=(const octree& copy) -> octree& = delete;
|
||||||
octree(octree&& move) = delete;
|
// octree(octree&& move) = delete;
|
||||||
auto operator=(octree&& move) -> octree& = delete;
|
// auto operator=(octree&& move) -> octree& = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
[[nodiscard]] INLINE static inline auto get_octant(const Vector3& box_min,
|
[[nodiscard]] INLINE static inline auto get_octant(const Vector3& box_min,
|
||||||
|
|||||||
@ -90,16 +90,16 @@ auto cpu_layout_engine::physics_thread(physics_state& state, const std::optional
|
|||||||
|
|
||||||
// Start building the octree for the next physics update.
|
// Start building the octree for the next physics update.
|
||||||
// Move the snapshot into the closure so it doesn't get captured by reference (don't use [&])
|
// Move the snapshot into the closure so it doesn't get captured by reference (don't use [&])
|
||||||
octree_future = octree_thread.submit_task([&tree_buffer, positions = std::move(positions)]()
|
octree_future = octree_thread.submit_task([&tree_buffer, &thread_pool, positions = std::move(positions)]()
|
||||||
{
|
{
|
||||||
octree::build_octree(tree_buffer, positions);
|
octree::build_octree_morton(tree_buffer, positions, thread_pool);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Rebuild the tree synchronously if we changed the number of masses to not use
|
// Rebuild the tree synchronously if we changed the number of masses to not use
|
||||||
// an empty tree from the last frame in the frame where the graph was generated
|
// an empty tree from the last frame in the frame where the graph was generated
|
||||||
if (last_mass_count != mass_springs.positions.size()) {
|
if (last_mass_count != mass_springs.positions.size()) {
|
||||||
traceln("Rebuilding octree synchronously because graph size changed");
|
traceln("Rebuilding octree synchronously because graph size changed");
|
||||||
octree::build_octree(mass_springs.tree, mass_springs.positions);
|
octree::build_octree_morton(mass_springs.tree, mass_springs.positions, thread_pool);
|
||||||
last_mass_count = mass_springs.positions.size();
|
last_mass_count = mass_springs.positions.size();
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
|||||||
@ -1,900 +0,0 @@
|
|||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -332,7 +332,13 @@ auto main(const int argc, char* argv[]) -> int
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
infoln("Using background thread for physics.");
|
infoln("Using background thread for physics.");
|
||||||
infoln("Using octree-barnes-hut for graph layout.");
|
infoln("Using linear octree + Barnes-Hut for graph layout.");
|
||||||
|
|
||||||
|
#ifdef ASYNC_OCTREE
|
||||||
|
infoln("Using asynchronous octree build.");
|
||||||
|
#else
|
||||||
|
infoln("Using synchronous octree build.");
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef THREADPOOL
|
#ifdef THREADPOOL
|
||||||
infoln("Additional thread-pool enabled ({} threads).", threads.get_thread_count());
|
infoln("Additional thread-pool enabled ({} threads).", threads.get_thread_count());
|
||||||
|
|||||||
@ -67,7 +67,10 @@ auto renderer::draw_mass_springs(const std::vector<Vector3>& masses) -> void
|
|||||||
// TODO: This would benefit greatly from a spatial data structure.
|
// TODO: This would benefit greatly from a spatial data structure.
|
||||||
// Would it be worth to copy the octree from the physics thread?
|
// Would it be worth to copy the octree from the physics thread?
|
||||||
input.collision_mass = -1;
|
input.collision_mass = -1;
|
||||||
if (input.mouse_in_graph_pane()) {
|
if (input.mouse_in_graph_pane() && IsKeyDown(KEY_Q)) {
|
||||||
|
#ifdef TRACY
|
||||||
|
ZoneNamedN(mass_collisions, "MassCollisions", true);
|
||||||
|
#endif
|
||||||
const Ray ray = GetScreenToWorldRayEx(
|
const Ray ray = GetScreenToWorldRayEx(
|
||||||
GetMousePosition() - Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT),
|
GetMousePosition() - Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT),
|
||||||
camera.camera, graph_target.texture.width, graph_target.texture.height);
|
camera.camera, graph_target.texture.width, graph_target.texture.height);
|
||||||
|
|||||||
Reference in New Issue
Block a user