mirror of
https://github.com/Relintai/texture_packer.git
synced 2024-11-12 10:15:16 +01:00
Added the rectpack2D library. (https://github.com/TeamHypersomnia/rectpack2D)
This commit is contained in:
parent
4a4c98fa68
commit
1eaeafb49a
21
rectpack2D/LICENSE
Normal file
21
rectpack2D/LICENSE
Normal 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
208
rectpack2D/README.md
Normal 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.
|
270
rectpack2D/best_bin_finder.h
Normal file
270
rectpack2D/best_bin_finder.h
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
70
rectpack2D/empty_space_allocators.h
Normal file
70
rectpack2D/empty_space_allocators.h
Normal 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
149
rectpack2D/empty_spaces.h
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
153
rectpack2D/finders_interface.h
Normal file
153
rectpack2D/finders_interface.h
Normal 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();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
135
rectpack2D/insert_and_split.h
Normal file
135
rectpack2D/insert_and_split.h
Normal 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
78
rectpack2D/rect_structs.h
Normal 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
32
rectpack2D/thoughts.md
Normal 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
|
Loading…
Reference in New Issue
Block a user