mirror of
https://github.com/Relintai/texture_packer.git
synced 2024-11-14 10:17:21 +01:00
380 lines
7.9 KiB
C++
380 lines
7.9 KiB
C++
#include "pack.h"
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
|
|
using namespace std;
|
|
|
|
bool area(rect_xywhf *a, rect_xywhf *b) {
|
|
return a->area() > b->area();
|
|
}
|
|
|
|
bool perimeter(rect_xywhf *a, rect_xywhf *b) {
|
|
return a->perimeter() > b->perimeter();
|
|
}
|
|
|
|
bool max_side(rect_xywhf *a, rect_xywhf *b) {
|
|
return std::max(a->w, a->h) > std::max(b->w, b->h);
|
|
}
|
|
|
|
bool max_width(rect_xywhf *a, rect_xywhf *b) {
|
|
return a->w > b->w;
|
|
}
|
|
|
|
bool max_height(rect_xywhf *a, rect_xywhf *b) {
|
|
return a->h > b->h;
|
|
}
|
|
|
|
// just add another comparing function name to cmpf to perform another packing attempt
|
|
// more functions == slower but probably more efficient cases covered and hence less area wasted
|
|
|
|
bool (*cmpf[])(rect_xywhf *, rect_xywhf *) = {
|
|
area,
|
|
perimeter,
|
|
max_side,
|
|
max_width,
|
|
max_height
|
|
};
|
|
|
|
// if you find the algorithm running too slow you may double this factor to increase speed but also decrease efficiency
|
|
// 1 == most efficient, slowest
|
|
// efficiency may be still satisfying at 64 or even 256 with nice speedup
|
|
|
|
int discard_step = 128;
|
|
|
|
/*
|
|
|
|
For every sorting function, algorithm will perform packing attempts beginning with a bin with width and height equal to max_side,
|
|
and decreasing its dimensions if it finds out that rectangles did actually fit, increasing otherwise.
|
|
Although, it's doing that in sort of binary search manner, so for every comparing function it will perform at most log2(max_side) packing attempts looking for the smallest possible bin size.
|
|
discard_step = 128 means that the algorithm will break of the searching loop if the rectangles fit but "it may be possible to fit them in a bin smaller by 128"
|
|
the bigger the value, the sooner the algorithm will finish but the rectangles will be packed less tightly.
|
|
use discard_step = 1 for maximum tightness.
|
|
|
|
the algorithm was based on http://www.blackpawn.com/texts/lightmaps/default.html
|
|
the algorithm reuses the node tree so it doesn't reallocate them between searching attempts
|
|
|
|
*/
|
|
|
|
/*************************************************************************** CHAOS BEGINS HERE */
|
|
|
|
struct node {
|
|
struct pnode {
|
|
node *pn = nullptr;
|
|
bool fill = false;
|
|
|
|
void set(int l, int t, int r, int b) {
|
|
if (!pn)
|
|
pn = new node(rect_ltrb(l, t, r, b));
|
|
else {
|
|
(*pn).rc = rect_ltrb(l, t, r, b);
|
|
(*pn).id = false;
|
|
}
|
|
fill = true;
|
|
}
|
|
};
|
|
|
|
pnode c[2];
|
|
rect_ltrb rc;
|
|
bool id = false;
|
|
node(rect_ltrb rect_rc = rect_ltrb()) :
|
|
rc(rect_rc) {}
|
|
|
|
void reset(const rect_wh &r) {
|
|
id = false;
|
|
rc = rect_ltrb(0, 0, r.w, r.h);
|
|
delcheck();
|
|
}
|
|
|
|
node *insert(rect_xywhf &img, bool allowFlip) {
|
|
if (c[0].pn && c[0].fill) {
|
|
if (auto newn = c[0].pn->insert(img, allowFlip)) return newn;
|
|
return c[1].pn->insert(img, allowFlip);
|
|
}
|
|
|
|
if (id) return 0;
|
|
int f = img.fits(rect_xywh(rc), allowFlip);
|
|
|
|
switch (f) {
|
|
case 0: return 0;
|
|
case 1: img.flipped = false; break;
|
|
case 2: img.flipped = true; break;
|
|
case 3:
|
|
id = true;
|
|
img.flipped = false;
|
|
return this;
|
|
case 4:
|
|
id = true;
|
|
img.flipped = true;
|
|
return this;
|
|
}
|
|
|
|
int iw = (img.flipped ? img.h : img.w), ih = (img.flipped ? img.w : img.h);
|
|
|
|
if (rc.w() - iw > rc.h() - ih) {
|
|
c[0].set(rc.l, rc.t, rc.l + iw, rc.b);
|
|
c[1].set(rc.l + iw, rc.t, rc.r, rc.b);
|
|
} else {
|
|
c[0].set(rc.l, rc.t, rc.r, rc.t + ih);
|
|
c[1].set(rc.l, rc.t + ih, rc.r, rc.b);
|
|
}
|
|
|
|
return c[0].pn->insert(img, allowFlip);
|
|
}
|
|
|
|
void delcheck() {
|
|
if (c[0].pn) {
|
|
c[0].fill = false;
|
|
c[0].pn->delcheck();
|
|
}
|
|
if (c[1].pn) {
|
|
c[1].fill = false;
|
|
c[1].pn->delcheck();
|
|
}
|
|
}
|
|
|
|
~node() {
|
|
if (c[0].pn) delete c[0].pn;
|
|
if (c[1].pn) delete c[1].pn;
|
|
}
|
|
};
|
|
|
|
rect_wh _rect2D(rect_xywhf *const *v, int n, int max_s, bool allowFlip, vector<rect_xywhf *> &succ, vector<rect_xywhf *> &unsucc) {
|
|
node root;
|
|
|
|
const int funcs = (sizeof(cmpf) / sizeof(bool (*)(rect_xywhf *, rect_xywhf *)));
|
|
|
|
rect_xywhf **order[funcs];
|
|
|
|
for (int f = 0; f < funcs; ++f) {
|
|
order[f] = new rect_xywhf *[n];
|
|
std::memcpy(order[f], v, sizeof(rect_xywhf *) * n);
|
|
sort(order[f], order[f] + n, cmpf[f]);
|
|
}
|
|
|
|
rect_wh min_bin = rect_wh(max_s, max_s);
|
|
int min_func = -1, best_func = 0, best_area = 0, _area = 0, step, fit, i;
|
|
|
|
bool fail = false;
|
|
|
|
for (int f = 0; f < funcs; ++f) {
|
|
v = order[f];
|
|
step = min_bin.w / 2;
|
|
root.reset(min_bin);
|
|
|
|
while (true) {
|
|
if (root.rc.w() > min_bin.w) {
|
|
if (min_func > -1) break;
|
|
_area = 0;
|
|
|
|
root.reset(min_bin);
|
|
for (i = 0; i < n; ++i)
|
|
if (root.insert(*v[i], allowFlip))
|
|
_area += v[i]->area();
|
|
|
|
fail = true;
|
|
break;
|
|
}
|
|
|
|
fit = -1;
|
|
|
|
for (i = 0; i < n; ++i)
|
|
if (!root.insert(*v[i], allowFlip)) {
|
|
fit = 1;
|
|
break;
|
|
}
|
|
|
|
if (fit == -1 && step <= discard_step)
|
|
break;
|
|
|
|
root.reset(rect_wh(root.rc.w() + fit * step, root.rc.h() + fit * step));
|
|
|
|
step /= 2;
|
|
if (!step)
|
|
step = 1;
|
|
}
|
|
|
|
if (!fail && (min_bin.area() >= root.rc.area())) {
|
|
min_bin = rect_wh(root.rc);
|
|
min_func = f;
|
|
}
|
|
|
|
else if (fail && (_area > best_area)) {
|
|
best_area = _area;
|
|
best_func = f;
|
|
}
|
|
fail = false;
|
|
}
|
|
|
|
v = order[min_func == -1 ? best_func : min_func];
|
|
|
|
int clip_x = 0, clip_y = 0;
|
|
|
|
root.reset(min_bin);
|
|
|
|
for (i = 0; i < n; ++i) {
|
|
if (auto ret = root.insert(*v[i], allowFlip)) {
|
|
v[i]->x = ret->rc.l;
|
|
v[i]->y = ret->rc.t;
|
|
|
|
if (v[i]->flipped) {
|
|
v[i]->flipped = false;
|
|
v[i]->flip();
|
|
}
|
|
|
|
clip_x = std::max(clip_x, ret->rc.r);
|
|
clip_y = std::max(clip_y, ret->rc.b);
|
|
|
|
succ.push_back(v[i]);
|
|
} else {
|
|
unsucc.push_back(v[i]);
|
|
|
|
v[i]->flipped = false;
|
|
}
|
|
}
|
|
|
|
for (int f = 0; f < funcs; ++f)
|
|
delete[] order[f];
|
|
|
|
return rect_wh(clip_x, clip_y);
|
|
}
|
|
|
|
bool pack(rect_xywhf *const *v, int n, int max_s, bool allowFlip, vector<bin> &bins) {
|
|
rect_wh _rect(max_s, max_s);
|
|
|
|
for (int i = 0; i < n; ++i)
|
|
if (!v[i]->fits(_rect, allowFlip)) return false;
|
|
|
|
vector<rect_xywhf *> vec[2], *p[2] = { vec, vec + 1 };
|
|
vec[0].resize(n);
|
|
vec[1].clear();
|
|
std::memcpy(&vec[0][0], v, sizeof(rect_xywhf *) * n);
|
|
|
|
bin *b = 0;
|
|
|
|
while (true) {
|
|
bins.push_back(bin());
|
|
b = &bins[bins.size() - 1];
|
|
|
|
b->size = _rect2D(&((*p[0])[0]), static_cast<int>(p[0]->size()), max_s, allowFlip, b->rects, *p[1]);
|
|
p[0]->clear();
|
|
|
|
if (!p[1]->size()) break;
|
|
|
|
std::swap(p[0], p[1]);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
rect_wh::rect_wh(const rect_ltrb &rr) :
|
|
w(rr.w()),
|
|
h(rr.h()) {}
|
|
rect_wh::rect_wh(const rect_xywh &rr) :
|
|
w(rr.w),
|
|
h(rr.h) {}
|
|
rect_wh::rect_wh(int p_w, int p_h) :
|
|
w(p_w),
|
|
h(p_h) {}
|
|
|
|
int rect_wh::fits(const rect_wh &p_r, bool p_allow_flip) const {
|
|
if (w == p_r.w && h == p_r.h) return 3;
|
|
if (p_allow_flip && h == p_r.w && w == p_r.h) return 4;
|
|
if (w <= p_r.w && h <= p_r.h) return 1;
|
|
if (p_allow_flip && h <= p_r.w && w <= p_r.h) return 2;
|
|
return 0;
|
|
}
|
|
|
|
rect_ltrb::rect_ltrb() :
|
|
l(0),
|
|
t(0),
|
|
r(0),
|
|
b(0) {}
|
|
rect_ltrb::rect_ltrb(int p_l, int p_t, int p_r, int p_b) :
|
|
l(p_l),
|
|
t(p_t),
|
|
r(p_r),
|
|
b(p_b) {}
|
|
|
|
int rect_ltrb::w() const {
|
|
return r - l;
|
|
}
|
|
|
|
int rect_ltrb::h() const {
|
|
return b - t;
|
|
}
|
|
|
|
int rect_ltrb::area() const {
|
|
return w() * h();
|
|
}
|
|
|
|
int rect_ltrb::perimeter() const {
|
|
return 2 * w() + 2 * h();
|
|
}
|
|
|
|
void rect_ltrb::w(int ww) {
|
|
r = l + ww;
|
|
}
|
|
|
|
void rect_ltrb::h(int hh) {
|
|
b = t + hh;
|
|
}
|
|
|
|
rect_xywh::rect_xywh() :
|
|
x(0),
|
|
y(0) {}
|
|
rect_xywh::rect_xywh(const rect_ltrb &p_rc) :
|
|
x(p_rc.l),
|
|
y(p_rc.t) {
|
|
b(p_rc.b);
|
|
r(p_rc.r);
|
|
}
|
|
rect_xywh::rect_xywh(int p_x, int p_y, int p_w, int p_h) :
|
|
rect_wh(p_w, p_h),
|
|
x(p_x),
|
|
y(p_y) {}
|
|
|
|
rect_xywh::operator rect_ltrb() {
|
|
rect_ltrb rr(x, y, 0, 0);
|
|
rr.w(w);
|
|
rr.h(h);
|
|
return rr;
|
|
}
|
|
|
|
int rect_xywh::r() const {
|
|
return x + w;
|
|
};
|
|
|
|
int rect_xywh::b() const {
|
|
return y + h;
|
|
}
|
|
|
|
void rect_xywh::r(int p_right) {
|
|
w = p_right - x;
|
|
}
|
|
|
|
void rect_xywh::b(int p_bottom) {
|
|
h = p_bottom - y;
|
|
}
|
|
|
|
int rect_wh::area() {
|
|
return w * h;
|
|
}
|
|
|
|
int rect_wh::perimeter() {
|
|
return 2 * w + 2 * h;
|
|
}
|
|
|
|
rect_xywhf::rect_xywhf(const rect_ltrb &p_rr) :
|
|
rect_xywh(p_rr),
|
|
flipped(false) {}
|
|
rect_xywhf::rect_xywhf(int p_x, int p_y, int p_width, int p_height) :
|
|
rect_xywh(p_x, p_y, p_width, p_height),
|
|
flipped(false) {}
|
|
rect_xywhf::rect_xywhf() :
|
|
flipped(false) {}
|
|
|
|
void rect_xywhf::flip() {
|
|
flipped = !flipped;
|
|
std::swap(w, h);
|
|
}
|