#include "renderer.hpp" #include "config.hpp" #include #include #include #ifdef TRACY #include #endif auto renderer::update_texture_sizes() -> void { if (!IsWindowResized()) { return; } UnloadRenderTexture(render_target); UnloadRenderTexture(klotski_target); UnloadRenderTexture(menu_target); const int width = GetScreenWidth() / 2; const int height = GetScreenHeight() - MENU_HEIGHT; render_target = LoadRenderTexture(width, height); klotski_target = LoadRenderTexture(width, height); menu_target = LoadRenderTexture(width * 2, MENU_HEIGHT); } auto renderer::draw_mass_springs(const std::vector& masses) -> void { #ifdef TRACY ZoneScoped; #endif if (masses.size() != state.get_state_count()) { // Because the physics run in a different thread, it might need time to catch up return; } // Prepare connection batching { #ifdef TRACY ZoneNamedN(prepare_masses, "PrepareConnectionsBatching", true); #endif connections.clear(); connections.reserve(state.get_target_count()); if (input.connect_solutions) { for (const size_t& _state : state.get_winning_indices()) { const Vector3& current_mass = masses.at(state.get_current_index()); const Vector3& winning_mass = masses.at(_state); connections.emplace_back(current_mass, winning_mass); DrawLine3D(current_mass, winning_mass, Fade(TARGET_BLOCK_COLOR, 0.5)); } } } // Prepare cube instancing { #ifdef TRACY ZoneNamedN(prepare_masses, "PrepareMassInstancing", true); #endif if (masses.size() < DRAW_VERTICES_LIMIT) { // Don't have to reserve, capacity is already set to DRAW_VERTICES_LIMIT in constructor transforms.clear(); colors.clear(); size_t mass = 0; for (const auto& [x, y, z] : masses) { transforms.emplace_back(MatrixTranslate(x, y, z)); // Normal vertex Color c = VERTEX_COLOR; if ((input.mark_solutions || input.mark_path) && state.get_winning_indices().contains(mass)) { // Winning vertex c = VERTEX_TARGET_COLOR; } else if ((input.mark_solutions || input.mark_path) && state.get_path_indices().contains(mass)) { // Path vertex c = VERTEX_PATH_COLOR; } else if (mass == state.get_starting_index()) { // Starting vertex c = VERTEX_START_COLOR; } else if (state.get_visit_counts().at(mass) > 0) { // Visited vertex c = VERTEX_VISITED_COLOR; } // Current vertex is drawn as individual cube to increase its size colors.emplace_back(c); ++mass; } } rlUpdateVertexBuffer(color_vbo_id, colors.data(), colors.size() * sizeof(Color), 0); } BeginTextureMode(render_target); ClearBackground(RAYWHITE); BeginMode3D(camera.camera); // Draw springs (batched) { #ifdef TRACY ZoneNamedN(draw_springs, "DrawSprings", true); #endif rlBegin(RL_LINES); for (const auto& [from, to] : state.get_links()) { if (masses.size() > from && masses.size() > to) { const auto& [ax, ay, az] = masses.at(from); const auto& [bx, by, bz] = masses.at(to); rlColor4ub(EDGE_COLOR.r, EDGE_COLOR.g, EDGE_COLOR.b, EDGE_COLOR.a); rlVertex3f(ax, ay, az); rlVertex3f(bx, by, bz); } } rlEnd(); } // Draw masses (instanced) { #ifdef TRACY ZoneNamedN(draw_masses, "DrawMasses", true); #endif if (masses.size() < DRAW_VERTICES_LIMIT) { // NOTE: I don't know if drawing all this inside a shader would make it // much faster... The amount of data sent to the GPU would be // reduced (just positions instead of matrices), but is this // noticable for < 100000 cubes? DrawMeshInstanced(cube_instance, vertex_mat, transforms.data(), masses.size()); // NOLINT(*-narrowing-conversions) } } // Connect current to winning states (batched) const auto [r, g, b, a] = Fade(VERTEX_CURRENT_COLOR, 0.3); rlBegin(RL_LINES); for (const auto& [from, to] : connections) { const auto& [ax, ay, az] = from; const auto& [bx, by, bz] = to; rlColor4ub(r, g, b, a); rlVertex3f(ax, ay, az); rlVertex3f(bx, by, bz); } rlEnd(); // Mark current state const size_t current_index = state.get_current_index(); if (masses.size() > current_index) { const Vector3& current_mass = masses.at(current_index); DrawCube(current_mass, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_SIZE * 2, VERTEX_CURRENT_COLOR); } EndMode3D(); EndTextureMode(); } auto renderer::draw_klotski() const -> void { #ifdef TRACY ZoneScoped; #endif BeginTextureMode(klotski_target); ClearBackground(RAYWHITE); gui.draw_puzzle_board(); EndTextureMode(); } auto renderer::draw_menu() const -> void { #ifdef TRACY ZoneScoped; #endif BeginTextureMode(menu_target); ClearBackground(RAYWHITE); gui.draw_main_menu(); EndTextureMode(); } auto renderer::draw_textures(const int fps, const int ups, const size_t mass_count, const size_t spring_count) const -> void { BeginDrawing(); DrawTextureRec(menu_target.texture, Rectangle(0, 0, menu_target.texture.width, -menu_target.texture.height), Vector2(0, 0), WHITE); DrawTextureRec(klotski_target.texture, Rectangle(0, 0, klotski_target.texture.width, -klotski_target.texture.height), Vector2(0, MENU_HEIGHT), WHITE); DrawTextureRec(render_target.texture, Rectangle(0, 0, render_target.texture.width, -render_target.texture.height), Vector2(GetScreenWidth() / 2.0f, MENU_HEIGHT), WHITE); // Draw borders DrawRectangleLinesEx(Rectangle(0, 0, GetScreenWidth(), MENU_HEIGHT), 1.0f, BLACK); DrawRectangleLinesEx(Rectangle(0, MENU_HEIGHT, GetScreenWidth() / 2.0f, GetScreenHeight() - MENU_HEIGHT), 1.0f, BLACK); DrawRectangleLinesEx(Rectangle(GetScreenWidth() / 2.0f, MENU_HEIGHT, GetScreenWidth() / 2.0f, GetScreenHeight() - MENU_HEIGHT), 1.0f, BLACK); gui.draw_graph_overlay(fps, ups, mass_count, spring_count); gui.draw_save_preset_popup(); gui.update(); EndDrawing(); } auto renderer::render(const std::vector& masses, const int fps, const int ups, const size_t mass_count, const size_t spring_count) -> void { update_texture_sizes(); draw_mass_springs(masses); draw_klotski(); draw_menu(); draw_textures(fps, ups, mass_count, spring_count); }