Skip to content

Commit

Permalink
rect packer (#1)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
Husenap and github-actions[bot] authored Jul 10, 2021
1 parent 5630493 commit a577138
Show file tree
Hide file tree
Showing 21 changed files with 255 additions and 22 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
6 changes: 3 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -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()
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
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 <dubu-rect-pack/dubu-rect-pack.hpp>

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}
}
```
18 changes: 13 additions & 5 deletions new_project_name/CMakeLists.txt → dubu_rect_pack/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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})
Expand Down
5 changes: 5 additions & 0 deletions dubu_rect_pack/src/dubu_rect_pack/dubu_rect_pack.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#pragma once

#include "packer/Packer.hpp"
#include "packer/Space.hpp"
#include "packer/Types.hpp"
37 changes: 37 additions & 0 deletions dubu_rect_pack/src/dubu_rect_pack/packer/Packer.cpp
Original file line number Diff line number Diff line change
@@ -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<Rect> 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
17 changes: 17 additions & 0 deletions dubu_rect_pack/src/dubu_rect_pack/packer/Packer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#pragma once

#include "Space.hpp"

namespace dubu::rect_pack {

class Packer {
public:
Packer(int width, int height);

std::optional<Rect> Pack(Size rectangle);

private:
std::vector<Space> mSpaces;
};

} // namespace dubu::rect_pack
62 changes: 62 additions & 0 deletions dubu_rect_pack/src/dubu_rect_pack/packer/Space.cpp
Original file line number Diff line number Diff line change
@@ -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, std::vector<Space>> 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
30 changes: 30 additions & 0 deletions dubu_rect_pack/src/dubu_rect_pack/packer/Space.hpp
Original file line number Diff line number Diff line change
@@ -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<Space, std::vector<Space>> 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
23 changes: 23 additions & 0 deletions dubu_rect_pack/src/dubu_rect_pack/packer/Types.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

#include <cstdint>

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
8 changes: 8 additions & 0 deletions dubu_rect_pack/src/dubu_rect_pack/precompiled.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <iterator>
#include <optional>
#include <vector>
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
set(target_name "new_project_name_test")
set(target_name "dubu_rect_pack_test")

set(src_files
"src/unit.cpp")

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)
Expand Down
29 changes: 29 additions & 0 deletions dubu_rect_pack_test/src/unit.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#include <dubu_rect_pack/dubu_rect_pack.hpp>
#include <gtest/gtest.h>

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}));
}
}
Empty file.
Empty file.
5 changes: 0 additions & 5 deletions new_project_name_test/src/unit.cpp

This file was deleted.

Empty file modified scripts/build
100644 → 100755
Empty file.
Empty file modified scripts/clean
100644 → 100755
Empty file.
Empty file modified scripts/gen
100644 → 100755
Empty file.
Empty file modified scripts/open
100644 → 100755
Empty file.
3 changes: 1 addition & 2 deletions scripts/test
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
#! /bin/bash

bash $(dirname $0)/build
cd .build/
pushd .build/
env CTEST_OUTPUT_ON_FAILURE=1 ctest . "$@"
cd ..

0 comments on commit a577138

Please sign in to comment.