diff --git a/include/config.hpp b/include/config.hpp index 95a91ef..d3ea536 100644 --- a/include/config.hpp +++ b/include/config.hpp @@ -41,10 +41,10 @@ constexpr int REPULSION_GRID_REFRESH = 5; // Frames between grid rebuilds constexpr float VERLET_DAMPENING = 0.05; // [0, 1] // Graph Drawing -constexpr float VERTEX_SIZE = 0.1; +constexpr float VERTEX_SIZE = 0.5; constexpr Color VERTEX_COLOR = GREEN; constexpr Color EDGE_COLOR = DARKGREEN; -constexpr int DRAW_VERTICES_LIMIT = 10000; +constexpr int DRAW_VERTICES_LIMIT = 100000; // Klotski Drawing constexpr int BOARD_PADDING = 5; diff --git a/include/mass_springs.hpp b/include/mass_springs.hpp index 0c3043f..3bea1c0 100644 --- a/include/mass_springs.hpp +++ b/include/mass_springs.hpp @@ -118,6 +118,8 @@ public: auto GetMass(const State &state) -> Mass &; + auto GetMass(const State &state) const -> const Mass &; + auto AddSpring(const State &massA, const State &massB, float spring_constant, float dampening_constant, float rest_length) -> void; diff --git a/include/renderer.hpp b/include/renderer.hpp index 40826ef..fe7c630 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -49,6 +49,10 @@ private: RenderTexture klotski_target; RenderTexture menu_target; + float *cube; + Mesh graph; + Material mat; + public: bool mark_solutions; bool connect_solutions; @@ -62,6 +66,7 @@ public: klotski_target = LoadRenderTexture(GetScreenWidth() / 2.0, GetScreenHeight() - MENU_HEIGHT); menu_target = LoadRenderTexture(GetScreenWidth(), MENU_HEIGHT); + AllocateGraphMesh(); } Renderer(const Renderer ©) = delete; @@ -73,23 +78,31 @@ public: UnloadRenderTexture(render_target); UnloadRenderTexture(klotski_target); UnloadRenderTexture(menu_target); + UnloadMesh(graph); + UnloadMaterial(mat); + MemFree(cube); } public: - auto UpdateCamera(const MassSpringSystem &masssprings, const State ¤t) - -> void; + auto UpdateCamera(const MassSpringSystem &mass_springs, + const State ¤t_state) -> void; auto UpdateTextureSizes() -> void; - auto DrawMassSprings(const MassSpringSystem &masssprings, - const State ¤t, + auto AllocateGraphMesh() -> void; + + auto ReallocateGraphMeshIfNecessary(const MassSpringSystem &mass_springs) + -> void; + + auto DrawMassSprings(const MassSpringSystem &mass_springs, + const State ¤t_state, const std::unordered_set &winning_states) -> void; auto DrawKlotski(const State &state, int hov_x, int hov_y, int sel_x, int sel_y, int block_add_x, int block_add_y, const WinCondition win_condition) -> void; - auto DrawMenu(const MassSpringSystem &masssprings, int current_preset, + auto DrawMenu(const MassSpringSystem &mass_springs, int current_preset, const State ¤t_state, const std::unordered_set &winning_states) -> void; diff --git a/src/main.cpp b/src/main.cpp index 7a8e1ea..8078c71 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -105,8 +105,11 @@ auto main(int argc, char *argv[]) -> int { renderer.UpdateCamera(mass_springs, state.current_state); renderer.UpdateTextureSizes(); + renderer.ReallocateGraphMeshIfNecessary(mass_springs); renderer.DrawMassSprings(mass_springs, state.current_state, state.winning_states); + + // TODO: Don't render each frame renderer.DrawKlotski(state.current_state, input.hov_x, input.hov_y, input.sel_x, input.sel_y, input.block_add_x, input.block_add_y, state.CurrentWinCondition()); diff --git a/src/mass_springs.cpp b/src/mass_springs.cpp index c61b7c6..9b6ecf6 100644 --- a/src/mass_springs.cpp +++ b/src/mass_springs.cpp @@ -89,6 +89,10 @@ auto MassSpringSystem::GetMass(const State &state) -> Mass & { return masses.at(state); } +auto MassSpringSystem::GetMass(const State &state) const -> const Mass & { + return masses.at(state); +} + auto MassSpringSystem::AddSpring(const State &massA, const State &massB, float spring_constant, float dampening_constant, float rest_length) diff --git a/src/renderer.cpp b/src/renderer.cpp index 7af2911..5d64601 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,8 +1,11 @@ #include "renderer.hpp" +#include +#include #include #include #include +#include #include #include "config.hpp" @@ -111,8 +114,60 @@ auto Renderer::UpdateTextureSizes() -> void { menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT); } -auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings, - const State ¤t, +auto Renderer::AllocateGraphMesh() -> void { + int vertices = 36; + int max_masses = 100000; + + graph = {0}; + graph.vertexCount = max_masses * vertices; + graph.triangleCount = max_masses * 12; + graph.vertices = (float *)MemAlloc(graph.vertexCount * 3 * sizeof(float)); + + memset(graph.vertices, 0, graph.vertexCount * 3 * sizeof(float)); + + UploadMesh(&graph, true); + + Mesh indexed_cube = GenMeshCube(VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE); + cube = (float *)MemAlloc(indexed_cube.triangleCount * 3 * 3 * sizeof(float)); + for (int i = 0; i < indexed_cube.triangleCount * 3; ++i) { + int idx = indexed_cube.indices[i]; + cube[i * 3 + 0] = indexed_cube.vertices[idx * 3 + 0]; + cube[i * 3 + 1] = indexed_cube.vertices[idx * 3 + 1]; + cube[i * 3 + 2] = indexed_cube.vertices[idx * 3 + 2]; + } + UnloadMesh(indexed_cube); + + mat = LoadMaterialDefault(); + mat.maps[MATERIAL_MAP_DIFFUSE].color = VERTEX_COLOR; + + std::cout << "Allocated graph mesh." << std::endl; +} + +auto Renderer::ReallocateGraphMeshIfNecessary( + const MassSpringSystem &mass_springs) -> void { + if (graph.vertexCount / 3 > mass_springs.masses.size()) { + return; + } + + int vertices = 36; + int max_masses = mass_springs.masses.size() * 2; + + UnloadMesh(graph); + + graph = {0}; + graph.vertexCount = max_masses * vertices; + graph.triangleCount = max_masses * 12; + graph.vertices = (float *)MemAlloc(graph.vertexCount * 3 * sizeof(float)); + + memset(graph.vertices, 0, graph.vertexCount * 3 * sizeof(float)); + + UploadMesh(&graph, true); + + std::cout << "Reallocated graph mesh." << std::endl; +} + +auto Renderer::DrawMassSprings(const MassSpringSystem &mass_springs, + const State ¤t_state, const std::unordered_set &winning_states) -> void { BeginTextureMode(render_target); @@ -120,32 +175,63 @@ auto Renderer::DrawMassSprings(const MassSpringSystem &masssprings, BeginMode3D(camera.camera); - // Draw springs - for (const auto &[states, spring] : masssprings.springs) { - const Mass a = spring.massA; - const Mass b = spring.massB; - - DrawLine3D(a.position, b.position, EDGE_COLOR); + // Draw springs (batched) + rlBegin(RL_LINES); + for (const auto &[states, spring] : mass_springs.springs) { + rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a); + rlVertex3f(spring.massA.position.x, spring.massA.position.y, + spring.massA.position.z); + rlVertex3f(spring.massB.position.x, spring.massB.position.y, + spring.massB.position.z); } + rlEnd(); - // Draw masses (high performance impact) - for (const auto &[state, mass] : masssprings.masses) { - if (state == current) { - DrawCube(mass.position, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE, - RED); - } else if (winning_states.contains(state)) { - // TODO: Would be better to store the winning flag in the state itself + // Draw masses (batched) + int vertices = 36; + int current_size = mass_springs.masses.size(); + int i = 0; + for (const auto &[state, mass] : mass_springs.masses) { + for (int v = 0; v < vertices; ++v) { + int dst = (i * vertices + v) * 3; + int src = v * 3; + graph.vertices[dst + 0] = cube[src + 0] + mass.position.x; + graph.vertices[dst + 1] = cube[src + 1] + mass.position.y; + graph.vertices[dst + 2] = cube[src + 2] + mass.position.z; + } + ++i; + } + UpdateMeshBuffer(graph, 0, graph.vertices, + current_size * vertices * 3 * sizeof(float), 0); + + // Temporarily reduce the vertex count to the used part (we overallocate) + int full_size = graph.vertexCount; + graph.vertexCount = current_size * vertices; + graph.triangleCount = current_size * 12; + + DrawMesh(graph, mat, MatrixIdentity()); + + // Restore the vertex count + graph.vertexCount = full_size; + graph.triangleCount = full_size / 3; + + // Mark current state + const Mass ¤t_mass = mass_springs.GetMass(current_state); + DrawCube(current_mass.position, VERTEX_SIZE * 4, VERTEX_SIZE * 4, + VERTEX_SIZE * 4, RED); + + // Mark winning states + if (mark_solutions || connect_solutions) { + for (const auto &state : winning_states) { + const Mass &winning_mass = mass_springs.GetMass(state); if (mark_solutions) { - DrawCube(mass.position, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE, + DrawCube(winning_mass.position, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE, 4 * VERTEX_SIZE, BLUE); } + if (connect_solutions) { - DrawLine3D(mass.position, masssprings.masses.at(current).position, - PURPLE); + DrawLine3D(winning_mass.position, + mass_springs.GetMass(current_state).position, PURPLE); } - } else if (masssprings.masses.size() <= DRAW_VERTICES_LIMIT) { - DrawCube(mass.position, VERTEX_SIZE, VERTEX_SIZE, VERTEX_SIZE, - VERTEX_COLOR); } }