You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1334 lines
43 KiB
1334 lines
43 KiB
/* |
|
* RectsBinPack.cpp |
|
* |
|
* Copyright (c) 2014-2015 SEACAVE |
|
* |
|
* Author(s): |
|
* |
|
* cDc <cdc.seacave@gmail.com> |
|
* |
|
* |
|
* This program is free software: you can redistribute it and/or modify |
|
* it under the terms of the GNU Affero General Public License as published by |
|
* the Free Software Foundation, either version 3 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU Affero General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU Affero General Public License |
|
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
* |
|
* |
|
* Additional Terms: |
|
* |
|
* You are required to preserve legal notices and author attributions in |
|
* that material or in the Appropriate Legal Notices displayed by works |
|
* containing it. |
|
*/ |
|
|
|
#include "Common.h" |
|
#include "RectsBinPack.h" |
|
|
|
using namespace MVS; |
|
|
|
|
|
// D E F I N E S /////////////////////////////////////////////////// |
|
|
|
// uncomment to enable multi-threading based on OpenMP |
|
#ifdef _USE_OPENMP |
|
#define RECTPACK_USE_OPENMP |
|
#endif |
|
|
|
|
|
// S T R U C T S /////////////////////////////////////////////////// |
|
|
|
MaxRectsBinPack::MaxRectsBinPack() |
|
: |
|
binWidth(0), |
|
binHeight(0) |
|
{ |
|
} |
|
|
|
MaxRectsBinPack::MaxRectsBinPack(int width, int height) |
|
{ |
|
Init(width, height); |
|
} |
|
|
|
void MaxRectsBinPack::Init(int width, int height) |
|
{ |
|
binWidth = width; |
|
binHeight = height; |
|
|
|
usedRectangles.Empty(); |
|
|
|
freeRectangles.Empty(); |
|
freeRectangles.Insert(Rect(0,0, width,height)); |
|
} |
|
|
|
MaxRectsBinPack::Rect MaxRectsBinPack::Insert(int width, int height, FreeRectChoiceHeuristic method) |
|
{ |
|
Rect newNode; |
|
// Unused in this function. We don't need to know the score after finding the position. |
|
int score1, score2; |
|
switch (method) { |
|
case RectBestShortSideFit: newNode = FindPositionForNewNodeBestShortSideFit(width, height, score1, score2); break; |
|
case RectBestLongSideFit: newNode = FindPositionForNewNodeBestLongSideFit(width, height, score2, score1); break; |
|
case RectBestAreaFit: newNode = FindPositionForNewNodeBestAreaFit(width, height, score1, score2); break; |
|
case RectBottomLeftRule: newNode = FindPositionForNewNodeBottomLeft(width, height, score1, score2); break; |
|
case RectContactPointRule: newNode = FindPositionForNewNodeContactPoint(width, height, score1); break; |
|
} |
|
|
|
if (newNode.height == 0) |
|
return newNode; |
|
|
|
PlaceRect(newNode); |
|
return newNode; |
|
} |
|
|
|
MaxRectsBinPack::RectWIdxArr MaxRectsBinPack::Insert(RectWIdxArr& unplacedRects, FreeRectChoiceHeuristic method) |
|
{ |
|
RectWIdxArr placedRects; |
|
while (!unplacedRects.IsEmpty()) { |
|
int bestScore1 = std::numeric_limits<int>::max(); |
|
int bestScore2 = std::numeric_limits<int>::max(); |
|
IDX bestRectIndex = NO_IDX; |
|
Rect bestNode; |
|
|
|
// find the best place to store this rectangle |
|
#ifdef RECTPACK_USE_OPENMP |
|
#pragma omp parallel |
|
{ |
|
int privBestScore1 = std::numeric_limits<int>::max(); |
|
int privBestScore2 = std::numeric_limits<int>::max(); |
|
IDX privBestRectIndex = NO_IDX; |
|
Rect privBestNode; |
|
#pragma omp for nowait |
|
for (int_t i=0; i<(int_t)unplacedRects.size(); ++i) { |
|
int score1, score2; |
|
Rect newNode(ScoreRect(unplacedRects[i].rect.width, unplacedRects[i].rect.height, method, score1, score2)); |
|
if (score1 < privBestScore1 || (score1 == privBestScore1 && score2 < privBestScore2)) { |
|
privBestScore1 = score1; |
|
privBestScore2 = score2; |
|
privBestNode = newNode; |
|
privBestRectIndex = i; |
|
} |
|
} |
|
#pragma omp critical |
|
{ |
|
if (privBestScore1 < bestScore1 || (privBestScore1 == bestScore1 && privBestScore2 < bestScore2)) { |
|
bestScore1 = privBestScore1; |
|
bestScore2 = privBestScore2; |
|
bestNode = privBestNode; |
|
bestRectIndex = privBestRectIndex; |
|
} |
|
} |
|
} |
|
#else |
|
FOREACH(i, unplacedRects) { |
|
int score1, score2; |
|
Rect newNode(ScoreRect(unplacedRects[i].rect.width, unplacedRects[i].rect.height, method, score1, score2)); |
|
if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) { |
|
bestScore1 = score1; |
|
bestScore2 = score2; |
|
bestNode = newNode; |
|
bestRectIndex = i; |
|
} |
|
} |
|
#endif |
|
|
|
// if no place found, return the placed rectangles list |
|
if (bestRectIndex == NO_IDX) { |
|
break; |
|
} |
|
|
|
// store rectangle |
|
PlaceRect(bestNode); |
|
|
|
placedRects.Insert(RectWIdx{bestNode, unplacedRects[bestRectIndex].patchIdx}); |
|
unplacedRects.RemoveAt(bestRectIndex); |
|
} |
|
return placedRects; |
|
} |
|
|
|
void MaxRectsBinPack::PlaceRect(const Rect &node) |
|
{ |
|
for (size_t i = 0; i < freeRectangles.GetSize(); ++i) { |
|
if (SplitFreeNode(freeRectangles[i], node)) |
|
freeRectangles.RemoveAtMove(i--); |
|
} |
|
|
|
PruneFreeList(); |
|
|
|
usedRectangles.Insert(node); |
|
} |
|
|
|
MaxRectsBinPack::Rect MaxRectsBinPack::ScoreRect(int width, int height, FreeRectChoiceHeuristic method, int &score1, int &score2) const |
|
{ |
|
switch (method) { |
|
case RectBestShortSideFit: return FindPositionForNewNodeBestShortSideFit(width, height, score1, score2); |
|
case RectBestLongSideFit: return FindPositionForNewNodeBestLongSideFit(width, height, score2, score1); |
|
case RectBestAreaFit: return FindPositionForNewNodeBestAreaFit(width, height, score1, score2); |
|
case RectBottomLeftRule: return FindPositionForNewNodeBottomLeft(width, height, score1, score2); |
|
case RectContactPointRule: { Rect newNode = FindPositionForNewNodeContactPoint(width, height, score1); |
|
score1 = -score1; // Reverse since we are minimizing, but for contact point score bigger is better. |
|
return newNode; } |
|
default: ASSERT("unknown method" == NULL); return Rect(); |
|
} |
|
} |
|
|
|
/// Computes the ratio of used surface area. |
|
float MaxRectsBinPack::Occupancy() const |
|
{ |
|
unsigned long usedSurfaceArea = 0; |
|
for (size_t i = 0; i < usedRectangles.GetSize(); ++i) |
|
usedSurfaceArea += usedRectangles[i].width * usedRectangles[i].height; |
|
|
|
return (float)usedSurfaceArea / (binWidth * binHeight); |
|
} |
|
|
|
MaxRectsBinPack::Rect MaxRectsBinPack::FindPositionForNewNodeBottomLeft(int width, int height, int &bestY, int &bestX) const |
|
{ |
|
Rect bestNode; |
|
|
|
bestY = std::numeric_limits<int>::max(); |
|
bestX = std::numeric_limits<int>::max(); |
|
|
|
for (size_t i = 0; i < freeRectangles.GetSize(); ++i) { |
|
// Try to place the rectangle in upright (non-flipped) orientation. |
|
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) { |
|
int topSideY = freeRectangles[i].y + height; |
|
if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX)) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = width; |
|
bestNode.height = height; |
|
bestY = topSideY; |
|
bestX = freeRectangles[i].x; |
|
} |
|
} |
|
if (freeRectangles[i].width >= height && freeRectangles[i].height >= width) { |
|
int topSideY = freeRectangles[i].y + width; |
|
if (topSideY < bestY || (topSideY == bestY && freeRectangles[i].x < bestX)) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = height; |
|
bestNode.height = width; |
|
bestY = topSideY; |
|
bestX = freeRectangles[i].x; |
|
} |
|
} |
|
} |
|
return bestNode; |
|
} |
|
|
|
MaxRectsBinPack::Rect MaxRectsBinPack::FindPositionForNewNodeBestShortSideFit(int width, int height, int &bestShortSideFit, int &bestLongSideFit) const |
|
{ |
|
Rect bestNode; |
|
|
|
bestShortSideFit = std::numeric_limits<int>::max(); |
|
bestLongSideFit = std::numeric_limits<int>::max(); |
|
|
|
for (size_t i = 0; i < freeRectangles.GetSize(); ++i) { |
|
// Try to place the rectangle in upright (non-flipped) orientation. |
|
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) { |
|
int leftoverHoriz = ABS(freeRectangles[i].width - width); |
|
int leftoverVert = ABS(freeRectangles[i].height - height); |
|
int shortSideFit = MINF(leftoverHoriz, leftoverVert); |
|
int longSideFit = MAXF(leftoverHoriz, leftoverVert); |
|
|
|
if (shortSideFit < bestShortSideFit || (shortSideFit == bestShortSideFit && longSideFit < bestLongSideFit)) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = width; |
|
bestNode.height = height; |
|
bestShortSideFit = shortSideFit; |
|
bestLongSideFit = longSideFit; |
|
} |
|
} |
|
|
|
if (freeRectangles[i].width >= height && freeRectangles[i].height >= width) { |
|
int flippedLeftoverHoriz = ABS(freeRectangles[i].width - height); |
|
int flippedLeftoverVert = ABS(freeRectangles[i].height - width); |
|
int flippedShortSideFit = MINF(flippedLeftoverHoriz, flippedLeftoverVert); |
|
int flippedLongSideFit = MAXF(flippedLeftoverHoriz, flippedLeftoverVert); |
|
|
|
if (flippedShortSideFit < bestShortSideFit || (flippedShortSideFit == bestShortSideFit && flippedLongSideFit < bestLongSideFit)) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = height; |
|
bestNode.height = width; |
|
bestShortSideFit = flippedShortSideFit; |
|
bestLongSideFit = flippedLongSideFit; |
|
} |
|
} |
|
} |
|
return bestNode; |
|
} |
|
|
|
MaxRectsBinPack::Rect MaxRectsBinPack::FindPositionForNewNodeBestLongSideFit(int width, int height, int &bestShortSideFit, int &bestLongSideFit) const |
|
{ |
|
Rect bestNode; |
|
|
|
bestShortSideFit = std::numeric_limits<int>::max(); |
|
bestLongSideFit = std::numeric_limits<int>::max(); |
|
|
|
for (size_t i = 0; i < freeRectangles.GetSize(); ++i) { |
|
// Try to place the rectangle in upright (non-flipped) orientation. |
|
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) { |
|
int leftoverHoriz = ABS(freeRectangles[i].width - width); |
|
int leftoverVert = ABS(freeRectangles[i].height - height); |
|
int shortSideFit = MINF(leftoverHoriz, leftoverVert); |
|
int longSideFit = MAXF(leftoverHoriz, leftoverVert); |
|
|
|
if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit)) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = width; |
|
bestNode.height = height; |
|
bestShortSideFit = shortSideFit; |
|
bestLongSideFit = longSideFit; |
|
} |
|
} |
|
|
|
if (freeRectangles[i].width >= height && freeRectangles[i].height >= width) { |
|
int leftoverHoriz = ABS(freeRectangles[i].width - height); |
|
int leftoverVert = ABS(freeRectangles[i].height - width); |
|
int shortSideFit = MINF(leftoverHoriz, leftoverVert); |
|
int longSideFit = MAXF(leftoverHoriz, leftoverVert); |
|
|
|
if (longSideFit < bestLongSideFit || (longSideFit == bestLongSideFit && shortSideFit < bestShortSideFit)) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = height; |
|
bestNode.height = width; |
|
bestShortSideFit = shortSideFit; |
|
bestLongSideFit = longSideFit; |
|
} |
|
} |
|
} |
|
return bestNode; |
|
} |
|
|
|
MaxRectsBinPack::Rect MaxRectsBinPack::FindPositionForNewNodeBestAreaFit(int width, int height, int &bestAreaFit, int &bestShortSideFit) const |
|
{ |
|
Rect bestNode; |
|
|
|
bestAreaFit = std::numeric_limits<int>::max(); |
|
bestShortSideFit = std::numeric_limits<int>::max(); |
|
|
|
for (size_t i = 0; i < freeRectangles.GetSize(); ++i) { |
|
int areaFit = freeRectangles[i].width * freeRectangles[i].height - width * height; |
|
|
|
// Try to place the rectangle in upright (non-flipped) orientation. |
|
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) { |
|
int leftoverHoriz = ABS(freeRectangles[i].width - width); |
|
int leftoverVert = ABS(freeRectangles[i].height - height); |
|
int shortSideFit = MINF(leftoverHoriz, leftoverVert); |
|
|
|
if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit)) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = width; |
|
bestNode.height = height; |
|
bestShortSideFit = shortSideFit; |
|
bestAreaFit = areaFit; |
|
} |
|
} |
|
|
|
if (freeRectangles[i].width >= height && freeRectangles[i].height >= width) { |
|
int leftoverHoriz = ABS(freeRectangles[i].width - height); |
|
int leftoverVert = ABS(freeRectangles[i].height - width); |
|
int shortSideFit = MINF(leftoverHoriz, leftoverVert); |
|
|
|
if (areaFit < bestAreaFit || (areaFit == bestAreaFit && shortSideFit < bestShortSideFit)) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = height; |
|
bestNode.height = width; |
|
bestShortSideFit = shortSideFit; |
|
bestAreaFit = areaFit; |
|
} |
|
} |
|
} |
|
return bestNode; |
|
} |
|
|
|
/// Returns 0 if the two intervals i1 and i2 are disjoint, or the length of their overlap otherwise. |
|
int CommonIntervalLength(int i1start, int i1end, int i2start, int i2end) |
|
{ |
|
if (i1end < i2start || i2end < i1start) |
|
return 0; |
|
return MINF(i1end, i2end) - MAXF(i1start, i2start); |
|
} |
|
|
|
int MaxRectsBinPack::ContactPointScoreNode(int x, int y, int width, int height) const |
|
{ |
|
int score = 0; |
|
|
|
if (x == 0 || x + width == binWidth) |
|
score += height; |
|
if (y == 0 || y + height == binHeight) |
|
score += width; |
|
|
|
for (size_t i = 0; i < usedRectangles.GetSize(); ++i) { |
|
if (usedRectangles[i].x == x + width || usedRectangles[i].x + usedRectangles[i].width == x) |
|
score += CommonIntervalLength(usedRectangles[i].y, usedRectangles[i].y + usedRectangles[i].height, y, y + height); |
|
if (usedRectangles[i].y == y + height || usedRectangles[i].y + usedRectangles[i].height == y) |
|
score += CommonIntervalLength(usedRectangles[i].x, usedRectangles[i].x + usedRectangles[i].width, x, x + width); |
|
} |
|
return score; |
|
} |
|
|
|
MaxRectsBinPack::Rect MaxRectsBinPack::FindPositionForNewNodeContactPoint(int width, int height, int &bestContactScore) const |
|
{ |
|
Rect bestNode; |
|
|
|
bestContactScore = -1; |
|
|
|
for (size_t i = 0; i < freeRectangles.GetSize(); ++i) { |
|
// Try to place the rectangle in upright (non-flipped) orientation. |
|
if (freeRectangles[i].width >= width && freeRectangles[i].height >= height) { |
|
int score = ContactPointScoreNode(freeRectangles[i].x, freeRectangles[i].y, width, height); |
|
if (score > bestContactScore) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = width; |
|
bestNode.height = height; |
|
bestContactScore = score; |
|
} |
|
} |
|
if (freeRectangles[i].width >= height && freeRectangles[i].height >= width) { |
|
int score = ContactPointScoreNode(freeRectangles[i].x, freeRectangles[i].y, height, width); |
|
if (score > bestContactScore) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = height; |
|
bestNode.height = width; |
|
bestContactScore = score; |
|
} |
|
} |
|
} |
|
return bestNode; |
|
} |
|
|
|
bool MaxRectsBinPack::SplitFreeNode(Rect freeNode, const Rect &usedNode) |
|
{ |
|
// Test with SAT if the rectangles even intersect. |
|
if (usedNode.x >= freeNode.x + freeNode.width || usedNode.x + usedNode.width <= freeNode.x || |
|
usedNode.y >= freeNode.y + freeNode.height || usedNode.y + usedNode.height <= freeNode.y) |
|
return false; |
|
|
|
if (usedNode.x < freeNode.x + freeNode.width && usedNode.x + usedNode.width > freeNode.x) { |
|
// New node at the top side of the used node. |
|
if (usedNode.y > freeNode.y && usedNode.y < freeNode.y + freeNode.height) { |
|
Rect newNode = freeNode; |
|
newNode.height = usedNode.y - newNode.y; |
|
freeRectangles.Insert(newNode); |
|
} |
|
|
|
// New node at the bottom side of the used node. |
|
if (usedNode.y + usedNode.height < freeNode.y + freeNode.height) { |
|
Rect newNode = freeNode; |
|
newNode.y = usedNode.y + usedNode.height; |
|
newNode.height = freeNode.y + freeNode.height - (usedNode.y + usedNode.height); |
|
freeRectangles.Insert(newNode); |
|
} |
|
} |
|
|
|
if (usedNode.y < freeNode.y + freeNode.height && usedNode.y + usedNode.height > freeNode.y) { |
|
// New node at the left side of the used node. |
|
if (usedNode.x > freeNode.x && usedNode.x < freeNode.x + freeNode.width) { |
|
Rect newNode = freeNode; |
|
newNode.width = usedNode.x - newNode.x; |
|
freeRectangles.Insert(newNode); |
|
} |
|
|
|
// New node at the right side of the used node. |
|
if (usedNode.x + usedNode.width < freeNode.x + freeNode.width) { |
|
Rect newNode = freeNode; |
|
newNode.x = usedNode.x + usedNode.width; |
|
newNode.width = freeNode.x + freeNode.width - (usedNode.x + usedNode.width); |
|
freeRectangles.Insert(newNode); |
|
} |
|
} |
|
|
|
return true; |
|
} |
|
|
|
void MaxRectsBinPack::PruneFreeList() |
|
{ |
|
/* |
|
/// Would be nice to do something like this, to avoid a Theta(n^2) loop through each pair. |
|
/// But unfortunately it doesn't quite cut it, since we also want to detect containment. |
|
/// Perhaps there's another way to do this faster than Theta(n^2). |
|
if (freeRectangles.size() > 0) { |
|
clb::sort::QuickSort(freeRectangles.Begin(), freeRectangles.GetSize(), NodeSortCmp); |
|
for (size_t i = 0; i < freeRectangles.GetSize()-1; ++i) |
|
if (freeRectangles[i].x == freeRectangles[i+1].x && |
|
freeRectangles[i].y == freeRectangles[i+1].y && |
|
freeRectangles[i].width == freeRectangles[i+1].width && |
|
freeRectangles[i].height == freeRectangles[i+1].height) |
|
{ |
|
freeRectangles.RemoveAtMove(i--); |
|
} |
|
} |
|
*/ |
|
|
|
/// Go through each pair and remove any rectangle that is redundant. |
|
for (size_t i = 0; i < freeRectangles.GetSize(); ++i) |
|
for (size_t j = i+1; j < freeRectangles.GetSize(); ++j) { |
|
if (IsContainedIn(freeRectangles[i], freeRectangles[j])) { |
|
freeRectangles.RemoveAtMove(i--); |
|
break; |
|
} |
|
if (IsContainedIn(freeRectangles[j], freeRectangles[i])) { |
|
freeRectangles.RemoveAtMove(j--); |
|
} |
|
} |
|
} |
|
|
|
|
|
// Compute the appropriate texture atlas size |
|
// (an approximation since the packing is a heuristic) |
|
// (if mult > 0, the returned size is a multiple of that value, otherwise is a power of two) |
|
int MaxRectsBinPack::ComputeTextureSize(const RectArr& rects, int mult) |
|
{ |
|
int area(0), maxSizePatch(0); |
|
FOREACHPTR(pRect, rects) { |
|
const Rect& rect = *pRect; |
|
area += rect.area(); |
|
const int sizePatch(MAXF(rect.width, rect.height)); |
|
if (maxSizePatch < sizePatch) |
|
maxSizePatch = sizePatch; |
|
} |
|
// compute the approximate area |
|
// considering the best case scenario for the packing algorithm: 0.9 fill |
|
area = CEIL2INT((1.f/0.9f)*(float)area); |
|
// compute texture size... |
|
const int sizeTex(MAXF(CEIL2INT(SQRT((float)area)), maxSizePatch)); |
|
if (mult > 0) { |
|
// ... as multiple of mult |
|
return ((sizeTex+mult-1)/mult)*mult; |
|
} |
|
// ... as power of two |
|
return POWI(2, CEIL2INT<unsigned>(LOGN((float)sizeTex) / LOGN(2.f))); |
|
} |
|
|
|
int MaxRectsBinPack::ComputeTextureSize(const RectWIdxArr& rectsWIdx, int mult) { |
|
RectArr rects(rectsWIdx.GetSize()); |
|
FOREACH(i, rectsWIdx) |
|
rects[i] = rectsWIdx[i].rect; |
|
return ComputeTextureSize(rects, mult); |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
|
|
// S T R U C T S /////////////////////////////////////////////////// |
|
|
|
GuillotineBinPack::GuillotineBinPack() |
|
: binWidth(0), binHeight(0) |
|
{ |
|
} |
|
|
|
GuillotineBinPack::GuillotineBinPack(int width, int height) |
|
{ |
|
Init(width, height); |
|
} |
|
|
|
void GuillotineBinPack::Init(int width, int height) |
|
{ |
|
binWidth = width; |
|
binHeight = height; |
|
|
|
#ifndef _RELEASE |
|
disjointRects.Clear(); |
|
#endif |
|
|
|
// Clear any memory of previously packed rectangles. |
|
usedRectangles.clear(); |
|
|
|
// We start with a single big free rectangle that spans the whole bin. |
|
Rect n; |
|
n.x = 0; |
|
n.y = 0; |
|
n.width = width; |
|
n.height = height; |
|
|
|
freeRectangles.clear(); |
|
freeRectangles.push_back(n); |
|
} |
|
|
|
GuillotineBinPack::RectWIdxArr GuillotineBinPack::Insert(RectWIdxArr& unplacedRects, bool merge, |
|
FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod) |
|
{ |
|
// Remember variables about the best packing choice we have made so far during the iteration process. |
|
size_t bestFreeRect = 0; |
|
size_t bestRect = 0; |
|
bool bestFlipped = false; |
|
|
|
// Pack rectangles one at a time until we have cleared the rects array of all rectangles or there is no space. |
|
// unplacedRects will get destroyed in the process. |
|
RectWIdxArr placedRects; |
|
while (!unplacedRects.IsEmpty()) { |
|
// Stores the penalty score of the best rectangle placement - bigger=worse, smaller=better. |
|
int bestScore = std::numeric_limits<int>::max(); |
|
|
|
for (size_t i = 0; i < freeRectangles.size(); ++i) { |
|
for (size_t j = 0; j < unplacedRects.GetSize(); ++j) { |
|
Rect currentRect = unplacedRects[j].rect; |
|
// If this rectangle is a perfect match, we pick it instantly. |
|
if (currentRect.width == freeRectangles[i].width && currentRect.height == freeRectangles[i].height) { |
|
bestFreeRect = i; |
|
bestRect = j; |
|
bestFlipped = false; |
|
bestScore = std::numeric_limits<int>::min(); |
|
i = freeRectangles.size(); // Force a jump out of the outer loop as well - we got an instant fit. |
|
break; |
|
} |
|
// If flipping this rectangle is a perfect match, pick that then. |
|
else if (currentRect.height == freeRectangles[i].width && currentRect.width == freeRectangles[i].height) { |
|
bestFreeRect = i; |
|
bestRect = j; |
|
bestFlipped = true; |
|
bestScore = std::numeric_limits<int>::min(); |
|
i = freeRectangles.size(); // Force a jump out of the outer loop as well - we got an instant fit. |
|
break; |
|
} |
|
// Try if we can fit the rectangle upright. |
|
else if (currentRect.width <= freeRectangles[i].width && currentRect.height <= freeRectangles[i].height) { |
|
int score = ScoreByHeuristic(currentRect.width, currentRect.height, freeRectangles[i], rectChoice); |
|
if (score < bestScore) { |
|
bestFreeRect = i; |
|
bestRect = j; |
|
bestFlipped = false; |
|
bestScore = score; |
|
} |
|
} |
|
// If not, then perhaps flipping sideways will make it fit? |
|
else if (currentRect.height <= freeRectangles[i].width && currentRect.width <= freeRectangles[i].height) { |
|
int score = ScoreByHeuristic(currentRect.height, currentRect.width, freeRectangles[i], rectChoice); |
|
if (score < bestScore) { |
|
bestFreeRect = i; |
|
bestRect = j; |
|
bestFlipped = true; |
|
bestScore = score; |
|
} |
|
} |
|
} |
|
} |
|
|
|
// If we didn't manage to find any rectangle to pack, abort. |
|
if (bestScore == std::numeric_limits<int>::max()) { |
|
break; |
|
} |
|
|
|
// Otherwise, we're good to go and do the actual packing. |
|
Rect newNode; |
|
newNode.x = freeRectangles[bestFreeRect].x; |
|
newNode.y = freeRectangles[bestFreeRect].y; |
|
newNode.width = unplacedRects[bestRect].rect.width; |
|
newNode.height = unplacedRects[bestRect].rect.height; |
|
|
|
if (bestFlipped) |
|
std::swap(newNode.width, newNode.height); |
|
|
|
// Remove the free space we lost in the bin. |
|
SplitFreeRectByHeuristic(freeRectangles[bestFreeRect], newNode, splitMethod); |
|
freeRectangles.erase(freeRectangles.begin() + bestFreeRect); |
|
|
|
// Remove the rectangle we just packed from the input list. |
|
placedRects.Insert(MaxRectsBinPack::RectWIdx{newNode, unplacedRects[bestRect].patchIdx}); |
|
unplacedRects.RemoveAt(bestRect); |
|
|
|
// Perform a Rectangle Merge step if desired. |
|
if (merge) |
|
MergeFreeList(); |
|
|
|
// Remember the new used rectangle. |
|
usedRectangles.push_back(newNode); |
|
|
|
// Check that we're really producing correct packings here. |
|
ASSERT(disjointRects.Add(newNode) == true); |
|
} |
|
return placedRects; |
|
} |
|
|
|
GuillotineBinPack::Rect GuillotineBinPack::Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod) |
|
{ |
|
// Find where to put the new rectangle. |
|
size_t freeNodeIndex = 0; |
|
Rect newRect = FindPositionForNewNode(width, height, rectChoice, freeNodeIndex); |
|
|
|
// Abort if we didn't have enough space in the bin. |
|
if (newRect.height == 0) |
|
return newRect; |
|
|
|
// Remove the space that was just consumed by the new rectangle. |
|
SplitFreeRectByHeuristic(freeRectangles[freeNodeIndex], newRect, splitMethod); |
|
freeRectangles.erase(freeRectangles.begin() + freeNodeIndex); |
|
|
|
// Perform a Rectangle Merge step if desired. |
|
if (merge) |
|
MergeFreeList(); |
|
|
|
// Remember the new used rectangle. |
|
usedRectangles.push_back(newRect); |
|
|
|
// Check that we're really producing correct packings here. |
|
ASSERT(disjointRects.Add(newRect) == true); |
|
|
|
return newRect; |
|
} |
|
|
|
/// Computes the ratio of used surface area to the total bin area. |
|
float GuillotineBinPack::Occupancy() const |
|
{ |
|
///\todo The occupancy rate could be cached/tracked incrementally instead |
|
/// of looping through the list of packed rectangles here. |
|
unsigned long usedSurfaceArea = 0; |
|
for (size_t i = 0; i < usedRectangles.size(); ++i) |
|
usedSurfaceArea += usedRectangles[i].width * usedRectangles[i].height; |
|
|
|
return (float)usedSurfaceArea / (binWidth * binHeight); |
|
} |
|
|
|
/// Returns the heuristic score value for placing a rectangle of size width*height into freeRect. Does not try to rotate. |
|
int GuillotineBinPack::ScoreByHeuristic(int width, int height, const Rect &freeRect, FreeRectChoiceHeuristic rectChoice) |
|
{ |
|
switch (rectChoice) { |
|
case RectBestAreaFit: return ScoreBestAreaFit(width, height, freeRect); |
|
case RectBestShortSideFit: return ScoreBestShortSideFit(width, height, freeRect); |
|
case RectBestLongSideFit: return ScoreBestLongSideFit(width, height, freeRect); |
|
case RectWorstAreaFit: return ScoreWorstAreaFit(width, height, freeRect); |
|
case RectWorstShortSideFit: return ScoreWorstShortSideFit(width, height, freeRect); |
|
case RectWorstLongSideFit: return ScoreWorstLongSideFit(width, height, freeRect); |
|
default: ASSERT(false); return std::numeric_limits<int>::max(); |
|
} |
|
} |
|
|
|
int GuillotineBinPack::ScoreBestAreaFit(int width, int height, const Rect &freeRect) |
|
{ |
|
return freeRect.width * freeRect.height - width * height; |
|
} |
|
|
|
int GuillotineBinPack::ScoreBestShortSideFit(int width, int height, const Rect &freeRect) |
|
{ |
|
int leftoverHoriz = abs(freeRect.width - width); |
|
int leftoverVert = abs(freeRect.height - height); |
|
int leftover = MINF(leftoverHoriz, leftoverVert); |
|
return leftover; |
|
} |
|
|
|
int GuillotineBinPack::ScoreBestLongSideFit(int width, int height, const Rect &freeRect) |
|
{ |
|
int leftoverHoriz = abs(freeRect.width - width); |
|
int leftoverVert = abs(freeRect.height - height); |
|
int leftover = MAXF(leftoverHoriz, leftoverVert); |
|
return leftover; |
|
} |
|
|
|
int GuillotineBinPack::ScoreWorstAreaFit(int width, int height, const Rect &freeRect) |
|
{ |
|
return -ScoreBestAreaFit(width, height, freeRect); |
|
} |
|
|
|
int GuillotineBinPack::ScoreWorstShortSideFit(int width, int height, const Rect &freeRect) |
|
{ |
|
return -ScoreBestShortSideFit(width, height, freeRect); |
|
} |
|
|
|
int GuillotineBinPack::ScoreWorstLongSideFit(int width, int height, const Rect &freeRect) |
|
{ |
|
return -ScoreBestLongSideFit(width, height, freeRect); |
|
} |
|
|
|
GuillotineBinPack::Rect GuillotineBinPack::FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, size_t& nodeIndex) |
|
{ |
|
Rect bestNode; |
|
|
|
int bestScore = std::numeric_limits<int>::max(); |
|
|
|
/// Try each free rectangle to find the best one for placement. |
|
for (size_t i = 0; i < freeRectangles.size(); ++i) { |
|
// If this is a perfect fit upright, choose it immediately. |
|
if (width == freeRectangles[i].width && height == freeRectangles[i].height) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = width; |
|
bestNode.height = height; |
|
bestScore = std::numeric_limits<int>::min(); |
|
nodeIndex = i; |
|
ASSERT(disjointRects.Disjoint(bestNode)); |
|
break; |
|
} |
|
// If this is a perfect fit sideways, choose it. |
|
else if (height == freeRectangles[i].width && width == freeRectangles[i].height) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = height; |
|
bestNode.height = width; |
|
bestScore = std::numeric_limits<int>::min(); |
|
nodeIndex = i; |
|
ASSERT(disjointRects.Disjoint(bestNode)); |
|
break; |
|
} |
|
// Does the rectangle fit upright? |
|
else if (width <= freeRectangles[i].width && height <= freeRectangles[i].height) { |
|
int score = ScoreByHeuristic(width, height, freeRectangles[i], rectChoice); |
|
|
|
if (score < bestScore) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = width; |
|
bestNode.height = height; |
|
bestScore = score; |
|
nodeIndex = i; |
|
ASSERT(disjointRects.Disjoint(bestNode)); |
|
} |
|
} |
|
// Does the rectangle fit sideways? |
|
else if (height <= freeRectangles[i].width && width <= freeRectangles[i].height) { |
|
int score = ScoreByHeuristic(height, width, freeRectangles[i], rectChoice); |
|
|
|
if (score < bestScore) { |
|
bestNode.x = freeRectangles[i].x; |
|
bestNode.y = freeRectangles[i].y; |
|
bestNode.width = height; |
|
bestNode.height = width; |
|
bestScore = score; |
|
nodeIndex = i; |
|
ASSERT(disjointRects.Disjoint(bestNode)); |
|
} |
|
} |
|
} |
|
return bestNode; |
|
} |
|
|
|
void GuillotineBinPack::SplitFreeRectByHeuristic(const Rect &freeRect, const Rect &placedRect, GuillotineSplitHeuristic method) |
|
{ |
|
// Compute the lengths of the leftover area. |
|
const int w = freeRect.width - placedRect.width; |
|
const int h = freeRect.height - placedRect.height; |
|
|
|
// Placing placedRect into freeRect results in an L-shaped free area, which must be split into |
|
// two disjoint rectangles. This can be achieved with by splitting the L-shape using a single line. |
|
// We have two choices: horizontal or vertical. |
|
|
|
// Use the given heuristic to decide which choice to make. |
|
|
|
bool splitHorizontal; |
|
switch (method) { |
|
case SplitShorterLeftoverAxis: |
|
// Split along the shorter leftover axis. |
|
splitHorizontal = (w <= h); |
|
break; |
|
case SplitLongerLeftoverAxis: |
|
// Split along the longer leftover axis. |
|
splitHorizontal = (w > h); |
|
break; |
|
case SplitMinimizeArea: |
|
// Maximize the larger area == minimize the smaller area. |
|
// Tries to make the single bigger rectangle. |
|
splitHorizontal = (placedRect.width * h > w * placedRect.height); |
|
break; |
|
case SplitMaximizeArea: |
|
// Maximize the smaller area == minimize the larger area. |
|
// Tries to make the rectangles more even-sized. |
|
splitHorizontal = (placedRect.width * h <= w * placedRect.height); |
|
break; |
|
case SplitShorterAxis: |
|
// Split along the shorter total axis. |
|
splitHorizontal = (freeRect.width <= freeRect.height); |
|
break; |
|
case SplitLongerAxis: |
|
// Split along the longer total axis. |
|
splitHorizontal = (freeRect.width > freeRect.height); |
|
break; |
|
default: |
|
splitHorizontal = true; |
|
ASSERT(false); |
|
} |
|
|
|
// Perform the actual split. |
|
SplitFreeRectAlongAxis(freeRect, placedRect, splitHorizontal); |
|
} |
|
|
|
/// This function will add the two generated rectangles into the freeRectangles array. The caller is expected to |
|
/// remove the original rectangle from the freeRectangles array after that. |
|
void GuillotineBinPack::SplitFreeRectAlongAxis(const Rect &freeRect, const Rect &placedRect, bool splitHorizontal) |
|
{ |
|
// Form the two new rectangles. |
|
Rect bottom; |
|
bottom.x = freeRect.x; |
|
bottom.y = freeRect.y + placedRect.height; |
|
bottom.height = freeRect.height - placedRect.height; |
|
|
|
Rect right; |
|
right.x = freeRect.x + placedRect.width; |
|
right.y = freeRect.y; |
|
right.width = freeRect.width - placedRect.width; |
|
|
|
if (splitHorizontal) { |
|
bottom.width = freeRect.width; |
|
right.height = placedRect.height; |
|
} else // Split vertically |
|
{ |
|
bottom.width = placedRect.width; |
|
right.height = freeRect.height; |
|
} |
|
|
|
// Add the new rectangles into the free rectangle pool if they weren't degenerate. |
|
if (bottom.width > 0 && bottom.height > 0) |
|
freeRectangles.push_back(bottom); |
|
if (right.width > 0 && right.height > 0) |
|
freeRectangles.push_back(right); |
|
|
|
ASSERT(disjointRects.Disjoint(bottom)); |
|
ASSERT(disjointRects.Disjoint(right)); |
|
} |
|
|
|
void GuillotineBinPack::MergeFreeList() |
|
{ |
|
#ifndef _RELEASE |
|
DisjointRectCollection test; |
|
for (size_t i = 0; i < freeRectangles.size(); ++i) |
|
ASSERT(test.Add(freeRectangles[i]) == true); |
|
#endif |
|
|
|
// Do a Theta(n^2) loop to see if any pair of free rectangles could me merged into one. |
|
// Note that we miss any opportunities to merge three rectangles into one. (should call this function again to detect that) |
|
for (size_t i = 0; i < freeRectangles.size(); ++i) |
|
for (size_t j = i+1; j < freeRectangles.size(); ++j) { |
|
if (freeRectangles[i].width == freeRectangles[j].width && freeRectangles[i].x == freeRectangles[j].x) { |
|
if (freeRectangles[i].y == freeRectangles[j].y + freeRectangles[j].height) { |
|
freeRectangles[i].y -= freeRectangles[j].height; |
|
freeRectangles[i].height += freeRectangles[j].height; |
|
freeRectangles.erase(freeRectangles.begin() + j); |
|
--j; |
|
} else if (freeRectangles[i].y + freeRectangles[i].height == freeRectangles[j].y) { |
|
freeRectangles[i].height += freeRectangles[j].height; |
|
freeRectangles.erase(freeRectangles.begin() + j); |
|
--j; |
|
} |
|
} else if (freeRectangles[i].height == freeRectangles[j].height && freeRectangles[i].y == freeRectangles[j].y) { |
|
if (freeRectangles[i].x == freeRectangles[j].x + freeRectangles[j].width) { |
|
freeRectangles[i].x -= freeRectangles[j].width; |
|
freeRectangles[i].width += freeRectangles[j].width; |
|
freeRectangles.erase(freeRectangles.begin() + j); |
|
--j; |
|
} else if (freeRectangles[i].x + freeRectangles[i].width == freeRectangles[j].x) { |
|
freeRectangles[i].width += freeRectangles[j].width; |
|
freeRectangles.erase(freeRectangles.begin() + j); |
|
--j; |
|
} |
|
} |
|
} |
|
|
|
#ifndef _RELEASE |
|
test.Clear(); |
|
for (size_t i = 0; i < freeRectangles.size(); ++i) |
|
ASSERT(test.Add(freeRectangles[i]) == true); |
|
#endif |
|
} |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
|
|
// S T R U C T S /////////////////////////////////////////////////// |
|
|
|
|
|
SkylineBinPack::SkylineBinPack() |
|
:binWidth(0), |
|
binHeight(0) |
|
{ |
|
} |
|
|
|
SkylineBinPack::SkylineBinPack(int width, int height, bool useWasteMap) |
|
{ |
|
Init(width, height, useWasteMap); |
|
} |
|
|
|
void SkylineBinPack::Init(int width, int height, bool useWasteMap_) |
|
{ |
|
binWidth = width; |
|
binHeight = height; |
|
|
|
useWasteMap = useWasteMap_; |
|
|
|
#ifndef _RELEASE |
|
disjointRects.Clear(); |
|
#endif |
|
|
|
usedSurfaceArea = 0; |
|
skyLine.clear(); |
|
SkylineNode node; |
|
node.x = 0; |
|
node.y = 0; |
|
node.width = binWidth; |
|
skyLine.push_back(node); |
|
|
|
if (useWasteMap) { |
|
wasteMap.Init(width, height); |
|
wasteMap.GetFreeRectangles().clear(); |
|
} |
|
} |
|
|
|
SkylineBinPack::RectWIdxArr SkylineBinPack::Insert(RectWIdxArr& unplacedRects, LevelChoiceHeuristic method) |
|
{ |
|
RectWIdxArr placedRects; |
|
while (!unplacedRects.IsEmpty()) { |
|
int bestScore1 = std::numeric_limits<int>::max(); |
|
int bestScore2 = std::numeric_limits<int>::max(); |
|
int bestSkylineIndex = -1; |
|
IDX bestRectIndex = NO_IDX; |
|
Rect bestNode; |
|
|
|
#ifdef RECTPACK_USE_OPENMP |
|
#pragma omp parallel |
|
{ |
|
int privBestScore1 = std::numeric_limits<int>::max(); |
|
int privBestScore2 = std::numeric_limits<int>::max(); |
|
int privBestSkylineIndex = -1; |
|
IDX privBestRectIndex = NO_IDX; |
|
Rect privBestNode; |
|
#pragma omp for nowait |
|
for (int_t i=0; i<(int_t)unplacedRects.GetSize(); ++i) { |
|
int score1, score2, index; |
|
Rect newNode(ScoreRect(unplacedRects[i].rect.width, unplacedRects[i].rect.height, method, score1, score2, index)); |
|
if (score1 < privBestScore1 || (score1 == privBestScore1 && score2 < privBestScore2)) { |
|
privBestScore1 = score1; |
|
privBestScore2 = score2; |
|
privBestNode = newNode; |
|
privBestSkylineIndex = index; |
|
privBestRectIndex = i; |
|
} |
|
} |
|
#pragma omp critical |
|
{ |
|
if (privBestScore1 < bestScore1 || (privBestScore1 == bestScore1 && privBestScore2 < bestScore2)) { |
|
bestScore1 = privBestScore1; |
|
bestScore2 = privBestScore2; |
|
bestNode = privBestNode; |
|
bestSkylineIndex = privBestSkylineIndex; |
|
bestRectIndex = privBestRectIndex; |
|
} |
|
} |
|
} |
|
#else |
|
FOREACH(i, unplacedRects) { |
|
int score1, score2, index; |
|
Rect newNode(ScoreRect(unplacedRects[i].rect.width, unplacedRects[i].rect.height, method, score1, score2, index)); |
|
if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) { |
|
bestNode = newNode; |
|
bestScore1 = score1; |
|
bestScore2 = score2; |
|
bestSkylineIndex = index; |
|
bestRectIndex = i; |
|
} |
|
} |
|
#endif |
|
|
|
// if no place found, give up |
|
if (bestRectIndex == NO_IDX) { |
|
break; |
|
} |
|
|
|
// Perform the actual packing. |
|
#ifndef _RELEASE |
|
ASSERT(disjointRects.Disjoint(bestNode)); |
|
disjointRects.Add(bestNode); |
|
#endif |
|
AddSkylineLevel(bestSkylineIndex, bestNode); |
|
usedSurfaceArea += unplacedRects[bestRectIndex].rect.area(); |
|
|
|
placedRects.Insert(MaxRectsBinPack::RectWIdx{bestNode, unplacedRects[bestRectIndex].patchIdx}); |
|
unplacedRects.RemoveAt(bestRectIndex); |
|
} |
|
return placedRects; |
|
} |
|
|
|
SkylineBinPack::Rect SkylineBinPack::Insert(int width, int height, LevelChoiceHeuristic method) |
|
{ |
|
if (useWasteMap) { |
|
// First try to pack this rectangle into the waste map, if it fits. |
|
Rect node = wasteMap.Insert(width, height, true, GuillotineBinPack::RectBestShortSideFit, |
|
GuillotineBinPack::SplitMaximizeArea); |
|
ASSERT(disjointRects.Disjoint(node)); |
|
if (node.height != 0) { |
|
Rect newNode; |
|
newNode.x = node.x; |
|
newNode.y = node.y; |
|
newNode.width = node.width; |
|
newNode.height = node.height; |
|
usedSurfaceArea += width * height; |
|
#ifndef _RELEASE |
|
ASSERT(disjointRects.Disjoint(newNode)); |
|
disjointRects.Add(newNode); |
|
#endif |
|
return newNode; |
|
} |
|
} |
|
switch (method) { |
|
case LevelBottomLeft: return InsertBottomLeft(width, height); |
|
case LevelMinWasteFit: return InsertMinWaste(width, height); |
|
default: ASSERT(false); return Rect(); |
|
} |
|
} |
|
|
|
SkylineBinPack::Rect SkylineBinPack::ScoreRect(int width, int height, LevelChoiceHeuristic method, int &score1, int &score2, int &index) const |
|
{ |
|
Rect newNode; |
|
switch (method) { |
|
case LevelBottomLeft: newNode = FindPositionForNewNodeBottomLeft(width, height, score1, score2, index); break; |
|
case LevelMinWasteFit: newNode = FindPositionForNewNodeMinWaste(width, height, score2, score1, index); break; |
|
default: ASSERT(false); |
|
} |
|
ASSERT(disjointRects.Disjoint(newNode)); |
|
return newNode; |
|
} |
|
|
|
bool SkylineBinPack::RectangleFits(int skylineNodeIndex, int width, int height, int &y) const |
|
{ |
|
int x = skyLine[skylineNodeIndex].x; |
|
if (x + width > binWidth) |
|
return false; |
|
int widthLeft = width; |
|
int i = skylineNodeIndex; |
|
y = skyLine[skylineNodeIndex].y; |
|
while (widthLeft > 0) { |
|
y = MAXF(y, skyLine[i].y); |
|
if (y + height > binHeight) |
|
return false; |
|
widthLeft -= skyLine[i].width; |
|
++i; |
|
ASSERT(i < (int)skyLine.size() || widthLeft <= 0); |
|
} |
|
return true; |
|
} |
|
|
|
int SkylineBinPack::ComputeWastedArea(int skylineNodeIndex, int width, int height, int y) const |
|
{ |
|
int wastedArea = 0; |
|
const int rectLeft = skyLine[skylineNodeIndex].x; |
|
const int rectRight = rectLeft + width; |
|
for (; skylineNodeIndex < (int)skyLine.size() && skyLine[skylineNodeIndex].x < rectRight; ++skylineNodeIndex) { |
|
if (skyLine[skylineNodeIndex].x >= rectRight || skyLine[skylineNodeIndex].x + skyLine[skylineNodeIndex].width <= rectLeft) |
|
break; |
|
|
|
int leftSide = skyLine[skylineNodeIndex].x; |
|
int rightSide = MINF(rectRight, leftSide + skyLine[skylineNodeIndex].width); |
|
ASSERT(y >= skyLine[skylineNodeIndex].y); |
|
wastedArea += (rightSide - leftSide) * (y - skyLine[skylineNodeIndex].y); |
|
} |
|
return wastedArea; |
|
} |
|
|
|
bool SkylineBinPack::RectangleFits(int skylineNodeIndex, int width, int height, int &y, int &wastedArea) const |
|
{ |
|
bool fits = RectangleFits(skylineNodeIndex, width, height, y); |
|
if (fits) |
|
wastedArea = ComputeWastedArea(skylineNodeIndex, width, height, y); |
|
|
|
return fits; |
|
} |
|
|
|
void SkylineBinPack::AddWasteMapArea(int skylineNodeIndex, int width, int height, int y) |
|
{ |
|
// int wastedArea = 0; // unused |
|
const int rectLeft = skyLine[skylineNodeIndex].x; |
|
const int rectRight = rectLeft + width; |
|
for (; skylineNodeIndex < (int)skyLine.size() && skyLine[skylineNodeIndex].x < rectRight; ++skylineNodeIndex) { |
|
if (skyLine[skylineNodeIndex].x >= rectRight || skyLine[skylineNodeIndex].x + skyLine[skylineNodeIndex].width <= rectLeft) |
|
break; |
|
|
|
int leftSide = skyLine[skylineNodeIndex].x; |
|
int rightSide = MINF(rectRight, leftSide + skyLine[skylineNodeIndex].width); |
|
ASSERT(y >= skyLine[skylineNodeIndex].y); |
|
|
|
Rect waste; |
|
waste.x = leftSide; |
|
waste.y = skyLine[skylineNodeIndex].y; |
|
waste.width = rightSide - leftSide; |
|
waste.height = y - skyLine[skylineNodeIndex].y; |
|
|
|
ASSERT(disjointRects.Disjoint(waste)); |
|
wasteMap.GetFreeRectangles().push_back(waste); |
|
} |
|
} |
|
|
|
void SkylineBinPack::AddSkylineLevel(int skylineNodeIndex, const Rect &rect) |
|
{ |
|
// First track all wasted areas and mark them into the waste map if we're using one. |
|
if (useWasteMap) |
|
AddWasteMapArea(skylineNodeIndex, rect.width, rect.height, rect.y); |
|
|
|
SkylineNode newNode; |
|
newNode.x = rect.x; |
|
newNode.y = rect.y + rect.height; |
|
newNode.width = rect.width; |
|
skyLine.insert(skyLine.begin() + skylineNodeIndex, newNode); |
|
|
|
ASSERT(newNode.x + newNode.width <= binWidth); |
|
ASSERT(newNode.y <= binHeight); |
|
|
|
for (size_t i = skylineNodeIndex+1; i < skyLine.size(); ++i) { |
|
ASSERT(skyLine[i-1].x <= skyLine[i].x); |
|
|
|
if (skyLine[i].x < skyLine[i-1].x + skyLine[i-1].width) { |
|
int shrink = skyLine[i-1].x + skyLine[i-1].width - skyLine[i].x; |
|
|
|
skyLine[i].x += shrink; |
|
skyLine[i].width -= shrink; |
|
|
|
if (skyLine[i].width <= 0) { |
|
skyLine.erase(skyLine.begin() + i); |
|
--i; |
|
} else |
|
break; |
|
} else |
|
break; |
|
} |
|
MergeSkylines(); |
|
} |
|
|
|
void SkylineBinPack::MergeSkylines() |
|
{ |
|
for (size_t i = 0; i < skyLine.size()-1; ++i) |
|
if (skyLine[i].y == skyLine[i+1].y) { |
|
skyLine[i].width += skyLine[i+1].width; |
|
skyLine.erase(skyLine.begin() + (i+1)); |
|
--i; |
|
} |
|
} |
|
|
|
SkylineBinPack::Rect SkylineBinPack::InsertBottomLeft(int width, int height) |
|
{ |
|
int bestHeight; |
|
int bestWidth; |
|
int bestIndex; |
|
Rect newNode = FindPositionForNewNodeBottomLeft(width, height, bestHeight, bestWidth, bestIndex); |
|
|
|
if (bestIndex != -1) { |
|
ASSERT(disjointRects.Disjoint(newNode)); |
|
|
|
// Perform the actual packing. |
|
AddSkylineLevel(bestIndex, newNode); |
|
|
|
usedSurfaceArea += width * height; |
|
#ifndef _RELEASE |
|
disjointRects.Add(newNode); |
|
#endif |
|
} |
|
|
|
return newNode; |
|
} |
|
|
|
SkylineBinPack::Rect SkylineBinPack::FindPositionForNewNodeBottomLeft(int width, int height, int &bestHeight, int &bestWidth, int &bestIndex) const |
|
{ |
|
bestHeight = std::numeric_limits<int>::max(); |
|
bestIndex = -1; |
|
// Used to break ties if there are nodes at the same level. Then pick the narrowest one. |
|
bestWidth = std::numeric_limits<int>::max(); |
|
Rect newNode; |
|
for (int i = 0; i < (int)skyLine.size(); ++i) { |
|
int y; |
|
if (RectangleFits(i, width, height, y)) { |
|
if (y + height < bestHeight || (y + height == bestHeight && skyLine[i].width < bestWidth)) { |
|
bestHeight = y + height; |
|
bestIndex = i; |
|
bestWidth = skyLine[i].width; |
|
newNode.x = skyLine[i].x; |
|
newNode.y = y; |
|
newNode.width = width; |
|
newNode.height = height; |
|
ASSERT(disjointRects.Disjoint(newNode)); |
|
} |
|
} |
|
if (RectangleFits(i, height, width, y)) { |
|
if (y + width < bestHeight || (y + width == bestHeight && skyLine[i].width < bestWidth)) { |
|
bestHeight = y + width; |
|
bestIndex = i; |
|
bestWidth = skyLine[i].width; |
|
newNode.x = skyLine[i].x; |
|
newNode.y = y; |
|
newNode.width = height; |
|
newNode.height = width; |
|
ASSERT(disjointRects.Disjoint(newNode)); |
|
} |
|
} |
|
} |
|
|
|
return newNode; |
|
} |
|
|
|
SkylineBinPack::Rect SkylineBinPack::InsertMinWaste(int width, int height) |
|
{ |
|
int bestHeight; |
|
int bestWastedArea; |
|
int bestIndex; |
|
Rect newNode = FindPositionForNewNodeMinWaste(width, height, bestHeight, bestWastedArea, bestIndex); |
|
|
|
if (bestIndex != -1) { |
|
ASSERT(disjointRects.Disjoint(newNode)); |
|
|
|
// Perform the actual packing. |
|
AddSkylineLevel(bestIndex, newNode); |
|
|
|
usedSurfaceArea += width * height; |
|
#ifndef _RELEASE |
|
disjointRects.Add(newNode); |
|
#endif |
|
} |
|
|
|
return newNode; |
|
} |
|
|
|
SkylineBinPack::Rect SkylineBinPack::FindPositionForNewNodeMinWaste(int width, int height, int &bestHeight, int &bestWastedArea, int &bestIndex) const |
|
{ |
|
bestHeight = std::numeric_limits<int>::max(); |
|
bestWastedArea = std::numeric_limits<int>::max(); |
|
bestIndex = -1; |
|
Rect newNode; |
|
for (int i = 0; i < (int)skyLine.size(); ++i) { |
|
int y; |
|
int wastedArea; |
|
|
|
if (RectangleFits(i, width, height, y, wastedArea)) { |
|
if (wastedArea < bestWastedArea || (wastedArea == bestWastedArea && y + height < bestHeight)) { |
|
bestHeight = y + height; |
|
bestWastedArea = wastedArea; |
|
bestIndex = i; |
|
newNode.x = skyLine[i].x; |
|
newNode.y = y; |
|
newNode.width = width; |
|
newNode.height = height; |
|
ASSERT(disjointRects.Disjoint(newNode)); |
|
} |
|
} |
|
if (RectangleFits(i, height, width, y, wastedArea)) { |
|
if (wastedArea < bestWastedArea || (wastedArea == bestWastedArea && y + width < bestHeight)) { |
|
bestHeight = y + width; |
|
bestWastedArea = wastedArea; |
|
bestIndex = i; |
|
newNode.x = skyLine[i].x; |
|
newNode.y = y; |
|
newNode.width = height; |
|
newNode.height = width; |
|
ASSERT(disjointRects.Disjoint(newNode)); |
|
} |
|
} |
|
} |
|
|
|
return newNode; |
|
} |
|
|
|
/// Computes the ratio of used surface area. |
|
float SkylineBinPack::Occupancy() const |
|
{ |
|
return (float)usedSurfaceArea / (binWidth * binHeight); |
|
} |
|
/*----------------------------------------------------------------*/
|
|
|