From a577138a8052e7a3563f393376bd8fbd9e7b9bf5 Mon Sep 17 00:00:00 2001 From: Hussein Taher <6496177+Husenap@users.noreply.github.com> Date: Sat, 10 Jul 2021 14:42:47 +0200 Subject: [PATCH] rect packer (#1) * basic setup * Formatted files using clang-format * added basic unit test * fixed compilation errer * use gcc-10 * use gcc-10 * use gcc-10 * updated actions * updated actions * updated actions * updated actions * updated actions * updated actions * updated actions * implemented basic packing algorithm * removed ranges include * fixed compilation errors * updated readme * updated readme Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 6 +- CMakeLists.txt | 6 +- README.md | 25 +++++++- .../CMakeLists.txt | 18 ++++-- .../src/dubu_rect_pack/dubu_rect_pack.hpp | 5 ++ .../src/dubu_rect_pack/packer/Packer.cpp | 37 +++++++++++ .../src/dubu_rect_pack/packer/Packer.hpp | 17 +++++ .../src/dubu_rect_pack/packer/Space.cpp | 62 +++++++++++++++++++ .../src/dubu_rect_pack/packer/Space.hpp | 30 +++++++++ .../src/dubu_rect_pack/packer/Types.hpp | 23 +++++++ .../src/dubu_rect_pack/precompiled.hpp | 8 +++ .../CMakeLists.txt | 3 +- dubu_rect_pack_test/src/unit.cpp | 29 +++++++++ .../src/new_project_name/new_project_name.h | 0 .../src/new_project_name/precompiled.h | 0 new_project_name_test/src/unit.cpp | 5 -- scripts/build | 0 scripts/clean | 0 scripts/gen | 0 scripts/open | 0 scripts/test | 3 +- 21 files changed, 255 insertions(+), 22 deletions(-) rename {new_project_name => dubu_rect_pack}/CMakeLists.txt (60%) create mode 100644 dubu_rect_pack/src/dubu_rect_pack/dubu_rect_pack.hpp create mode 100644 dubu_rect_pack/src/dubu_rect_pack/packer/Packer.cpp create mode 100644 dubu_rect_pack/src/dubu_rect_pack/packer/Packer.hpp create mode 100644 dubu_rect_pack/src/dubu_rect_pack/packer/Space.cpp create mode 100644 dubu_rect_pack/src/dubu_rect_pack/packer/Space.hpp create mode 100644 dubu_rect_pack/src/dubu_rect_pack/packer/Types.hpp create mode 100644 dubu_rect_pack/src/dubu_rect_pack/precompiled.hpp rename {new_project_name_test => dubu_rect_pack_test}/CMakeLists.txt (81%) create mode 100644 dubu_rect_pack_test/src/unit.cpp delete mode 100644 new_project_name/src/new_project_name/new_project_name.h delete mode 100644 new_project_name/src/new_project_name/precompiled.h delete mode 100644 new_project_name_test/src/unit.cpp mode change 100644 => 100755 scripts/build mode change 100644 => 100755 scripts/clean mode change 100644 => 100755 scripts/gen mode change 100644 => 100755 scripts/open mode change 100644 => 100755 scripts/test diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5a0173..f5a61dd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,12 +17,12 @@ jobs: - name: Generate shell: bash - run: bash scripts/gen -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} + run: scripts/gen -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} - name: Build shell: bash - run: bash scripts/build + run: scripts/build - name: Test shell: bash - run: bash scripts/test + run: scripts/test \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 33f78ce..9db4f14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.16) -project("new_project_name" C CXX) +project("dubu_rect_pack" C CXX) include("cmake/prevent_in_source_builds.cmake") include("cmake/standard_project_setup.cmake") @@ -18,11 +18,11 @@ option(${PROJECT_NAME}_BUILD_TESTS "If the ${PROJECT_NAME} tests are built in addition to the ${PROJECT_NAME} library." ON) -add_subdirectory("new_project_name") +add_subdirectory("dubu_rect_pack") if(${${PROJECT_NAME}_BUILD_TESTS}) enable_testing() include(GoogleTest) include("thirdparty/googletest.cmake") - add_subdirectory("new_project_name_test") + add_subdirectory("dubu_rect_pack_test") endif() diff --git a/README.md b/README.md index 0ebfa49..a941fcf 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,24 @@ -[![Build](https://github.com/Husenap/cmake-project-template/actions/workflows/build.yml/badge.svg)](https://github.com/Husenap/cmake-project-template/actions/workflows/build.yml) +[![Build](https://github.com/Husenap/dubu-rect-pack/actions/workflows/build.yml/badge.svg)](https://github.com/Husenap/dubu-rect-pack/actions/workflows/build.yml) -# cmake-project-template +# dubu-rect-pack -Basic C++ project template using CMake. \ No newline at end of file +C++ library for packing rectangles + +## Algorithm + +* When packing a new rect: it tries to find the smallest area it fits into. +* When splitting: it prefers the split which yields the biggest area in one of the spaces. + +## Example Usage + +```cpp +#include + +int main() { + dubu::rect_pack::Packer packer(256, 256); // create a space of 256x256 + packer.Pack({100, 100}); // Rect{0, 0, 100, 100} + packer.Pack({100, 100}); // Rect{100, 0, 100, 100} + packer.Pack({1000, 100}); // std::nullopt, doesn't fit + packer.Pack({56, 100}); // Rect{200, 0, 56, 100} +} +``` \ No newline at end of file diff --git a/new_project_name/CMakeLists.txt b/dubu_rect_pack/CMakeLists.txt similarity index 60% rename from new_project_name/CMakeLists.txt rename to dubu_rect_pack/CMakeLists.txt index 2f9b59b..5914432 100644 --- a/new_project_name/CMakeLists.txt +++ b/dubu_rect_pack/CMakeLists.txt @@ -1,14 +1,22 @@ -set(target_name "new_project_name") +set(target_name "dubu_rect_pack") set(src_precompiled - "src/new_project_name/precompiled.h") + "src/dubu_rect_pack/precompiled.hpp") -set(src_new_project_name - "src/new_project_name/new_project_name.h") +set(src_packer + "src/dubu_rect_pack/packer/Packer.cpp" + "src/dubu_rect_pack/packer/Packer.hpp" + "src/dubu_rect_pack/packer/Space.cpp" + "src/dubu_rect_pack/packer/Space.hpp" + "src/dubu_rect_pack/packer/Types.hpp") + +set(src_dubu_rect_pack + "src/dubu_rect_pack/dubu_rect_pack.hpp") set(src_files ${src_precompiled} - ${src_new_project_name}) + ${src_packer} + ${src_dubu_rect_pack}) # Project add_library(${target_name} STATIC ${src_files}) diff --git a/dubu_rect_pack/src/dubu_rect_pack/dubu_rect_pack.hpp b/dubu_rect_pack/src/dubu_rect_pack/dubu_rect_pack.hpp new file mode 100644 index 0000000..8060bab --- /dev/null +++ b/dubu_rect_pack/src/dubu_rect_pack/dubu_rect_pack.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include "packer/Packer.hpp" +#include "packer/Space.hpp" +#include "packer/Types.hpp" \ No newline at end of file diff --git a/dubu_rect_pack/src/dubu_rect_pack/packer/Packer.cpp b/dubu_rect_pack/src/dubu_rect_pack/packer/Packer.cpp new file mode 100644 index 0000000..36c5219 --- /dev/null +++ b/dubu_rect_pack/src/dubu_rect_pack/packer/Packer.cpp @@ -0,0 +1,37 @@ +#include "Packer.hpp" + +namespace dubu::rect_pack { + +Packer::Packer(int width, int height) { + mSpaces.emplace_back(0, 0, width, height); +} + +std::optional Packer::Pack(Size rectangle) { + for (auto it = mSpaces.crbegin(); it != mSpaces.crend(); ++it) { + auto& space = *it; + + if (!space.CanFitRect(rectangle)) { + continue; + } + + const auto [occupied, newSpaces] = space.Split(rectangle); + + mSpaces.erase(it.base() - 1); + + std::move(std::begin(newSpaces), + std::end(newSpaces), + std::back_inserter(mSpaces)); + + std::sort(std::begin(mSpaces), + std::end(mSpaces), + [](const Space& lhs, const Space& rhs) { + return lhs.Area() > rhs.Area(); + }); + + return occupied; + } + + return std::nullopt; +} + +} // namespace dubu::rect_pack \ No newline at end of file diff --git a/dubu_rect_pack/src/dubu_rect_pack/packer/Packer.hpp b/dubu_rect_pack/src/dubu_rect_pack/packer/Packer.hpp new file mode 100644 index 0000000..f22c100 --- /dev/null +++ b/dubu_rect_pack/src/dubu_rect_pack/packer/Packer.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "Space.hpp" + +namespace dubu::rect_pack { + +class Packer { +public: + Packer(int width, int height); + + std::optional Pack(Size rectangle); + +private: + std::vector mSpaces; +}; + +} // namespace dubu::rect_pack \ No newline at end of file diff --git a/dubu_rect_pack/src/dubu_rect_pack/packer/Space.cpp b/dubu_rect_pack/src/dubu_rect_pack/packer/Space.cpp new file mode 100644 index 0000000..a78b993 --- /dev/null +++ b/dubu_rect_pack/src/dubu_rect_pack/packer/Space.cpp @@ -0,0 +1,62 @@ +#include "Space.hpp" + +namespace dubu::rect_pack { + +Space::Space(std::uint32_t left, + std::uint32_t top, + std::uint32_t width, + std::uint32_t height) + : mLeft(left) + , mTop(top) + , mWidth(width) + , mHeight(height) + , mArea(width * height) {} + +bool Space::CanFitRect(Size rectangle) const { + return (rectangle.width <= mWidth) && (rectangle.height <= mHeight); +} + +std::pair> Space::Split(Size rectangle) const { + assert(rectangle.width <= mWidth); + assert(rectangle.height <= mHeight); + + if (rectangle.width == mWidth && rectangle.height == mHeight) { + return {*this, {}}; + } else if (rectangle.width == mWidth) { + return {Space(mLeft, mTop, mWidth, rectangle.height), + {Space(mLeft, + mTop + rectangle.height, + mWidth, + mHeight - rectangle.height)}}; + } else if (rectangle.height == mHeight) { + return {Space(mLeft, mTop, rectangle.width, mHeight), + {Space(mLeft + rectangle.width, + mTop, + mWidth - rectangle.width, + mHeight)}}; + } else { + Space newSpace(mLeft, mTop, rectangle.width, rectangle.height); + Space bottomSpace( + mLeft, mTop + rectangle.height, mWidth, mHeight - rectangle.height); + Space rightSpace( + mLeft + rectangle.width, mTop, mWidth - rectangle.width, mHeight); + + if (bottomSpace.Area() >= rightSpace.Area()) { + return {newSpace, + {bottomSpace, + Space(mLeft + rectangle.width, + mTop, + mWidth - rectangle.width, + rectangle.height)}}; + } else { + return {newSpace, + {rightSpace, + Space(mLeft, + mTop + rectangle.height, + rectangle.width, + mHeight - rectangle.height)}}; + } + } +} + +} // namespace dubu::rect_pack \ No newline at end of file diff --git a/dubu_rect_pack/src/dubu_rect_pack/packer/Space.hpp b/dubu_rect_pack/src/dubu_rect_pack/packer/Space.hpp new file mode 100644 index 0000000..dc0f1b2 --- /dev/null +++ b/dubu_rect_pack/src/dubu_rect_pack/packer/Space.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "Types.hpp" + +namespace dubu::rect_pack { + +class Space { +public: + explicit Space(std::uint32_t left, + std::uint32_t top, + std::uint32_t width, + std::uint32_t height); + + bool CanFitRect(Size rectangle) const; + + std::pair> Split(Size rectangle) const; + + std::uint32_t Area() const { return mArea; } + + operator Rect() const { return Rect{mLeft, mTop, mWidth, mHeight}; } + +private: + std::uint32_t mLeft; + std::uint32_t mTop; + std::uint32_t mWidth; + std::uint32_t mHeight; + std::uint32_t mArea; +}; + +} // namespace dubu::rect_pack \ No newline at end of file diff --git a/dubu_rect_pack/src/dubu_rect_pack/packer/Types.hpp b/dubu_rect_pack/src/dubu_rect_pack/packer/Types.hpp new file mode 100644 index 0000000..f598362 --- /dev/null +++ b/dubu_rect_pack/src/dubu_rect_pack/packer/Types.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include + +namespace dubu::rect_pack { + +struct Size { + std::uint32_t width = {}; + std::uint32_t height = {}; +}; + +struct Rect { + std::uint32_t x = {}; + std::uint32_t y = {}; + std::uint32_t w = {}; + std::uint32_t h = {}; + + bool operator==(const Rect& rhs) const { + return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h; + } +}; + +} // namespace dubu::rect_pack \ No newline at end of file diff --git a/dubu_rect_pack/src/dubu_rect_pack/precompiled.hpp b/dubu_rect_pack/src/dubu_rect_pack/precompiled.hpp new file mode 100644 index 0000000..a5398d5 --- /dev/null +++ b/dubu_rect_pack/src/dubu_rect_pack/precompiled.hpp @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include +#include +#include +#include \ No newline at end of file diff --git a/new_project_name_test/CMakeLists.txt b/dubu_rect_pack_test/CMakeLists.txt similarity index 81% rename from new_project_name_test/CMakeLists.txt rename to dubu_rect_pack_test/CMakeLists.txt index c94b784..37eb3b8 100644 --- a/new_project_name_test/CMakeLists.txt +++ b/dubu_rect_pack_test/CMakeLists.txt @@ -1,4 +1,4 @@ -set(target_name "new_project_name_test") +set(target_name "dubu_rect_pack_test") set(src_files "src/unit.cpp") @@ -6,6 +6,7 @@ set(src_files add_executable(${target_name} ${src_files}) target_link_libraries(${target_name} + dubu_rect_pack gmock_main) set_target_properties(${target_name} PROPERTIES FOLDER ${${PROJECT_NAME}_FOLDER}/test) diff --git a/dubu_rect_pack_test/src/unit.cpp b/dubu_rect_pack_test/src/unit.cpp new file mode 100644 index 0000000..d04206a --- /dev/null +++ b/dubu_rect_pack_test/src/unit.cpp @@ -0,0 +1,29 @@ +#include +#include + +using namespace dubu::rect_pack; + +TEST(dubu_rect_pack, pack_single_rect) { + { + dubu::rect_pack::Packer packer(256, 256); + EXPECT_EQ(packer.Pack({256, 256}), (Rect{0, 0, 256, 256})); + } + { + dubu::rect_pack::Packer packer(256, 256); + EXPECT_EQ(packer.Pack({100, 100}), (Rect{0, 0, 100, 100})); + } + { + dubu::rect_pack::Packer packer(256, 256); + EXPECT_EQ(packer.Pack({512, 512}), std::nullopt); + } +} + +TEST(dubu_rect_pack, pack_multiple_rects) { + { + dubu::rect_pack::Packer packer(256, 256); + EXPECT_EQ(packer.Pack({100, 100}), (Rect{0, 0, 100, 100})); + EXPECT_EQ(packer.Pack({100, 100}), (Rect{100, 0, 100, 100})); + EXPECT_EQ(packer.Pack({1000, 100}), std::nullopt); + EXPECT_EQ(packer.Pack({56, 100}), (Rect{200, 0, 56, 100})); + } +} diff --git a/new_project_name/src/new_project_name/new_project_name.h b/new_project_name/src/new_project_name/new_project_name.h deleted file mode 100644 index e69de29..0000000 diff --git a/new_project_name/src/new_project_name/precompiled.h b/new_project_name/src/new_project_name/precompiled.h deleted file mode 100644 index e69de29..0000000 diff --git a/new_project_name_test/src/unit.cpp b/new_project_name_test/src/unit.cpp deleted file mode 100644 index 5d6d336..0000000 --- a/new_project_name_test/src/unit.cpp +++ /dev/null @@ -1,5 +0,0 @@ -#include - -TEST(new_project_name, example_test) { - EXPECT_EQ(2, 1 + 1); -} diff --git a/scripts/build b/scripts/build old mode 100644 new mode 100755 diff --git a/scripts/clean b/scripts/clean old mode 100644 new mode 100755 diff --git a/scripts/gen b/scripts/gen old mode 100644 new mode 100755 diff --git a/scripts/open b/scripts/open old mode 100644 new mode 100755 diff --git a/scripts/test b/scripts/test old mode 100644 new mode 100755 index 40573d0..0532b3c --- a/scripts/test +++ b/scripts/test @@ -1,6 +1,5 @@ #! /bin/bash bash $(dirname $0)/build -cd .build/ +pushd .build/ env CTEST_OUTPUT_ON_FAILURE=1 ctest . "$@" -cd .. \ No newline at end of file