├── .gitattributes
├── .github
└── workflows
│ └── cmake-multi-platform.yml
├── .gitignore
├── .travis.yml
├── CMakeLists.txt
├── CMakePresets.json
├── LICENSE
├── README.md
├── example
├── CMakeLists.txt
└── main.cpp
├── images
├── atlas_big.png
├── atlas_big_color.png
├── atlas_small.png
├── atlas_small_color.png
├── atlas_tiny.png
├── atlas_tiny_color.png
└── diag01.png
├── src
├── CMakeLists.txt
└── rectpack2D
│ ├── best_bin_finder.h
│ ├── empty_space_allocators.h
│ ├── empty_spaces.h
│ ├── finders_interface.h
│ ├── insert_and_split.h
│ └── rect_structs.h
└── thoughts.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
--------------------------------------------------------------------------------
/.github/workflows/cmake-multi-platform.yml:
--------------------------------------------------------------------------------
1 | name: CMake on multiple platforms
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | jobs:
10 | build:
11 | runs-on: ${{ matrix.os }}
12 |
13 | strategy:
14 | fail-fast: false
15 |
16 | matrix:
17 | os: [ubuntu-latest, windows-latest]
18 | build_type: [Debug, Release]
19 | c_compiler: [gcc, clang, cl]
20 | include:
21 | - os: windows-latest
22 | c_compiler: cl
23 | cpp_compiler: cl
24 | - os: ubuntu-latest
25 | c_compiler: gcc
26 | cpp_compiler: g++
27 | - os: ubuntu-latest
28 | c_compiler: clang
29 | cpp_compiler: clang++
30 | exclude:
31 | - os: windows-latest
32 | c_compiler: gcc
33 | - os: windows-latest
34 | c_compiler: clang
35 | - os: ubuntu-latest
36 | c_compiler: cl
37 |
38 | steps:
39 | - uses: actions/checkout@v4
40 |
41 | - name: Set reusable strings
42 | # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file.
43 | id: strings
44 | shell: bash
45 | run: |
46 | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT"
47 |
48 | - name: Configure CMake
49 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
50 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
51 | run: >
52 | cmake -B ${{ steps.strings.outputs.build-output-dir }}
53 | -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}
54 | -DCMAKE_C_COMPILER=${{ matrix.c_compiler }}
55 | -DCMAKE_BUILD_TYPE=${{ matrix.build_type }}
56 | -DRECTPACK2D_BUILD_EXAMPLE=1
57 | -S ${{ github.workspace }}
58 |
59 | - name: Build
60 | # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
61 | run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }}
62 |
63 | # - name: Test
64 | # working-directory: ${{ steps.strings.outputs.build-output-dir }}
65 | # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator).
66 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail
67 | # run: ctest --build-config ${{ matrix.build_type }}
68 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /cmake-build-debug/
2 | .DS_Store
3 | .idea
4 | *.iml
5 | example/clang_commands.sh
6 | example/main
7 | example/main.cpp.o
8 | example/run.sh
9 | build
10 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | os: linux
2 | language: cpp
3 | sudo: required
4 | dist: trusty
5 | addons:
6 | apt:
7 | packages:
8 | - gdb
9 | - apport
10 | - gcc-7
11 | - g++-7
12 | - cmake
13 | - ninja-build
14 | sources:
15 | - ubuntu-toolchain-r-test
16 |
17 | matrix:
18 | include:
19 | - env: c_compiler=gcc-7 cxx_compiler=g++-7 config=Debug
20 | compiler: gcc
21 | - env: c_compiler=gcc-7 cxx_compiler=g++-7 config=Release
22 | compiler: gcc
23 |
24 | before_install:
25 | - ulimit -c
26 | - ulimit -a -S
27 | - ulimit -a -H
28 | - cat /proc/sys/kernel/core_pattern
29 | - cat /etc/default/apport
30 | - service --status-all || true
31 | - initctl list || true
32 |
33 | script:
34 | - ${c_compiler} -v && ${cxx_compiler} -v && cmake --version && ninja --version
35 | - export CC=${c_compiler}
36 | - export CXX=${cxx_compiler}
37 | - cmake/build_example.sh ${config} ${c_compiler} ${cxx_compiler}
38 | - ulimit -c unlimited -S
39 | - pushd build/current
40 | - ninja run -k 0
41 | - ls -l
42 | - popd
43 | - ls -l example
44 | - cmake/print_bt_if_core_found.sh example/core
45 |
46 | notfications:
47 | email: false
48 |
--------------------------------------------------------------------------------
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.20.0)
2 |
3 | project(rectpack2D)
4 |
5 | add_subdirectory(src)
6 | add_library(rectpack2D::rectpack2D ALIAS rectpack2D)
7 |
8 | option(RECTPACK2D_BUILD_EXAMPLE "Build example for rectpack2D" OFF)
9 |
10 | if(RECTPACK2D_BUILD_EXAMPLE)
11 | add_subdirectory(example)
12 | endif()
--------------------------------------------------------------------------------
/CMakePresets.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 4,
3 | "cmakeMinimumRequired": {
4 | "major": 3,
5 | "minor": 20
6 | },
7 | "configurePresets": [
8 | {
9 | "name": "linux_binary_env",
10 | "hidden": true,
11 | "binaryDir": "${sourceDir}/build/${presetName}",
12 | "generator": "Ninja Multi-Config",
13 | "cacheVariables" : {
14 | "RECTPACK2D_BUILD_EXAMPLE" : true
15 | }
16 | },
17 | {
18 | "name": "windows_binary_env",
19 | "hidden": true,
20 | "binaryDir": "${sourceDir}/build/${presetName}",
21 | "generator": "Visual Studio",
22 | "condition": {
23 | "type": "equals",
24 | "lhs": "${hostSystemName}",
25 | "rhs": "Windows"
26 | },
27 | "cacheVariables" : {
28 | "RECTPACK2D_BUILD_EXAMPLE" : true
29 | }
30 | },
31 | {
32 | "inherits": [ "linux_binary_env" ],
33 | "name" : "linux"
34 | },
35 | {
36 | "inherits": [ "windows_binary_env" ],
37 | "name" : "windows"
38 | }
39 | ],
40 | "buildPresets": [
41 | {
42 | "name": "linux-debug",
43 | "configurePreset": "linux",
44 | "configuration": "Debug"
45 | },
46 | {
47 | "name": "linux-release",
48 | "configurePreset": "linux",
49 | "configuration": "Release"
50 | },
51 | {
52 | "name": "windows-debug",
53 | "configurePreset": "windows",
54 | "configuration": "Debug"
55 | },
56 | {
57 | "name": "windows-release",
58 | "configurePreset": "windows",
59 | "configuration": "Release"
60 | }
61 | ]
62 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Patryk Czachurski
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # rectpack2D
5 |
6 | [](https://github.com/TeamHypersomnia/rectpack2D/actions/workflows/cmake-multi-platform.yml)
7 |
8 | **Used in [Assassin's Creed: Valhalla](https://www.youtube.com/watch?v=2KnjDL4DnwM&t=2382s)!**
9 |
10 | **Used by [Skydio](https://pages.skydio.com/rs/784-TUF-591/images/Open%20Source%20Software%20Notice%20v0.2.html), one of the top drone manufacturers!**
11 |
12 | **[2 scientific references](https://scholar.google.com/scholar?hl=en&as_sdt=0%2C5&q=teamhypersomnia&btnG=)!**
13 |
14 |
15 |
16 |
17 | A header-only 2D rectangle packing library written in modern C++.
18 | This is a refactored and **highly optimized** version of the [original library](https://github.com/TeamHypersomnia/rectpack2D/tree/legacy).
19 |
20 | It was originally developed for the needs of [Hypersomnia](https://github.com/TeamHypersomnia/Hypersomnia), a free and open-source multiplayer shooter.
21 |
22 | 
23 |
24 | ## Table of contents
25 |
26 | - [Benchmarks](#benchmarks)
27 | - [Usage](#usage)
28 | - [Building the example](#building-the-example)
29 | * [Windows](#windows)
30 | * [Linux](#linux)
31 | - [Algorithm](#algorithm)
32 | * [Insertion algorithm](#insertion-algorithm)
33 | * [Additional heuristics](#additional-heuristics)
34 |
35 | ## Benchmarks
36 |
37 | Tests were conducted on a ``Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz``.
38 | The binary was built with ``clang 6.0.0``, using an -03 switch.
39 |
40 | ### Arbitrary game sprites: 582 subjects.
41 |
42 | **Runtime: 0.8 ms**
43 | **Wasted pixels: 10982 (0.24% - equivalent of a 105 x 105 square)**
44 |
45 | Output (1896 x 2382):
46 |
47 | 
48 |
49 | In color:
50 | (black is wasted space)
51 |
52 | 
53 |
54 | ### Arbitrary game sprites + Japanese glyphs: 3264 subjects.
55 |
56 | **Runtime: 4 ms**
57 | **Wasted pixels: 15538 (0.31% - equivalent of a 125 x 125 square)**
58 |
59 | Output (2116 x 2382):
60 |
61 | 
62 |
63 | In color:
64 | (black is wasted space)
65 |
66 | 
67 |
68 |
69 | ### Japanese glyphs + some GUI sprites: 3122 subjects.
70 |
71 | **Runtime: 3.5 - 7 ms**
72 | **Wasted pixels: 9288 (1.23% - equivalent of a 96 x 96 square)**
73 |
74 | Output (866 x 871):
75 |
76 | 
77 |
78 | In color:
79 | (black is wasted space)
80 |
81 | 
82 |
83 | ## Usage
84 |
85 | This is a header-only library.
86 | Just include the ``src/finders_interface.h`` and you should be good to go.
87 |
88 | For an example use, see ``example/main.cpp``.
89 |
90 | ## Building the example
91 |
92 | ### Windows
93 |
94 | From the repository's folder, run:
95 |
96 | ```bash
97 | mkdir build
98 | cd build
99 | cmake -G "Visual Studio 15 2017 Win64" ..
100 | ````
101 |
102 | Then just build the generated ``.sln`` file using the newest Visual Studio Preview.
103 |
104 | ### Linux
105 |
106 | From the repository's folder, run:
107 |
108 | ```bash
109 | cmake/build_example.sh Release gcc g++
110 | cd build/current
111 | ninja run
112 | ````
113 |
114 | Or, if you want to use clang, run:
115 |
116 | ```bash
117 | cmake/build_example.sh Release clang clang++
118 | cd build/current
119 | ninja run
120 | ````
121 |
122 | ## Algorithm
123 |
124 | ### Insertion algorithm
125 |
126 | The library started as an implementation of this algorithm:
127 |
128 | http://blackpawn.com/texts/lightmaps/default.html
129 |
130 | The current version somewhat derives from the concept described there -
131 | however, it uses just a **vector of empty spaces, instead of a tree** - this turned out to be a performance breakthrough.
132 |
133 | Given
134 |
135 | ```cpp
136 | struct rect_xywh {
137 | int x;
138 | int y;
139 | int w;
140 | int h;
141 | };
142 | ````
143 |
144 | Let us create a vector and call it empty_spaces.
145 |
146 | ```cpp
147 | std::vector empty_spaces;
148 | ````
149 |
150 | Given a user-specified initial bin, which is a square of some size S, we initialize the first empty space.
151 |
152 | ```cpp
153 | empty_spaces.push_back(rect_xywh(0, 0, S, S));
154 | ````
155 |
156 | Now, we'd like to insert the first image rectangle.
157 |
158 | To do this, we iterate the vector of empty spaces **backwards** and look for an empty space into which the image can fit.
159 | For now, we only have the S x S square: let's save the index of this candidate empty space,
160 | which is ``candidate_space_index = 0;``
161 |
162 | If our image is strictly smaller than the candidate space, we have something like this:
163 |
164 | 
165 |
166 | The blue is our image rectangle.
167 | We now calculate the gray rectangles labeled as "bigger split" and "smaller split",
168 | and save them like this:
169 |
170 | ```cpp
171 | // Erase the space that we've just inserted to, by swapping and popping.
172 | empty_spaces[candidate_space_index] = empty_spaces.back();
173 | empty_spaces.pop_back();
174 |
175 | // Save the resultant splits
176 | empty_spaces.push_back(bigger_split);
177 | empty_spaces.push_back(smaller_split);
178 | ````
179 |
180 | Notice that we push the smaller split *after* the bigger one.
181 | This is fairly important, because later the candidate images will encounter the smaller splits first,
182 | which will make better use of empty spaces overall.
183 |
184 | #### Corner cases:
185 |
186 | - If the image dimensions equal the dimensions of the candidate empty space (image fits exactly),
187 | - we just delete the space and create no splits.
188 | - If the image fits into the candidate empty space, but exactly one of the image dimensions equals the respective dimension of the candidate empty space (e.g. image = 20x40, candidate space = 30x40)
189 | - we delete the space and create a single split. In this case a 10x40 space.
190 |
191 | To see the complete, modular procedure for calculating the splits (along with the corner cases),
192 | [see this source](src/rectpack2D/insert_and_split.h).
193 |
194 | If the insertion fails, we also try the same procedure for a flipped image.
195 |
196 | ### Additional heuristics
197 |
198 | Now we know how to insert individual images into a bin of a given initial size S.
199 |
200 | 1. However, what S should be passed to the algorithm so that the rectangles end up wasting the least amount of space?
201 | - We perform a binary search.
202 | - We start with the size specified by the library user. Typically, it would be the maximum texture size allowed on a particular GPU.
203 | - If the packing was successful on the given bin size, decrease the size and try to pack again.
204 | - If the packing has failed on the given bin size - because some rectangles could not be further inserted - increase the size and try to pack again.
205 | - The search is aborted if we've successfully inserted into a bin and the dimensions of the next candidate would differ from the previous by less than ``discard_step``.
206 | - This variable exists so that we may easily trade accuracy for a speedup. ``discard_step = 1`` yields the highest accuracy. ``discard_step = 128`` will yield worse packings, but will be a lot faster, etc.
207 | - The search is performed first by decreasing the bin size by both width and height, keeping it in square shape.
208 | - Then we do the same, but only decreasing width.
209 | - Then we do the same, but only decreasing height.
210 | - The last two were a breakthrough in packing tightness. It turns out important to consider non-square bins.
211 | 2. In what order should the rectangles be inserted so that they pack the tightest?
212 | - By default, the library tries 5 decreasing orders:
213 | - By area.
214 | - By perimeter.
215 | - By the bigger side.
216 | - By width.
217 | - By height.
218 |
--------------------------------------------------------------------------------
/example/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_executable(rectpack2D-example)
2 |
3 | target_sources(
4 | rectpack2D-example
5 | PUBLIC
6 | main.cpp
7 | )
8 |
9 | target_link_libraries(
10 | rectpack2D-example
11 | PUBLIC
12 | rectpack2D::rectpack2D
13 | )
14 |
15 | # Set C++ standard to 17
16 | set_property(
17 | TARGET rectpack2D-example
18 | PROPERTY
19 | CXX_STANDARD 17
20 | )
21 |
22 | set(RESULT_EXE_WORKING_DIR ${CMAKE_CURRENT_SOURCE_DIR})
23 |
24 | # Enable LTO
25 | include(CheckIPOSupported)
26 | check_ipo_supported(RESULT IPO_SUPPORTED OUTPUT output)
27 |
28 | if(IPO_SUPPORTED)
29 | set_target_properties(
30 | rectpack2D-example
31 | PROPERTIES
32 | INTERPROCEDURAL_OPTIMIZATION TRUE
33 | )
34 | endif()
35 |
36 | if(MSVC)
37 | # Set executable working directory to current folder if debugging in vs
38 | set_target_properties(
39 | rectpack2D-example
40 | PROPERTIES
41 | VS_DEBUGGER_WORKING_DIRECTORY ${RESULT_EXE_WORKING_DIR}
42 | )
43 |
44 | # Set executable as a startup project
45 | set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT rectpack2D-example)
46 |
47 | target_compile_options(
48 | rectpack2D-example
49 | PUBLIC
50 | /permissive-
51 | )
52 | endif()
53 |
54 | if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
55 | target_compile_options(
56 | rectpack2D-example
57 | PUBLIC
58 | -Wall
59 | -Werror
60 | -Wextra
61 | -ftemplate-backtrace-limit=0
62 | )
63 | endif()
64 |
--------------------------------------------------------------------------------
/example/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 |
4 | /* For description of the algorithm, please see the README.md */
5 |
6 | using namespace rectpack2D;
7 |
8 | int main() {
9 | constexpr bool allow_flip = true;
10 | const auto runtime_flipping_mode = flipping_option::ENABLED;
11 |
12 | /*
13 | Here, we choose the "empty_spaces" class that the algorithm will use from now on.
14 |
15 | The first template argument is a bool which determines
16 | if the algorithm will try to flip rectangles to better fit them.
17 |
18 | The second argument is optional and specifies an allocator for the empty spaces.
19 | The default one just uses a vector to store the spaces.
20 | You can also pass a "static_empty_spaces<10000>" which will allocate 10000 spaces on the stack,
21 | possibly improving performance.
22 | */
23 |
24 | using spaces_type = rectpack2D::empty_spaces;
25 |
26 | /*
27 | rect_xywh or rect_xywhf (see src/rect_structs.h),
28 | depending on the value of allow_flip.
29 | */
30 |
31 | using rect_type = output_rect_t;
32 |
33 | /*
34 | Note:
35 |
36 | The multiple-bin functionality was removed.
37 | This means that it is now up to you what is to be done with unsuccessful insertions.
38 | You may initialize another search when this happens.
39 | */
40 |
41 | auto report_successful = [](rect_type&) {
42 | return callback_result::CONTINUE_PACKING;
43 | };
44 |
45 | auto report_unsuccessful = [](rect_type&) {
46 | return callback_result::ABORT_PACKING;
47 | };
48 |
49 | /*
50 | Initial size for the bin, from which the search begins.
51 | The result can only be smaller - if it cannot, the algorithm will gracefully fail.
52 | */
53 |
54 | const auto max_side = 1000;
55 |
56 | /*
57 | The search stops when the bin was successfully inserted into,
58 | AND the next candidate bin size differs from the last successful one by *less* then discard_step.
59 |
60 | The best possible granuarity is achieved with discard_step = 1.
61 | If you pass a negative discard_step, the algoritm will search with even more granularity -
62 | E.g. with discard_step = -4, the algoritm will behave as if you passed discard_step = 1,
63 | but it will make as many as 4 attempts to optimize bins down to the single pixel.
64 |
65 | Since discard_step = 0 does not make sense, the algoritm will automatically treat this case
66 | as if it were passed a discard_step = 1.
67 |
68 | For common applications, a discard_step = 1 or even discard_step = 128
69 | should yield really good packings while being very performant.
70 | If you are dealing with very small rectangles specifically,
71 | it might be a good idea to make this value negative.
72 |
73 | See the algorithm section of README for more information.
74 | */
75 |
76 | const auto discard_step = -4;
77 |
78 | /*
79 | Create some arbitrary rectangles.
80 | Every subsequent call to the packer library will only read the widths and heights that we now specify,
81 | and always overwrite the x and y coordinates with calculated results.
82 | */
83 |
84 | std::vector rectangles;
85 |
86 | rectangles.emplace_back(rect_xywh(0, 0, 20, 40));
87 | rectangles.emplace_back(rect_xywh(0, 0, 120, 40));
88 | rectangles.emplace_back(rect_xywh(0, 0, 85, 59));
89 | rectangles.emplace_back(rect_xywh(0, 0, 199, 380));
90 | rectangles.emplace_back(rect_xywh(0, 0, 85, 875));
91 |
92 | auto report_result = [&rectangles](const rect_wh& result_size) {
93 | std::cout << "Resultant bin: " << result_size.w << " " << result_size.h << std::endl;
94 |
95 | for (const auto& r : rectangles) {
96 | std::cout << r.x << " " << r.y << " " << r.w << " " << r.h << std::endl;
97 | }
98 | };
99 |
100 | {
101 | /*
102 | Example 1: Find best packing with default orders.
103 |
104 | If you pass no comparators whatsoever,
105 | the standard collection of 6 orders:
106 | by area, by perimeter, by bigger side, by width, by height and by "pathological multiplier"
107 | - will be passed by default.
108 | */
109 |
110 | const auto result_size = find_best_packing(
111 | rectangles,
112 | make_finder_input(
113 | max_side,
114 | discard_step,
115 | report_successful,
116 | report_unsuccessful,
117 | runtime_flipping_mode
118 | )
119 | );
120 |
121 | report_result(result_size);
122 | }
123 |
124 | {
125 | /* Example 2: Find best packing using all provided custom rectangle orders. */
126 |
127 | using rect_ptr = rect_type*;
128 |
129 | auto my_custom_order_1 = [](const rect_ptr a, const rect_ptr b) {
130 | return a->get_wh().area() > b->get_wh().area();
131 | };
132 |
133 | auto my_custom_order_2 = [](const rect_ptr a, const rect_ptr b) {
134 | return a->get_wh().perimeter() < b->get_wh().perimeter();
135 | };
136 |
137 | const auto result_size = find_best_packing(
138 | rectangles,
139 | make_finder_input(
140 | max_side,
141 | discard_step,
142 | report_successful,
143 | report_unsuccessful,
144 | runtime_flipping_mode
145 | ),
146 |
147 | my_custom_order_1,
148 | my_custom_order_2
149 | );
150 |
151 | report_result(result_size);
152 | }
153 |
154 | {
155 | /* Example 3: Find best packing exactly in the order of provided input. */
156 |
157 | const auto result_size = find_best_packing_dont_sort(
158 | rectangles,
159 | make_finder_input(
160 | max_side,
161 | discard_step,
162 | report_successful,
163 | report_unsuccessful,
164 | runtime_flipping_mode
165 | )
166 | );
167 |
168 | report_result(result_size);
169 | }
170 |
171 | {
172 | /* Example 4: Manually perform insertions. This way you can try your own best-bin finding logic. */
173 |
174 | auto packing_root = spaces_type({ max_side, max_side });
175 | packing_root.flipping_mode = runtime_flipping_mode;
176 |
177 | for (auto& r : rectangles) {
178 | if (const auto inserted_rectangle = packing_root.insert(std::as_const(r).get_wh())) {
179 | r = *inserted_rectangle;
180 | }
181 | else {
182 | std::cout << "Failed to insert a rectangle." << std::endl;
183 | break;
184 | }
185 | }
186 |
187 | const auto result_size = packing_root.get_rects_aabb();
188 |
189 | report_result(result_size);
190 | }
191 |
192 | return 0;
193 | }
194 |
--------------------------------------------------------------------------------
/images/atlas_big.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamHypersomnia/rectpack2D/3344bf5f39db1a9fb45fd82bfc85c3a5be045e3b/images/atlas_big.png
--------------------------------------------------------------------------------
/images/atlas_big_color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamHypersomnia/rectpack2D/3344bf5f39db1a9fb45fd82bfc85c3a5be045e3b/images/atlas_big_color.png
--------------------------------------------------------------------------------
/images/atlas_small.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamHypersomnia/rectpack2D/3344bf5f39db1a9fb45fd82bfc85c3a5be045e3b/images/atlas_small.png
--------------------------------------------------------------------------------
/images/atlas_small_color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamHypersomnia/rectpack2D/3344bf5f39db1a9fb45fd82bfc85c3a5be045e3b/images/atlas_small_color.png
--------------------------------------------------------------------------------
/images/atlas_tiny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamHypersomnia/rectpack2D/3344bf5f39db1a9fb45fd82bfc85c3a5be045e3b/images/atlas_tiny.png
--------------------------------------------------------------------------------
/images/atlas_tiny_color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamHypersomnia/rectpack2D/3344bf5f39db1a9fb45fd82bfc85c3a5be045e3b/images/atlas_tiny_color.png
--------------------------------------------------------------------------------
/images/diag01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TeamHypersomnia/rectpack2D/3344bf5f39db1a9fb45fd82bfc85c3a5be045e3b/images/diag01.png
--------------------------------------------------------------------------------
/src/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | add_library(rectpack2D INTERFACE)
2 |
3 | target_include_directories(
4 | rectpack2D
5 | INTERFACE
6 | ${CMAKE_CURRENT_SOURCE_DIR}/
7 | )
--------------------------------------------------------------------------------
/src/rectpack2D/best_bin_finder.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 | #include "rect_structs.h"
5 |
6 | namespace rectpack2D {
7 | enum class callback_result {
8 | ABORT_PACKING,
9 | CONTINUE_PACKING
10 | };
11 |
12 | template
13 | auto& dereference(T& r) {
14 | /*
15 | This will allow us to pass orderings that consist of pointers,
16 | as well as ones that are just plain objects in a vector.
17 | */
18 |
19 | if constexpr(std::is_pointer_v) {
20 | return *r;
21 | }
22 | else {
23 | return r;
24 | }
25 | };
26 |
27 | /*
28 | This function will do a binary search on viable bin sizes,
29 | starting from the biggest one: starting_bin.
30 |
31 | The search stops when the bin was successfully inserted into,
32 | AND the bin size to be tried next differs in size from the last viable one by *less* then discard_step.
33 |
34 | If we could not insert all input rectangles into a bin even as big as the starting_bin - the search fails.
35 | In this case, we return the amount of space (total_area_type) inserted in total.
36 |
37 | If we've found a viable bin that is smaller or equal to starting_bin, the search succeeds.
38 | In this case, we return the viable bin (rect_wh).
39 | */
40 |
41 | enum class bin_dimension {
42 | BOTH,
43 | WIDTH,
44 | HEIGHT
45 | };
46 |
47 | template
48 | std::variant best_packing_for_ordering_impl(
49 | empty_spaces_type& root,
50 | O ordering,
51 | const rect_wh starting_bin,
52 | int discard_step,
53 | const bin_dimension tried_dimension
54 | ) {
55 | auto candidate_bin = starting_bin;
56 | int tries_before_discarding = 0;
57 |
58 | if (discard_step <= 0)
59 | {
60 | tries_before_discarding = -discard_step;
61 | discard_step = 1;
62 | }
63 |
64 | //std::cout << "best_packing_for_ordering_impl dim: " << int(tried_dimension) << " w: " << starting_bin.w << " h: " << starting_bin.h << std::endl;
65 |
66 | int starting_step = 0;
67 |
68 | if (tried_dimension == bin_dimension::BOTH) {
69 | candidate_bin.w /= 2;
70 | candidate_bin.h /= 2;
71 |
72 | starting_step = candidate_bin.w / 2;
73 | }
74 | else if (tried_dimension == bin_dimension::WIDTH) {
75 | candidate_bin.w /= 2;
76 | starting_step = candidate_bin.w / 2;
77 | }
78 | else {
79 | candidate_bin.h /= 2;
80 | starting_step = candidate_bin.h / 2;
81 | }
82 |
83 | for (int step = starting_step; ; step = std::max(1, step / 2)) {
84 | //std::cout << "candidate: " << candidate_bin.w << "x" << candidate_bin.h << std::endl;
85 |
86 | root.reset(candidate_bin);
87 |
88 | int total_inserted_area = 0;
89 |
90 | const bool all_inserted = [&]() {
91 | for (const auto& r : ordering) {
92 | const auto& rect = dereference(r);
93 |
94 | if (root.insert(rect.get_wh())) {
95 | total_inserted_area += rect.area();
96 | }
97 | else {
98 | return false;
99 | }
100 | }
101 |
102 | return true;
103 | }();
104 |
105 | if (all_inserted) {
106 | /* Attempt was successful. Try with a smaller bin. */
107 |
108 | if (step <= discard_step) {
109 | if (tries_before_discarding > 0)
110 | {
111 | tries_before_discarding--;
112 | }
113 | else
114 | {
115 | return candidate_bin;
116 | }
117 | }
118 |
119 | if (tried_dimension == bin_dimension::BOTH) {
120 | candidate_bin.w -= step;
121 | candidate_bin.h -= step;
122 | }
123 | else if (tried_dimension == bin_dimension::WIDTH) {
124 | candidate_bin.w -= step;
125 | }
126 | else {
127 | candidate_bin.h -= step;
128 | }
129 |
130 | root.reset(candidate_bin);
131 | }
132 | else {
133 | /* Attempt ended with failure. Try with a bigger bin. */
134 |
135 | if (tried_dimension == bin_dimension::BOTH) {
136 | candidate_bin.w += step;
137 | candidate_bin.h += step;
138 |
139 | if (candidate_bin.area() > starting_bin.area()) {
140 | return total_inserted_area;
141 | }
142 | }
143 | else if (tried_dimension == bin_dimension::WIDTH) {
144 | candidate_bin.w += step;
145 |
146 | if (candidate_bin.w > starting_bin.w) {
147 | return total_inserted_area;
148 | }
149 | }
150 | else {
151 | candidate_bin.h += step;
152 |
153 | if (candidate_bin.h > starting_bin.h) {
154 | return total_inserted_area;
155 | }
156 | }
157 | }
158 | }
159 | }
160 |
161 | template
162 | std::variant best_packing_for_ordering(
163 | empty_spaces_type& root,
164 | O&& ordering,
165 | const rect_wh starting_bin,
166 | const int discard_step
167 | ) {
168 | const auto try_pack = [&](
169 | const bin_dimension tried_dimension,
170 | const rect_wh starting_bin
171 | ) {
172 | return best_packing_for_ordering_impl(
173 | root,
174 | std::forward(ordering),
175 | starting_bin,
176 | discard_step,
177 | tried_dimension
178 | );
179 | };
180 |
181 | const auto best_result = try_pack(bin_dimension::BOTH, starting_bin);
182 |
183 | if (const auto failed = std::get_if(&best_result)) {
184 | return *failed;
185 | }
186 |
187 | auto best_bin = std::get(best_result);
188 |
189 | auto trial = [&](const bin_dimension tried_dimension) {
190 | const auto trial = try_pack(tried_dimension, best_bin);
191 |
192 | if (const auto better = std::get_if(&trial)) {
193 | best_bin = *better;
194 | }
195 | };
196 |
197 | trial(bin_dimension::WIDTH);
198 | trial(bin_dimension::HEIGHT);
199 |
200 | return best_bin;
201 | }
202 |
203 | /*
204 | This function will try to find the best bin size among the ones generated by all provided rectangle orders.
205 | Only the best order will have results written to.
206 |
207 | The function reports which of the rectangles did and did not fit in the end.
208 | */
209 |
210 | template <
211 | class empty_spaces_type,
212 | class OrderType,
213 | class F,
214 | class I
215 | >
216 | rect_wh find_best_packing_impl(F for_each_order, const I input) {
217 | const auto max_bin = rect_wh(input.max_bin_side, input.max_bin_side);
218 |
219 | OrderType* best_order = nullptr;
220 |
221 | int best_total_inserted = -1;
222 | auto best_bin = max_bin;
223 |
224 | /*
225 | The root node is re-used on the TLS.
226 | It is always reset before any packing attempt.
227 | */
228 |
229 | thread_local empty_spaces_type root = rect_wh();
230 | root.flipping_mode = input.flipping_mode;
231 |
232 | for_each_order ([&](OrderType& current_order) {
233 | const auto packing = best_packing_for_ordering(
234 | root,
235 | current_order,
236 | max_bin,
237 | input.discard_step
238 | );
239 |
240 | if (const auto total_inserted = std::get_if(&packing)) {
241 | /*
242 | Track which function inserts the most area in total,
243 | just in case that all orders will fail to fit into the largest allowed bin.
244 | */
245 | if (best_order == nullptr) {
246 | if (*total_inserted > best_total_inserted) {
247 | best_order = std::addressof(current_order);
248 | best_total_inserted = *total_inserted;
249 | }
250 | }
251 | }
252 | else if (const auto result_bin = std::get_if(&packing)) {
253 | /* Save the function if it performed the best. */
254 | if (result_bin->area() <= best_bin.area()) {
255 | best_order = std::addressof(current_order);
256 | best_bin = *result_bin;
257 | }
258 | }
259 | });
260 |
261 | {
262 | assert(best_order != nullptr);
263 |
264 | root.reset(best_bin);
265 |
266 | for (auto& rr : *best_order) {
267 | auto& rect = dereference(rr);
268 |
269 | if (const auto ret = root.insert(rect.get_wh())) {
270 | rect = *ret;
271 |
272 | if (callback_result::ABORT_PACKING == input.handle_successful_insertion(rect)) {
273 | break;
274 | }
275 | }
276 | else {
277 | if (callback_result::ABORT_PACKING == input.handle_unsuccessful_insertion(rect)) {
278 | break;
279 | }
280 | }
281 | }
282 |
283 | return root.get_rects_aabb();
284 | }
285 | }
286 | }
287 |
--------------------------------------------------------------------------------
/src/rectpack2D/empty_space_allocators.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 | #include
5 |
6 | #include "rect_structs.h"
7 |
8 | namespace rectpack2D {
9 | class default_empty_spaces {
10 | std::vector empty_spaces;
11 |
12 | public:
13 | void remove(const int i) {
14 | empty_spaces[i] = empty_spaces.back();
15 | empty_spaces.pop_back();
16 | }
17 |
18 | bool add(const space_rect r) {
19 | empty_spaces.emplace_back(r);
20 | return true;
21 | }
22 |
23 | auto get_count() const {
24 | return empty_spaces.size();
25 | }
26 |
27 | void reset() {
28 | empty_spaces.clear();
29 | }
30 |
31 | const auto& get(const int i) {
32 | return empty_spaces[i];
33 | }
34 | };
35 |
36 | template
37 | class static_empty_spaces {
38 | int count_spaces = 0;
39 | std::array empty_spaces;
40 |
41 | public:
42 | void remove(const int i) {
43 | empty_spaces[i] = empty_spaces[count_spaces - 1];
44 | --count_spaces;
45 | }
46 |
47 | bool add(const space_rect r) {
48 | if (count_spaces < static_cast(empty_spaces.size())) {
49 | empty_spaces[count_spaces] = r;
50 | ++count_spaces;
51 |
52 | return true;
53 | }
54 |
55 | return false;
56 | }
57 |
58 | auto get_count() const {
59 | return count_spaces;
60 | }
61 |
62 | void reset() {
63 | count_spaces = 0;
64 | }
65 |
66 | const auto& get(const int i) {
67 | return empty_spaces[i];
68 | }
69 | };
70 | }
71 |
--------------------------------------------------------------------------------
/src/rectpack2D/empty_spaces.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "insert_and_split.h"
3 |
4 | namespace rectpack2D {
5 | enum class flipping_option {
6 | DISABLED,
7 | ENABLED
8 | };
9 |
10 | class default_empty_spaces;
11 |
12 | template
13 | class empty_spaces {
14 | rect_wh current_aabb;
15 | empty_spaces_provider spaces;
16 |
17 | /* MSVC fix for non-conformant if constexpr implementation */
18 |
19 | static auto make_output_rect(const int x, const int y, const int w, const int h) {
20 | return rect_xywh(x, y, w, h);
21 | }
22 |
23 | static auto make_output_rect(const int x, const int y, const int w, const int h, const bool flipped) {
24 | return rect_xywhf(x, y, w, h, flipped);
25 | }
26 |
27 | public:
28 | using output_rect_type = std::conditional_t;
29 |
30 | flipping_option flipping_mode = flipping_option::ENABLED;
31 |
32 | empty_spaces(const rect_wh& r) {
33 | reset(r);
34 | }
35 |
36 | void reset(const rect_wh& r) {
37 | current_aabb = {};
38 |
39 | spaces.reset();
40 | spaces.add(rect_xywh(0, 0, r.w, r.h));
41 | }
42 |
43 | template
44 | std::optional insert(const rect_wh image_rectangle, F report_candidate_empty_space) {
45 | for (int i = static_cast(spaces.get_count()) - 1; i >= 0; --i) {
46 | const auto candidate_space = spaces.get(i);
47 |
48 | report_candidate_empty_space(candidate_space);
49 |
50 | auto accept_result = [this, i, image_rectangle, candidate_space](
51 | const created_splits& splits,
52 | const bool flipping_necessary
53 | ) -> std::optional {
54 | spaces.remove(i);
55 |
56 | for (int s = 0; s < splits.count; ++s) {
57 | if (!spaces.add(splits.spaces[s])) {
58 | return std::nullopt;
59 | }
60 | }
61 |
62 | if constexpr(allow_flip) {
63 | const auto result = make_output_rect(
64 | candidate_space.x,
65 | candidate_space.y,
66 | image_rectangle.w,
67 | image_rectangle.h,
68 | flipping_necessary
69 | );
70 |
71 | current_aabb.expand_with(result);
72 | return result;
73 | }
74 | else if constexpr(!allow_flip) {
75 | (void)flipping_necessary;
76 |
77 | const auto result = make_output_rect(
78 | candidate_space.x,
79 | candidate_space.y,
80 | image_rectangle.w,
81 | image_rectangle.h
82 | );
83 |
84 | current_aabb.expand_with(result);
85 | return result;
86 | }
87 | };
88 |
89 | auto try_to_insert = [&](const rect_wh& img) {
90 | return rectpack2D::insert_and_split(img, candidate_space);
91 | };
92 |
93 | if constexpr(!allow_flip) {
94 | if (const auto normal = try_to_insert(image_rectangle)) {
95 | return accept_result(normal, false);
96 | }
97 | }
98 | else {
99 | if (flipping_mode == flipping_option::ENABLED) {
100 | const auto normal = try_to_insert(image_rectangle);
101 | const auto flipped = try_to_insert(rect_wh(image_rectangle).flip());
102 |
103 | /*
104 | If both were successful,
105 | prefer the one that generated less remainder spaces.
106 | */
107 |
108 | if (normal && flipped) {
109 | if (flipped.better_than(normal)) {
110 | /* Accept the flipped result if it producues less or "better" spaces. */
111 |
112 | return accept_result(flipped, true);
113 | }
114 |
115 | return accept_result(normal, false);
116 | }
117 |
118 | if (normal) {
119 | return accept_result(normal, false);
120 | }
121 |
122 | if (flipped) {
123 | return accept_result(flipped, true);
124 | }
125 | }
126 | else {
127 | if (const auto normal = try_to_insert(image_rectangle)) {
128 | return accept_result(normal, false);
129 | }
130 | }
131 | }
132 | }
133 |
134 | return std::nullopt;
135 | }
136 |
137 | decltype(auto) insert(const rect_wh& image_rectangle) {
138 | return insert(image_rectangle, [](auto&){ });
139 | }
140 |
141 | auto get_rects_aabb() const {
142 | return current_aabb;
143 | }
144 |
145 | const auto& get_spaces() const {
146 | return spaces;
147 | }
148 | };
149 | }
150 |
--------------------------------------------------------------------------------
/src/rectpack2D/finders_interface.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #include "insert_and_split.h"
9 | #include "empty_spaces.h"
10 | #include "empty_space_allocators.h"
11 |
12 | #include "best_bin_finder.h"
13 |
14 | namespace rectpack2D {
15 | template
16 | using output_rect_t = typename empty_spaces_type::output_rect_type;
17 |
18 | template
19 | struct finder_input {
20 | const int max_bin_side;
21 | const int discard_step;
22 | F handle_successful_insertion;
23 | G handle_unsuccessful_insertion;
24 | const flipping_option flipping_mode;
25 | };
26 |
27 | template
28 | auto make_finder_input(
29 | const int max_bin_side,
30 | const int discard_step,
31 | F&& handle_successful_insertion,
32 | G&& handle_unsuccessful_insertion,
33 | const flipping_option flipping_mode
34 | ) {
35 | return finder_input {
36 | max_bin_side,
37 | discard_step,
38 | std::forward(handle_successful_insertion),
39 | std::forward(handle_unsuccessful_insertion),
40 | flipping_mode
41 | };
42 | };
43 |
44 | /*
45 | Finds the best packing for the rectangles,
46 | just in the order that they were passed.
47 | */
48 |
49 | template
50 | rect_wh find_best_packing_dont_sort(
51 | std::vector>& subjects,
52 | const finder_input& input
53 | ) {
54 | using order_type = std::remove_reference_t;
55 |
56 | return find_best_packing_impl(
57 | [&subjects](auto callback) { callback(subjects); },
58 | input
59 | );
60 | }
61 |
62 |
63 | /*
64 | Finds the best packing for the rectangles.
65 | Accepts a list of predicates able to compare two input rectangles.
66 |
67 | The function will try to pack the rectangles in all orders generated by the predicates,
68 | and will only write the x, y coordinates of the best packing found among the orders.
69 | */
70 |
71 | template
72 | rect_wh find_best_packing(
73 | std::vector>& subjects,
74 | const finder_input& input,
75 |
76 | Comparator comparator,
77 | Comparators... comparators
78 | ) {
79 | using rect_type = output_rect_t;
80 | using order_type = std::vector;
81 |
82 | constexpr auto count_orders = 1 + sizeof...(Comparators);
83 | thread_local std::array orders;
84 |
85 | {
86 | /* order[0] will always exist since this overload requires at least one comparator */
87 | auto& initial_pointers = orders[0];
88 | initial_pointers.clear();
89 |
90 | for (auto& s : subjects) {
91 | if (s.area() > 0) {
92 | initial_pointers.emplace_back(std::addressof(s));
93 | }
94 | }
95 |
96 | for (std::size_t i = 1; i < count_orders; ++i) {
97 | orders[i] = initial_pointers;
98 | }
99 | }
100 |
101 | std::size_t f = 0;
102 |
103 | auto& orders_ref = orders;
104 |
105 | auto make_order = [&f, &orders_ref](auto& predicate) {
106 | std::sort(orders_ref[f].begin(), orders_ref[f].end(), predicate);
107 | ++f;
108 | };
109 |
110 | make_order(comparator);
111 | (make_order(comparators), ...);
112 |
113 | return find_best_packing_impl(
114 | [&orders_ref](auto callback){ for (auto& o : orders_ref) { callback(o); } },
115 | input
116 | );
117 | }
118 |
119 | /*
120 | Finds the best packing for the rectangles.
121 | Provides a list of several sensible comparison predicates.
122 | */
123 |
124 | template
125 | rect_wh find_best_packing(
126 | std::vector>& subjects,
127 | const finder_input& input
128 | ) {
129 | using rect_type = output_rect_t;
130 |
131 | return find_best_packing(
132 | subjects,
133 | input,
134 |
135 | [](const rect_type* const a, const rect_type* const b) {
136 | return a->area() > b->area();
137 | },
138 | [](const rect_type* const a, const rect_type* const b) {
139 | return a->perimeter() > b->perimeter();
140 | },
141 | [](const rect_type* const a, const rect_type* const b) {
142 | return std::max(a->w, a->h) > std::max(b->w, b->h);
143 | },
144 | [](const rect_type* const a, const rect_type* const b) {
145 | return a->w > b->w;
146 | },
147 | [](const rect_type* const a, const rect_type* const b) {
148 | return a->h > b->h;
149 | }
150 | );
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/rectpack2D/insert_and_split.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 | #include "rect_structs.h"
4 |
5 | namespace rectpack2D {
6 | struct created_splits {
7 | int count = 0;
8 | std::array spaces;
9 |
10 | static auto failed() {
11 | created_splits result;
12 | result.count = -1;
13 | return result;
14 | }
15 |
16 | static auto none() {
17 | return created_splits();
18 | }
19 |
20 | template
21 | created_splits(Args&&... args) : spaces({ std::forward(args)... }) {
22 | count = sizeof...(Args);
23 | }
24 |
25 | bool better_than(const created_splits& b) const {
26 | return count < b.count;
27 | }
28 |
29 | explicit operator bool() const {
30 | return count != -1;
31 | }
32 | };
33 |
34 | inline created_splits insert_and_split(
35 | const rect_wh& im, /* Image rectangle */
36 | const space_rect& sp /* Space rectangle */
37 | ) {
38 | const auto free_w = sp.w - im.w;
39 | const auto free_h = sp.h - im.h;
40 |
41 | if (free_w < 0 || free_h < 0) {
42 | /*
43 | Image is bigger than the candidate empty space.
44 | We'll need to look further.
45 | */
46 |
47 | return created_splits::failed();
48 | }
49 |
50 | if (free_w == 0 && free_h == 0) {
51 | /*
52 | If the image dimensions equal the dimensions of the candidate empty space (image fits exactly),
53 | we will just delete the space and create no splits.
54 | */
55 |
56 | return created_splits::none();
57 | }
58 |
59 | /*
60 | If the image fits into the candidate empty space,
61 | but exactly one of the image dimensions equals the respective dimension of the candidate empty space
62 | (e.g. image = 20x40, candidate space = 30x40)
63 | we delete the space and create a single split. In this case a 10x40 space.
64 | */
65 |
66 | if (free_w > 0 && free_h == 0) {
67 | auto r = sp;
68 | r.x += im.w;
69 | r.w -= im.w;
70 | return created_splits(r);
71 | }
72 |
73 | if (free_w == 0 && free_h > 0) {
74 | auto r = sp;
75 | r.y += im.h;
76 | r.h -= im.h;
77 | return created_splits(r);
78 | }
79 |
80 | /*
81 | Every other option has been exhausted,
82 | so at this point the image must be *strictly* smaller than the empty space,
83 | that is, it is smaller in both width and height.
84 |
85 | Thus, free_w and free_h must be positive.
86 | */
87 |
88 | /*
89 | Decide which way to split.
90 |
91 | Instead of having two normally-sized spaces,
92 | it is better - though I have no proof of that - to have a one tiny space and a one huge space.
93 | This creates better opportunity for insertion of future rectangles.
94 |
95 | This is why, if we had more of width remaining than we had of height,
96 | we split along the vertical axis,
97 | and if we had more of height remaining than we had of width,
98 | we split along the horizontal axis.
99 | */
100 |
101 | if (free_w > free_h) {
102 | const auto bigger_split = space_rect(
103 | sp.x + im.w,
104 | sp.y,
105 | free_w,
106 | sp.h
107 | );
108 |
109 | const auto lesser_split = space_rect(
110 | sp.x,
111 | sp.y + im.h,
112 | im.w,
113 | free_h
114 | );
115 |
116 | return created_splits(bigger_split, lesser_split);
117 | }
118 |
119 | const auto bigger_split = space_rect(
120 | sp.x,
121 | sp.y + im.h,
122 | sp.w,
123 | free_h
124 | );
125 |
126 | const auto lesser_split = space_rect(
127 | sp.x + im.w,
128 | sp.y,
129 | free_w,
130 | im.h
131 | );
132 |
133 | return created_splits(bigger_split, lesser_split);
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/rectpack2D/rect_structs.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include
3 |
4 | namespace rectpack2D {
5 | using total_area_type = int;
6 |
7 | struct rect_wh {
8 | rect_wh() : w(0), h(0) {}
9 | rect_wh(const int w, const int h) : w(w), h(h) {}
10 |
11 | int w;
12 | int h;
13 |
14 | auto& flip() {
15 | std::swap(w, h);
16 | return *this;
17 | }
18 |
19 | int max_side() const {
20 | return h > w ? h : w;
21 | }
22 |
23 | int min_side() const {
24 | return h < w ? h : w;
25 | }
26 |
27 | int area() const { return w * h; }
28 | int perimeter() const { return 2 * w + 2 * h; }
29 |
30 | template
31 | void expand_with(const R& r) {
32 | w = std::max(w, r.x + r.w);
33 | h = std::max(h, r.y + r.h);
34 | }
35 | };
36 |
37 | struct rect_xywh {
38 | int x;
39 | int y;
40 | int w;
41 | int h;
42 |
43 | rect_xywh() : x(0), y(0), w(0), h(0) {}
44 | rect_xywh(const int x, const int y, const int w, const int h) : x(x), y(y), w(w), h(h) {}
45 |
46 | int area() const { return w * h; }
47 | int perimeter() const { return 2 * w + 2 * h; }
48 |
49 | auto get_wh() const {
50 | return rect_wh(w, h);
51 | }
52 | };
53 |
54 | struct rect_xywhf {
55 | int x;
56 | int y;
57 | int w;
58 | int h;
59 | bool flipped;
60 |
61 | rect_xywhf() : x(0), y(0), w(0), h(0), flipped(false) {}
62 | rect_xywhf(const int x, const int y, const int w, const int h, const bool flipped) : x(x), y(y), w(flipped ? h : w), h(flipped ? w : h), flipped(flipped) {}
63 | rect_xywhf(const rect_xywh& b) : rect_xywhf(b.x, b.y, b.w, b.h, false) {}
64 |
65 | int area() const { return w * h; }
66 | int perimeter() const { return 2 * w + 2 * h; }
67 |
68 | auto get_wh() const {
69 | return rect_wh(w, h);
70 | }
71 | };
72 |
73 | using space_rect = rect_xywh;
74 | }
--------------------------------------------------------------------------------
/thoughts.md:
--------------------------------------------------------------------------------
1 | ## Brute force approaches
2 |
3 | - Rect feasibility = number of empty spaces into which the rect fits
4 | - Rect difficulty = 1 / rect feasibility
5 | - Space feasibility = number of remaining input rectangles that fit into the space
6 | - How to break ties for huge, initial spaces?
7 | - How much one can insert until current rects AABB is expanded.
8 | - The more one can insert, the more feasible the space is.
9 | - In practice, will there be many ties?
10 | - There shouldn't be many not be if the max_size is carefully chosen
11 | - Determine difficulty recursively?
12 | - e.g. sum all successful insertions and successful insertions after each successful insertion...
13 | - ...quickly becomes exponential
14 | - Minimum of all free dimensions generated by all insertion trials?
15 | - Or some coefficient like pathological mult for rectangles?
16 | - Space difficulty = 1 / space feasibility
17 | - Algorithm: until no more spaces or no more rectangles
18 | - Find the most difficult space
19 | - among rects that fit...
20 | - insert the one that generates least spaces or the most difficult space - this means that, into the most difficult space, the most difficult rect will be inserted
21 | - In case of a perfectly fitting rect, this will be chosen
22 | - in case of a partly fitting or a strictly smaller rect, insert the most difficult rect, e.g. one that leaves the most difficult spaces
23 | - however, when splitting due to the rect being strictly smaller, split in the direction that generates a maximally feasible space
24 | - complexity: we iterate every space, number of which will grow linearly as time progresses, and then we iterate each rect
25 |
26 | ## Old thoughts
27 |
28 | - what about just resizing when there is no more space left?
29 | - then iterate over all empty spaces and resize those that are touching the edge
30 | - there might be none like this, though
31 | - then we can ditch iterating orders
32 | - we could then easily make it an on-line algorithm
33 |
--------------------------------------------------------------------------------