diff --git a/README.md b/README.md index 7aadb47..3e1cda9 100755 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ It is an [astar algorithm](https://en.wikipedia.org/wiki/A*_search_algorithm), t ![astar](resources/Screenshot_20240128_093812.png) +![astar](resources/Screenshot_20240213_000512.png) + # Features * [x] Header-only library C++20 diff --git a/cmake/lib/raygui.cmake b/cmake/lib/raygui.cmake index e69dae3..463f0f8 100755 --- a/cmake/lib/raygui.cmake +++ b/cmake/lib/raygui.cmake @@ -12,6 +12,6 @@ if (NOT raygui_FOUND) GIT_TAG 4.0 ) FetchContent_MakeAvailable(raygui) - include_directories(${raygui_SOURCE_DIR}) - include_directories(${raygui_SOURCE_DIR}/src) + include_directories(SYSTEM ${raygui_SOURCE_DIR}) + include_directories(SYSTEM ${raygui_SOURCE_DIR}/src) endif() \ No newline at end of file diff --git a/include/astar/astar.hpp b/include/astar/astar.hpp index b4e03fa..9aa8816 100644 --- a/include/astar/astar.hpp +++ b/include/astar/astar.hpp @@ -1,3 +1,8 @@ +/* + * Header only library of A* algorithm by Bensuperpc + * MIT License + */ + #pragma once #include diff --git a/resources/Screenshot_20240213_000512.png b/resources/Screenshot_20240213_000512.png new file mode 100644 index 0000000..e85643f Binary files /dev/null and b/resources/Screenshot_20240213_000512.png differ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 19d8ce2..b6f20e9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -14,10 +14,19 @@ endif() function(test_bench_generator TEST_BENCH_NAME IS_TEST ADD_TO_TEST) + + set(SOURCES + "source/generator/generator.cpp" + ) + + set(HEADERS + "source/generator/generator.hpp" + ) + if (IS_TEST) - add_executable("${TEST_BENCH_NAME}" "source/test/${TEST_BENCH_NAME}.cpp" source/generator/generator.cpp source/generator/generator.hpp) + add_executable("${TEST_BENCH_NAME}" "source/test/${TEST_BENCH_NAME}.cpp" ${SOURCES} ${HEADERS}) else() - add_executable("${TEST_BENCH_NAME}" "source/benchmark/${TEST_BENCH_NAME}.cpp" source/generator/generator.cpp source/generator/generator.hpp) + add_executable("${TEST_BENCH_NAME}" "source/benchmark/${TEST_BENCH_NAME}.cpp" ${SOURCES} ${HEADERS}) endif() @@ -74,7 +83,8 @@ if(NOT WIN32) test_bench_generator(astar_test true true) test_bench_generator(astar_bench false true) - test_bench_generator(path_finder false false) + test_bench_generator(path_finder_compare false false) + test_bench_generator(path_finder_visual false false) endif() # ---- End-of-file commands ---- diff --git a/test/source/benchmark/astar_bench.cpp b/test/source/benchmark/astar_bench.cpp index a078308..056e803 100644 --- a/test/source/benchmark/astar_bench.cpp +++ b/test/source/benchmark/astar_bench.cpp @@ -9,7 +9,7 @@ static constexpr int64_t multiplier = 4; static constexpr int64_t minRange = 16; -static constexpr int64_t maxRange = 256; +static constexpr int64_t maxRange = 1024; static constexpr int64_t minThreadRange = 1; static constexpr int64_t maxThreadRange = 1; static constexpr int64_t repetitions = 1; diff --git a/test/source/benchmark/path_finder.cpp b/test/source/benchmark/path_finder_compare.cpp similarity index 100% rename from test/source/benchmark/path_finder.cpp rename to test/source/benchmark/path_finder_compare.cpp diff --git a/test/source/benchmark/path_finder_visual.cpp b/test/source/benchmark/path_finder_visual.cpp new file mode 100644 index 0000000..e45c36d --- /dev/null +++ b/test/source/benchmark/path_finder_visual.cpp @@ -0,0 +1,211 @@ +#include // std::array +#include // std::chrono::system_clock +#include // std::abs +#include // std::uint32_t +#include // std::cout, std::endl +#include // std::map +#include // std::unique_ptr +#include // std::random_device, std::mt19937, std::uniform_int_distribution +#include // std::vector + +#include "astar/astar.hpp" +#include "generator/generator.hpp" + +#include "raylib.h" + +#define RAYGUI_IMPLEMENTATION +extern "C" { +#include "src/raygui.h" +} + +auto main() -> int { + // Set log level for Raylib + SetTraceLogLevel(LOG_WARNING); + + const int screenWidth = 1920; + const int screenHeight = 1080; + + const int mapWidth = 192; + const int mapHeight = 108; + + const uint32_t targetFPS = 120; + + const uint32_t ImageUpdatePerSecond = 30; + + SetConfigFlags(FLAG_WINDOW_RESIZABLE | FLAG_MSAA_4X_HINT); + InitWindow(screenWidth, screenHeight, "Path finder by Bensuperpc"); + + SetTargetFPS(targetFPS); + + float lacunarity = 1.6f; + float octaves = 6; + float gain = 3.5f; + float frequency = 1.7f; + float weighted_strength = 0.034f; + float multiplier = 118; + + Generator generator_2(-972960945); + generator_2.setLacunarity(lacunarity); + generator_2.setOctaves((uint32_t)octaves); + generator_2.setGain(gain); + generator_2.setFrequency(frequency); + generator_2.setWeightedStrength(0.0f); + generator_2.setMultiplier((uint32_t)multiplier); + + AStar::AStar pathFinder; + pathFinder.setWorldSize({mapWidth, mapHeight}); + pathFinder.setHeuristic(AStar::Heuristic::manhattan); + pathFinder.setDiagonalMovement(true); + + size_t pathSize = 0; + + std::vector heightmap; + + heightmap = generator_2.generate2dMeightmap(0, 0, 0, mapWidth, 0, mapHeight); + + std::vector blocks = std::vector(mapWidth * mapHeight, 0); + + uint64_t framesCounter = 0; + + bool needUpdate = true; + + while (!WindowShouldClose()) { + framesCounter++; + if (IsKeyPressed(KEY_S)) { + const std::string filename = "screenshot_" + std::to_string(std::chrono::system_clock::now().time_since_epoch().count()) + ".png"; + TakeScreenshot(filename.c_str()); + } + if (IsKeyPressed(KEY_R)) { + generator_2.randomizeSeed(); + needUpdate = true; + } + + if (framesCounter % (targetFPS / ImageUpdatePerSecond) == 0) { + if (needUpdate) { + needUpdate = false; + generator_2.setLacunarity(lacunarity); + generator_2.setOctaves((uint32_t)octaves); + generator_2.setGain(gain); + generator_2.setFrequency(frequency); + generator_2.setWeightedStrength(weighted_strength); + generator_2.setMultiplier((uint32_t)multiplier); + + pathFinder.clear(); + + heightmap = generator_2.generate2dMeightmap(0, 0, 0, screenWidth, 0, screenHeight); + + for (uint64_t x = 0; x < mapWidth; x++) { + for (uint64_t y = 0; y < mapHeight; y++) { + uint64_t index = x + y * mapWidth; + uint8_t value = static_cast(heightmap[index]); + + if (value < 128) { + blocks[index] = 0; + } else { + blocks[index] = 1; + pathFinder.addObstacle({static_cast(x), static_cast(y)}); + } + } + } + blocks[0] = 0; + pathFinder.removeObstacle({0, 0}); + blocks[mapWidth * mapHeight - 1] = 0; + pathFinder.removeObstacle({mapWidth - 1, mapHeight - 1}); + + pathFinder.setHeuristic(AStar::Heuristic::euclidean); + + std::function* node)> debugCurrentNode = [&](const AStar::Node* node) { + uint64_t index = node->pos.x + node->pos.y * mapWidth; + blocks[index] = 3; + }; + + pathFinder.setDebugCurrentNode(debugCurrentNode); + + std::function* node)> debugOpenNode = [&](const AStar::Node* node) { + uint64_t index = node->pos.x + node->pos.y * mapWidth; + blocks[index] = 4; + }; + + pathFinder.setDebugOpenNode(debugOpenNode); + + auto start1 = std::chrono::high_resolution_clock::now(); + auto path = pathFinder.findPath({0, 0}, {mapWidth - 1, mapHeight - 1}); + auto stop1 = std::chrono::high_resolution_clock::now(); + if (path.empty()) { + std::cout << "Path not found" << std::endl; + } + auto duration1 = std::chrono::duration_cast(stop1 - start1); + std::cout << "Path search: " << duration1.count() << " microseconds" << std::endl; + + pathSize = path.size(); + for (auto& i : path) { + uint64_t index = i.x + i.y * mapWidth; + blocks[index] = 2; + } + } + } + + ClearBackground(RAYWHITE); + BeginDrawing(); + + // Draw white if blocks[index] == 0 else black + int size = 10; + for (uint64_t x = 0; x < mapWidth; x++) { + for (uint64_t y = 0; y < mapHeight; y++) { + uint64_t index = x + y * mapWidth; + if (blocks[index] == 0) { + DrawRectangle(static_cast(x * size), static_cast(y * size), size, size, WHITE); + } else if (blocks[index] == 1) { + DrawRectangle(static_cast(x * size), static_cast(y * size), size, size, BLACK); + } else if (blocks[index] == 2) { + DrawRectangle(static_cast(x * size), static_cast(y * size), size, size, RED); + } else if (blocks[index] == 3) { + DrawRectangle(static_cast(x * size), static_cast(y * size), size, size, GREEN); + } else if (blocks[index] == 4) { + DrawRectangle(static_cast(x * size), static_cast(y * size), size, size, BLUE); + } else if (blocks[index] == 5) { + DrawRectangle(static_cast(x * size), static_cast(y * size), size, size, YELLOW); + } else if (blocks[index] == 6) { + DrawRectangle(static_cast(x * size), static_cast(y * size), size, size, ORANGE); + } else if (blocks[index] == 7) { + DrawRectangle(static_cast(x * size), static_cast(y * size), size, size, PURPLE); + } + } + } + + // display FPS + DrawRectangle(screenWidth - 90, 10, 80, 20, Fade(SKYBLUE, 0.95f)); + DrawText(TextFormat("FPS: %02d", GetFPS()), screenWidth - 80, 15, 15, DARKGRAY); + + DrawRectangle(0, 0, 275, 200, Fade(SKYBLUE, 0.95f)); + DrawRectangleLines(0, 0, 275, 200, BLUE); + GuiSlider((Rectangle){70, 10, 165, 20}, "Lacunarity", TextFormat("%2.3f", lacunarity), &lacunarity, -0.0f, 5.0f); + GuiSlider((Rectangle){70, 40, 165, 20}, "Octaves", TextFormat("%2.3f", octaves), &octaves, 1, 12); + GuiSlider((Rectangle){70, 70, 165, 20}, "Gain", TextFormat("%2.3f", gain), &gain, -0.0f, 16.0f); + GuiSlider((Rectangle){70, 100, 165, 20}, "Frequency", TextFormat("%2.3f", frequency), &frequency, -0.0f, 10.0f); + GuiSlider((Rectangle){70, 130, 165, 20}, "Weight", TextFormat("%2.3f", weighted_strength), &weighted_strength, -5.0f, 5.0f); + GuiSlider((Rectangle){70, 160, 165, 20}, "Multiplier", TextFormat("%2.3f", multiplier), &multiplier, 1, 512); + + // display info each color for each heuristic + DrawRectangle(0, 200, 275, 190, Fade(SKYBLUE, 0.95f)); + DrawRectangleLines(0, 200, 275, 190, BLUE); + + std::string pathText = "Path: " + std::to_string(pathSize); + DrawRectangle(10, 210, 20, 20, RED); + DrawText(pathText.c_str(), 40, 210, 20, DARKGRAY); + + std::string testedNodesText = "Tested nodes"; + DrawRectangle(10, 240, 20, 20, GREEN); + DrawText(testedNodesText.c_str(), 40, 240, 20, DARKGRAY); + + std::string openNodesText = "Open nodes"; + DrawRectangle(10, 270, 20, 20, BLUE); + DrawText(openNodesText.c_str(), 40, 270, 20, DARKGRAY); + + EndDrawing(); + } + + CloseWindow(); + + return 0; +}