This commit is contained in:
Relintai 2019-10-20 21:23:33 +02:00
parent 4a4c98fa68
commit 1eaeafb49a
9 changed files with 1116 additions and 0 deletions

21
rectpack2D/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2016 Team Hypersomnia
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

208
rectpack2D/README.md Normal file
View File

@ -0,0 +1,208 @@
If you are looking for the old version of rectpack2D, it can still be found in [a legacy branch](https://github.com/TeamHypersomnia/rectpack2D/tree/legacy).
# rectpack2D
[![Build Status](https://travis-ci.org/TeamHypersomnia/rectpack2D.svg?branch=master)](https://travis-ci.org/TeamHypersomnia/rectpack2D)
[![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/aojyt3r6ysvadkgl?svg=true)](https://ci.appveyor.com/project/geneotech/rectpack2D)
Table of contents:
- [Benchmarks](#benchmarks)
- [Usage](#usage)
- [Building the example](#building-the-example)
* [Windows](#windows)
* [Linux](#linux)
- [Algorithm](#algorithm)
* [Insertion algorithm](#insertion-algorithm)
* [Additional heuristics](#additional-heuristics)
Rectangle packing library (no longer tiny!).
This is a refactored and **highly optimized** branch of the original library which is easier to use and customize.
![7](https://user-images.githubusercontent.com/3588717/42707552-d8b1c65e-86da-11e8-9412-54c580bd2696.jpg)
## Benchmarks
Tests were conducted on a ``Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz``.
The binary was built with ``clang 6.0.0``, using an -03 switch.
### Arbitrary game sprites: 582 subjects.
**Runtime: 0.8 ms**
**Wasted pixels: 10982 (0.24% - equivalent of a 105 x 105 square)**
Output (1896 x 2382):
![1](images/atlas_small.png)
In color:
(black is wasted space)
![2](images/atlas_small_color.png)
### Arbitrary game sprites + Japanese glyphs: 3264 subjects.
**Runtime: 4 ms**
**Wasted pixels: 15538 (0.31% - equivalent of a 125 x 125 square)**
Output (2116 x 2382):
![3](images/atlas_big.png)
In color:
(black is wasted space)
![4](images/atlas_big_color.png)
### Japanese glyphs + some GUI sprites: 3122 subjects.
**Runtime: 3.5 - 7 ms**
**Wasted pixels: 9288 (1.23% - equivalent of a 96 x 96 square)**
Output (866 x 871):
![5](images/atlas_tiny.png)
In color:
(black is wasted space)
![6](images/atlas_tiny_color.png)
## Usage
This is a header-only library.
Just include the ``src/finders_interface.h`` and you should be good to go.
For an example use, see ``example/main.cpp``.
## Building the example
### Windows
From the repository's folder, run:
```bash
mkdir build
cd build
cmake -G "Visual Studio 15 2017 Win64" ..
````
Then just build the generated ``.sln`` file using the newest Visual Studio Preview.
### Linux
From the repository's folder, run:
```bash
cmake/build_example.sh Release gcc g++
cd build/current
ninja run
````
Or, if you want to use clang, run:
```bash
cmake/build_example.sh Release clang clang++
cd build/current
ninja run
````
## Algorithm
### Insertion algorithm
The library started as an implementation of this algorithm:
http://blackpawn.com/texts/lightmaps/default.html
The current version somewhat derives from the concept described there -
however, it uses just a **vector of empty spaces, instead of a tree** - this turned out to be a performance breakthrough.
Given
```cpp
struct rect_xywh {
int x;
int y;
int w;
int h;
};
````
Let us create a vector and call it empty_spaces.
```cpp
std::vector<rect_xywh> empty_spaces;
````
Given a user-specified initial bin, which is a square of some size S, we initialize the first empty space.
```cpp
empty_spaces.push_back(rect_xywh(0, 0, S, S));
````
Now, we'd like to insert the first image rectangle.
To do this, we iterate the vector of empty spaces **backwards** and look for an empty space into which the image can fit.
For now, we only have the S x S square: let's save the index of this candidate empty space,
which is ``candidate_space_index = 0;``
If our image is strictly smaller than the candidate space, we have something like this:
![diag01](images/diag01.png)
The blue is our image rectangle.
We now calculate the gray rectangles labeled as "bigger split" and "smaller split",
and save them like this:
```cpp
// Erase the space that we've just inserted to, by swapping and popping.
empty_spaces[candidate_space_index] = empty_spaces.back();
empty_spaces.pop_back();
// Save the resultant splits
empty_spaces.push_back(bigger_split);
empty_spaces.push_back(smaller_split);
````
Notice that we push the smaller split *after* the bigger one.
This is fairly important, because later the candidate images will encounter the smaller splits first,
which will make better use of empty spaces overall.
#### Corner cases:
- If the image dimensions equal the dimensions of the candidate empty space (image fits exactly),
- we just delete the space and create no splits.
- 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)
- we delete the space and create a single split. In this case a 10x40 space.
To see the complete, modular procedure for calculating the splits (along with the corner cases),
[see this source](src/insert_and_split.h).
If the insertion fails, we also try the same procedure for a flipped image.
### Additional heuristics
Now we know how to insert individual images into a bin of a given initial size S.
1. However, what S should be passed to the algorithm so that the rectangles end up wasting the least amount of space?
- We perform a binary search.
- We start with the size specified by the library user. Typically, it would be the maximum texture size allowed on a particular GPU.
- If the packing was successful on the given bin size, decrease the size and try to pack again.
- 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.
- 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``.
- 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.
- The search is performed first by decreasing the bin size by both width and height, keeping it in square shape.
- Then we do the same, but only decreasing width.
- Then we do the same, but only decreasing height.
- The last two were a breakthrough in packing tightness. It turns out important to consider non-square bins.
2. In what order should the rectangles be inserted so that they pack the tightest?
- By default, the library tries 6 decreasing orders:
- By area.
- By perimeter.
- By the bigger side.
- By width.
- By height.
- By 'a pathological multiplier": ``max(w, h) / min(w, h) * w * h``
- This makes some giant, irregular mutants always go first, which is good, because it doesn't shred our empty spaces to a lot of useless pieces.

View File

@ -0,0 +1,270 @@
#pragma once
#include <variant>
#include <cassert>
#include "rect_structs.h"
namespace rectpack2D {
enum class callback_result {
ABORT_PACKING,
CONTINUE_PACKING
};
template <class T>
auto& dereference(T& r) {
/*
This will allow us to pass orderings that consist of pointers,
as well as ones that are just plain objects in a vector.
*/
if constexpr(std::is_pointer_v<T>) {
return *r;
}
else {
return r;
}
};
/*
This function will do a binary search on viable bin sizes,
starting from the biggest one: starting_bin.
The search stops when the bin was successfully inserted into,
AND the bin size to be tried next differs in size from the last viable one by *less* then discard_step.
If we could not insert all input rectangles into a bin even as big as the starting_bin - the search fails.
In this case, we return the amount of space (total_area_type) inserted in total.
If we've found a viable bin that is smaller or equal to starting_bin, the search succeeds.
In this case, we return the viable bin (rect_wh).
*/
enum class bin_dimension {
BOTH,
WIDTH,
HEIGHT
};
template <class empty_spaces_type, class O>
std::variant<total_area_type, rect_wh> best_packing_for_ordering_impl(
empty_spaces_type& root,
O ordering,
const rect_wh starting_bin,
const int discard_step,
const bin_dimension tried_dimension
) {
auto candidate_bin = starting_bin;
int starting_step = 0;
if (tried_dimension == bin_dimension::BOTH) {
starting_step = starting_bin.w / 2;
candidate_bin.w /= 2;
candidate_bin.h /= 2;
}
else if (tried_dimension == bin_dimension::WIDTH) {
starting_step = starting_bin.w / 2;
candidate_bin.w /= 2;
}
else {
starting_step = starting_bin.h / 2;
candidate_bin.h /= 2;
}
for (int step = starting_step; ; step = std::max(1, step / 2)) {
root.reset(candidate_bin);
int total_inserted_area = 0;
const bool all_inserted = [&]() {
for (const auto& r : ordering) {
const auto& rect = dereference(r);
if (root.insert(rect.get_wh())) {
total_inserted_area += rect.area();
}
else {
return false;
}
}
return true;
}();
if (all_inserted) {
/* Attempt was successful. Try with a smaller bin. */
if (step <= discard_step) {
return candidate_bin;
}
if (tried_dimension == bin_dimension::BOTH) {
candidate_bin.w -= step;
candidate_bin.h -= step;
}
else if (tried_dimension == bin_dimension::WIDTH) {
candidate_bin.w -= step;
}
else {
candidate_bin.h -= step;
}
root.reset(candidate_bin);
}
else {
/* Attempt ended with failure. Try with a bigger bin. */
if (tried_dimension == bin_dimension::BOTH) {
candidate_bin.w += step;
candidate_bin.h += step;
if (candidate_bin.area() > starting_bin.area()) {
return total_inserted_area;
}
}
else if (tried_dimension == bin_dimension::WIDTH) {
candidate_bin.w += step;
if (candidate_bin.w > starting_bin.w) {
return total_inserted_area;
}
}
else {
candidate_bin.h += step;
if (candidate_bin.h > starting_bin.h) {
return total_inserted_area;
}
}
}
}
}
template <class empty_spaces_type, class O>
std::variant<total_area_type, rect_wh> best_packing_for_ordering(
empty_spaces_type& root,
O&& ordering,
const rect_wh starting_bin,
const int discard_step
) {
const auto try_pack = [&](
const bin_dimension tried_dimension,
const rect_wh starting_bin
) {
return best_packing_for_ordering_impl(
root,
std::forward<O>(ordering),
starting_bin,
discard_step,
tried_dimension
);
};
const auto best_result = try_pack(bin_dimension::BOTH, starting_bin);
if (const auto failed = std::get_if<total_area_type>(&best_result)) {
return *failed;
}
auto best_bin = std::get<rect_wh>(best_result);
auto trial = [&](const bin_dimension tried_dimension) {
const auto trial = try_pack(tried_dimension, best_bin);
if (const auto better = std::get_if<rect_wh>(&trial)) {
best_bin = *better;
}
};
trial(bin_dimension::WIDTH);
trial(bin_dimension::HEIGHT);
return best_bin;
}
/*
This function will try to find the best bin size among the ones generated by all provided rectangle orders.
Only the best order will have results written to.
The function reports which of the rectangles did and did not fit in the end.
*/
template <
class empty_spaces_type,
class OrderType,
class F,
class I
>
rect_wh find_best_packing_impl(F for_each_order, const I input) {
const auto max_bin = rect_wh(input.max_bin_side, input.max_bin_side);
OrderType* best_order = nullptr;
int best_total_inserted = -1;
auto best_bin = max_bin;
/*
The root node is re-used on the TLS.
It is always reset before any packing attempt.
*/
thread_local empty_spaces_type root = rect_wh();
root.flipping_mode = input.flipping_mode;
for_each_order ([&](OrderType& current_order) {
const auto packing = best_packing_for_ordering(
root,
current_order,
max_bin,
input.discard_step
);
if (const auto total_inserted = std::get_if<total_area_type>(&packing)) {
/*
Track which function inserts the most area in total,
just in case that all orders will fail to fit into the largest allowed bin.
*/
if (best_order == nullptr) {
if (*total_inserted > best_total_inserted) {
best_order = std::addressof(current_order);
best_total_inserted = *total_inserted;
}
}
}
else if (const auto result_bin = std::get_if<rect_wh>(&packing)) {
/* Save the function if it performed the best. */
if (result_bin->area() <= best_bin.area()) {
best_order = std::addressof(current_order);
best_bin = *result_bin;
}
}
});
{
assert(best_order != nullptr);
root.reset(best_bin);
for (auto& rr : *best_order) {
auto& rect = dereference(rr);
if (const auto ret = root.insert(rect.get_wh())) {
rect = *ret;
if (callback_result::ABORT_PACKING == input.handle_successful_insertion(rect)) {
break;
}
}
else {
if (callback_result::ABORT_PACKING == input.handle_unsuccessful_insertion(rect)) {
break;
}
}
}
return root.get_rects_aabb();
}
}
}

View File

@ -0,0 +1,70 @@
#pragma once
#include <array>
#include <vector>
#include <type_traits>
#include "rect_structs.h"
namespace rectpack2D {
class default_empty_spaces {
std::vector<space_rect> empty_spaces;
public:
void remove(const int i) {
empty_spaces[i] = empty_spaces.back();
empty_spaces.pop_back();
}
bool add(const space_rect r) {
empty_spaces.emplace_back(r);
return true;
}
auto get_count() const {
return empty_spaces.size();
}
void reset() {
empty_spaces.clear();
}
const auto& get(const int i) {
return empty_spaces[i];
}
};
template <int MAX_SPACES>
class static_empty_spaces {
int count_spaces = 0;
std::array<space_rect, MAX_SPACES> empty_spaces;
public:
void remove(const int i) {
empty_spaces[i] = empty_spaces[count_spaces - 1];
--count_spaces;
}
bool add(const space_rect r) {
if (count_spaces < static_cast<int>(empty_spaces.size())) {
empty_spaces[count_spaces] = r;
++count_spaces;
return true;
}
return false;
}
auto get_count() const {
return count_spaces;
}
void reset() {
count_spaces = 0;
}
const auto& get(const int i) {
return empty_spaces[i];
}
};
}

149
rectpack2D/empty_spaces.h Normal file
View File

@ -0,0 +1,149 @@
#pragma once
#include "insert_and_split.h"
namespace rectpack2D {
enum class flipping_option {
DISABLED,
ENABLED
};
class default_empty_spaces;
template <bool allow_flip, class empty_spaces_provider = default_empty_spaces>
class empty_spaces {
rect_wh current_aabb;
empty_spaces_provider spaces;
/* MSVC fix for non-conformant if constexpr implementation */
static auto make_output_rect(const int x, const int y, const int w, const int h) {
return rect_xywh(x, y, w, h);
}
static auto make_output_rect(const int x, const int y, const int w, const int h, const bool flipped) {
return rect_xywhf(x, y, w, h, flipped);
}
public:
using output_rect_type = std::conditional_t<allow_flip, rect_xywhf, rect_xywh>;
flipping_option flipping_mode = flipping_option::ENABLED;
empty_spaces(const rect_wh& r) {
reset(r);
}
void reset(const rect_wh& r) {
current_aabb = {};
spaces.reset();
spaces.add(rect_xywh(0, 0, r.w, r.h));
}
template <class F>
std::optional<output_rect_type> insert(const rect_wh image_rectangle, F report_candidate_empty_space) {
for (int i = static_cast<int>(spaces.get_count()) - 1; i >= 0; --i) {
const auto candidate_space = spaces.get(i);
report_candidate_empty_space(candidate_space);
auto accept_result = [this, i, image_rectangle, candidate_space](
const created_splits& splits,
const bool flipping_necessary
) -> std::optional<output_rect_type> {
spaces.remove(i);
for (int s = 0; s < splits.count; ++s) {
if (!spaces.add(splits.spaces[s])) {
return std::nullopt;
}
}
if constexpr(allow_flip) {
const auto result = make_output_rect(
candidate_space.x,
candidate_space.y,
image_rectangle.w,
image_rectangle.h,
flipping_necessary
);
current_aabb.expand_with(result);
return result;
}
else if constexpr(!allow_flip) {
(void)flipping_necessary;
const auto result = make_output_rect(
candidate_space.x,
candidate_space.y,
image_rectangle.w,
image_rectangle.h
);
current_aabb.expand_with(result);
return result;
}
};
auto try_to_insert = [&](const rect_wh& img) {
return rectpack2D::insert_and_split(img, candidate_space);
};
if constexpr(!allow_flip) {
if (const auto normal = try_to_insert(image_rectangle)) {
return accept_result(normal, false);
}
}
else {
if (flipping_mode == flipping_option::ENABLED) {
const auto normal = try_to_insert(image_rectangle);
const auto flipped = try_to_insert(rect_wh(image_rectangle).flip());
/*
If both were successful,
prefer the one that generated less remainder spaces.
*/
if (normal && flipped) {
if (flipped.better_than(normal)) {
/* Accept the flipped result if it producues less or "better" spaces. */
return accept_result(flipped, true);
}
return accept_result(normal, false);
}
if (normal) {
return accept_result(normal, false);
}
if (flipped) {
return accept_result(flipped, true);
}
}
else {
if (const auto normal = try_to_insert(image_rectangle)) {
return accept_result(normal, false);
}
}
}
}
return std::nullopt;
}
decltype(auto) insert(const rect_wh& image_rectangle) {
return insert(image_rectangle, [](auto&){ });
}
auto get_rects_aabb() const {
return current_aabb;
}
const auto& get_spaces() const {
return spaces;
}
};
}

View File

@ -0,0 +1,153 @@
#pragma once
#include <optional>
#include <vector>
#include <array>
#include <variant>
#include <algorithm>
#include "insert_and_split.h"
#include "empty_spaces.h"
#include "empty_space_allocators.h"
#include "best_bin_finder.h"
namespace rectpack2D {
template <class empty_spaces_type>
using output_rect_t = typename empty_spaces_type::output_rect_type;
template <class F, class G>
struct finder_input {
const int max_bin_side;
const int discard_step;
F handle_successful_insertion;
G handle_unsuccessful_insertion;
const flipping_option flipping_mode;
};
template <class F, class G>
auto make_finder_input(
const int max_bin_side,
const int discard_step,
F&& handle_successful_insertion,
G&& handle_unsuccessful_insertion,
const flipping_option flipping_mode
) {
return finder_input<F, G> {
max_bin_side,
discard_step,
std::forward<F>(handle_successful_insertion),
std::forward<G>(handle_unsuccessful_insertion),
flipping_mode
};
};
/*
Finds the best packing for the rectangles,
just in the order that they were passed.
*/
template <class empty_spaces_type, class F, class G>
rect_wh find_best_packing_dont_sort(
std::vector<output_rect_t<empty_spaces_type>>& subjects,
const finder_input<F, G>& input
) {
using order_type = std::remove_reference_t<decltype(subjects)>;
return find_best_packing_impl<empty_spaces_type, order_type>(
[&subjects](auto callback) { callback(subjects); },
input
);
}
/*
Finds the best packing for the rectangles.
Accepts a list of predicates able to compare two input rectangles.
The function will try to pack the rectangles in all orders generated by the predicates,
and will only write the x, y coordinates of the best packing found among the orders.
*/
template <class empty_spaces_type, class F, class G, class Comparator, class... Comparators>
rect_wh find_best_packing(
std::vector<output_rect_t<empty_spaces_type>>& subjects,
const finder_input<F, G>& input,
Comparator comparator,
Comparators... comparators
) {
using rect_type = output_rect_t<empty_spaces_type>;
using order_type = std::vector<rect_type*>;
constexpr auto count_orders = 1 + sizeof...(Comparators);
thread_local std::array<order_type, count_orders> orders;
{
/* order[0] will always exist since this overload requires at least one comparator */
auto& initial_pointers = orders[0];
initial_pointers.clear();
for (auto& s : subjects) {
if (s.area() > 0) {
initial_pointers.emplace_back(std::addressof(s));
}
}
for (std::size_t i = 1; i < count_orders; ++i) {
orders[i] = initial_pointers;
}
}
std::size_t f = 0;
auto make_order = [&f](auto& predicate) {
std::sort(orders[f].begin(), orders[f].end(), predicate);
++f;
};
make_order(comparator);
(make_order(comparators), ...);
return find_best_packing_impl<empty_spaces_type, order_type>(
[](auto callback){ for (auto& o : orders) { callback(o); } },
input
);
}
/*
Finds the best packing for the rectangles.
Provides a list of several sensible comparison predicates.
*/
template <class empty_spaces_type, class F, class G>
rect_wh find_best_packing(
std::vector<output_rect_t<empty_spaces_type>>& subjects,
const finder_input<F, G>& input
) {
using rect_type = output_rect_t<empty_spaces_type>;
return find_best_packing<empty_spaces_type>(
subjects,
input,
[](const rect_type* const a, const rect_type* const b) {
return a->area() > b->area();
},
[](const rect_type* const a, const rect_type* const b) {
return a->perimeter() > b->perimeter();
},
[](const rect_type* const a, const rect_type* const b) {
return std::max(a->w, a->h) > std::max(b->w, b->h);
},
[](const rect_type* const a, const rect_type* const b) {
return a->w > b->w;
},
[](const rect_type* const a, const rect_type* const b) {
return a->h > b->h;
},
[](const rect_type* const a, const rect_type* const b) {
return a->get_wh().pathological_mult() > b->get_wh().pathological_mult();
}
);
}
}

View File

@ -0,0 +1,135 @@
#pragma once
#include <array>
#include "rect_structs.h"
namespace rectpack2D {
struct created_splits {
int count = 0;
std::array<space_rect, 2> spaces;
static auto failed() {
created_splits result;
result.count = -1;
return result;
}
static auto none() {
return created_splits();
}
template <class... Args>
created_splits(Args&&... args) : spaces({ std::forward<Args>(args)... }) {
count = sizeof...(Args);
}
bool better_than(const created_splits& b) const {
return count < b.count;
}
explicit operator bool() const {
return count != -1;
}
};
inline created_splits insert_and_split(
const rect_wh& im, /* Image rectangle */
const space_rect& sp /* Space rectangle */
) {
const auto free_w = sp.w - im.w;
const auto free_h = sp.h - im.h;
if (free_w < 0 || free_h < 0) {
/*
Image is bigger than the candidate empty space.
We'll need to look further.
*/
return created_splits::failed();
}
if (free_w == 0 && free_h == 0) {
/*
If the image dimensions equal the dimensions of the candidate empty space (image fits exactly),
we will just delete the space and create no splits.
*/
return created_splits::none();
}
/*
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)
we delete the space and create a single split. In this case a 10x40 space.
*/
if (free_w > 0 && free_h == 0) {
auto r = sp;
r.x += im.w;
r.w -= im.w;
return created_splits(r);
}
if (free_w == 0 && free_h > 0) {
auto r = sp;
r.y += im.h;
r.h -= im.h;
return created_splits(r);
}
/*
Every other option has been exhausted,
so at this point the image must be *strictly* smaller than the empty space,
that is, it is smaller in both width and height.
Thus, free_w and free_h must be positive.
*/
/*
Decide which way to split.
Instead of having two normally-sized spaces,
it is better - though I have no proof of that - to have a one tiny space and a one huge space.
This creates better opportunity for insertion of future rectangles.
This is why, if we had more of width remaining than we had of height,
we split along the vertical axis,
and if we had more of height remaining than we had of width,
we split along the horizontal axis.
*/
if (free_w > free_h) {
const auto bigger_split = space_rect(
sp.x + im.w,
sp.y,
free_w,
sp.h
);
const auto lesser_split = space_rect(
sp.x,
sp.y + im.h,
im.w,
free_h
);
return created_splits(bigger_split, lesser_split);
}
const auto bigger_split = space_rect(
sp.x,
sp.y + im.h,
sp.w,
free_h
);
const auto lesser_split = space_rect(
sp.x + im.w,
sp.y,
free_w,
im.h
);
return created_splits(bigger_split, lesser_split);
}
}

78
rectpack2D/rect_structs.h Normal file
View File

@ -0,0 +1,78 @@
#pragma once
#include <utility>
namespace rectpack2D {
using total_area_type = int;
struct rect_wh {
rect_wh() : w(0), h(0) {}
rect_wh(const int w, const int h) : w(w), h(h) {}
int w;
int h;
auto& flip() {
std::swap(w, h);
return *this;
}
int max_side() const {
return h > w ? h : w;
}
int min_side() const {
return h < w ? h : w;
}
int area() const { return w * h; }
int perimeter() const { return 2 * w + 2 * h; }
float pathological_mult() const {
return float(max_side()) / min_side() * area();
}
template <class R>
void expand_with(const R& r) {
w = std::max(w, r.x + r.w);
h = std::max(h, r.y + r.h);
}
};
struct rect_xywh {
int x;
int y;
int w;
int h;
rect_xywh() : x(0), y(0), w(0), h(0) {}
rect_xywh(const int x, const int y, const int w, const int h) : x(x), y(y), w(w), h(h) {}
int area() const { return w * h; }
int perimeter() const { return 2 * w + 2 * h; }
auto get_wh() const {
return rect_wh(w, h);
}
};
struct rect_xywhf {
int x;
int y;
int w;
int h;
bool flipped;
rect_xywhf() : x(0), y(0), w(0), h(0), flipped(false) {}
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) {}
rect_xywhf(const rect_xywh& b) : rect_xywhf(b.x, b.y, b.w, b.h, false) {}
int area() const { return w * h; }
int perimeter() const { return 2 * w + 2 * h; }
auto get_wh() const {
return rect_wh(w, h);
}
};
using space_rect = rect_xywh;
}

32
rectpack2D/thoughts.md Normal file
View File

@ -0,0 +1,32 @@
## Brute force approaches
- Rect feasibility = number of empty spaces into which the rect fits
- Rect difficulty = 1 / rect feasibility
- Space feasibility = number of remaining input rectangles that fit into the space
- How to break ties for huge, initial spaces?
- How much one can insert until current rects AABB is expanded.
- The more one can insert, the more feasible the space is.
- In practice, will there be many ties?
- There shouldn't be many not be if the max_size is carefully chosen
- Determine difficulty recursively?
- e.g. sum all successful insertions and successful insertions after each successful insertion...
- ...quickly becomes exponential
- Minimum of all free dimensions generated by all insertion trials?
- Or some coefficient like pathological mult for rectangles?
- Space difficulty = 1 / space feasibility
- Algorithm: until no more spaces or no more rectangles
- Find the most difficult space
- among rects that fit...
- 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
- In case of a perfectly fitting rect, this will be chosen
- 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
- however, when splitting due to the rect being strictly smaller, split in the direction that generates a maximally feasible space
- complexity: we iterate every space, number of which will grow linearly as time progresses, and then we iterate each rect
## Old thoughts
- what about just resizing when there is no more space left?
- then iterate over all empty spaces and resize those that are touching the edge
- there might be none like this, though
- then we can ditch iterating orders
- we could then easily make it an on-line algorithm