1335 lines
43 KiB
C++
1335 lines
43 KiB
C++
/*
|
|
* 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);
|
|
}
|
|
/*----------------------------------------------------------------*/
|