diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ff47ce03e..087794b09 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1228,8 +1228,6 @@ set (PCH_SOURCES sound/wildmidi/wildmidi_lib.cpp sound/wildmidi/wm_error.cpp events.cpp - GuillotineBinPack.cpp - SkylineBinPack.cpp ) enable_precompiled_headers( g_pch.h PCH_SOURCES ) diff --git a/src/GuillotineBinPack.cpp b/src/GuillotineBinPack.cpp deleted file mode 100644 index 57dce4501..000000000 --- a/src/GuillotineBinPack.cpp +++ /dev/null @@ -1,643 +0,0 @@ -/** @file GuillotineBinPack.cpp - @author Jukka Jylänki - - @brief Implements different bin packer algorithms that use the GUILLOTINE data structure. - - This work is released to Public Domain, do whatever you want with it. -*/ - -#include -#include -#include "templates.h" -#include "GuillotineBinPack.h" - -using namespace std; - -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; - -#ifdef _DEBUG - 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(n); -} - -void GuillotineBinPack::Insert(TArray &rects, TArray &dst, bool merge, - FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod) -{ - dst.Clear(); - - // Remember variables about the best packing choice we have made so far during the iteration process. - int bestFreeRect = 0; - int bestRect = 0; - bool bestFlipped = false; - - // Pack rectangles one at a time until we have cleared the rects array of all rectangles. - // rects will get destroyed in the process. - while(rects.Size() > 0) - { - // Stores the penalty score of the best rectangle placement - bigger=worse, smaller=better. - int bestScore = INT_MAX; - - for(unsigned i = 0; i < freeRectangles.Size(); ++i) - { - for(unsigned j = 0; j < rects.Size(); ++j) - { - // If this rectangle is a perfect match, we pick it instantly. - if (rects[j].width == freeRectangles[i].width && rects[j].height == freeRectangles[i].height) - { - bestFreeRect = i; - bestRect = j; - bestFlipped = false; - bestScore = 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 (rects[j].height == freeRectangles[i].width && rects[j].width == freeRectangles[i].height) - { - bestFreeRect = i; - bestRect = j; - bestFlipped = true; - bestScore = 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 (rects[j].width <= freeRectangles[i].width && rects[j].height <= freeRectangles[i].height) - { - int score = ScoreByHeuristic(rects[j].width, rects[j].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 (rects[j].height <= freeRectangles[i].width && rects[j].width <= freeRectangles[i].height) - { - int score = ScoreByHeuristic(rects[j].height, rects[j].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 == INT_MAX) - return; - - // 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 = rects[bestRect].width; - newNode.height = rects[bestRect].height; - - if (bestFlipped) - std::swap(newNode.width, newNode.height); - - // Remove the free space we lost in the bin. - SplitFreeRectByHeuristic(freeRectangles[bestFreeRect], newNode, splitMethod); - freeRectangles.Delete(bestFreeRect); - - // Remove the rectangle we just packed from the input list. - rects.Delete(bestRect); - - // Perform a Rectangle Merge step if desired. - if (merge) - MergeFreeList(); - - // Remember the new used rectangle. - usedRectangles.Push(newNode); - - // Check that we're really producing correct packings here. -#ifdef _DEBUG - assert(disjointRects.Add(newNode) == true); -#endif - } -} - -/// @return True if r fits inside freeRect (possibly rotated). -bool Fits(const RectSize &r, const Rect &freeRect) -{ - return (r.width <= freeRect.width && r.height <= freeRect.height) || - (r.height <= freeRect.width && r.width <= freeRect.height); -} - -/// @return True if r fits perfectly inside freeRect, i.e. the leftover area is 0. -bool FitsPerfectly(const RectSize &r, const Rect &freeRect) -{ - return (r.width == freeRect.width && r.height == freeRect.height) || - (r.height == freeRect.width && r.width == freeRect.height); -} - -/* -// A helper function for GUILLOTINE-MAXFITTING. Counts how many rectangles fit into the given rectangle -// after it has been split. -void CountNumFitting(const Rect &freeRect, int width, int height, const TArray &rects, - int usedRectIndex, bool splitHorizontal, int &score1, int &score2) -{ - const int w = freeRect.width - width; - const int h = freeRect.height - height; - - Rect bottom; - bottom.x = freeRect.x; - bottom.y = freeRect.y + height; - bottom.height = h; - - Rect right; - right.x = freeRect.x + width; - right.y = freeRect.y; - right.width = w; - - if (splitHorizontal) - { - bottom.width = freeRect.width; - right.height = height; - } - else // Split vertically - { - bottom.width = width; - right.height = freeRect.height; - } - - int fitBottom = 0; - int fitRight = 0; - for(size_t i = 0; i < rects.size(); ++i) - if (i != usedRectIndex) - { - if (FitsPerfectly(rects[i], bottom)) - fitBottom |= 0x10000000; - if (FitsPerfectly(rects[i], right)) - fitRight |= 0x10000000; - - if (Fits(rects[i], bottom)) - ++fitBottom; - if (Fits(rects[i], right)) - ++fitRight; - } - - score1 = min(fitBottom, fitRight); - score2 = max(fitBottom, fitRight); -} -*/ -/* -// Implements GUILLOTINE-MAXFITTING, an experimental heuristic that's really cool but didn't quite work in practice. -void GuillotineBinPack::InsertMaxFitting(TArray &rects, TArray &dst, bool merge, - FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod) -{ - dst.clear(); - int bestRect = 0; - bool bestFlipped = false; - bool bestSplitHorizontal = false; - - // Pick rectangles one at a time and pack the one that leaves the most choices still open. - while(rects.size() > 0 && freeRectangles.size() > 0) - { - int bestScore1 = -1; - int bestScore2 = -1; - - ///\todo Different sort predicates. - clb::sort::QuickSort(&freeRectangles[0], freeRectangles.size(), CompareRectShortSide); - - Rect &freeRect = freeRectangles[0]; - - for(size_t j = 0; j < rects.size(); ++j) - { - int score1; - int score2; - - if (rects[j].width == freeRect.width && rects[j].height == freeRect.height) - { - bestRect = j; - bestFlipped = false; - bestScore1 = bestScore2 = std::numeric_limits::max(); - break; - } - else if (rects[j].width <= freeRect.width && rects[j].height <= freeRect.height) - { - CountNumFitting(freeRect, rects[j].width, rects[j].height, rects, j, false, score1, score2); - - if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2)) - { - bestRect = j; - bestScore1 = score1; - bestScore2 = score2; - bestFlipped = false; - bestSplitHorizontal = false; - } - - CountNumFitting(freeRect, rects[j].width, rects[j].height, rects, j, true, score1, score2); - - if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2)) - { - bestRect = j; - bestScore1 = score1; - bestScore2 = score2; - bestFlipped = false; - bestSplitHorizontal = true; - } - } - - if (rects[j].height == freeRect.width && rects[j].width == freeRect.height) - { - bestRect = j; - bestFlipped = true; - bestScore1 = bestScore2 = std::numeric_limits::max(); - break; - } - else if (rects[j].height <= freeRect.width && rects[j].width <= freeRect.height) - { - CountNumFitting(freeRect, rects[j].height, rects[j].width, rects, j, false, score1, score2); - - if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2)) - { - bestRect = j; - bestScore1 = score1; - bestScore2 = score2; - bestFlipped = true; - bestSplitHorizontal = false; - } - - CountNumFitting(freeRect, rects[j].height, rects[j].width, rects, j, true, score1, score2); - - if (score1 > bestScore1 || (score1 == bestScore1 && score2 > bestScore2)) - { - bestRect = j; - bestScore1 = score1; - bestScore2 = score2; - bestFlipped = true; - bestSplitHorizontal = true; - } - } - } - - if (bestScore1 >= 0) - { - Rect newNode; - newNode.x = freeRect.x; - newNode.y = freeRect.y; - newNode.width = rects[bestRect].width; - newNode.height = rects[bestRect].height; - if (bestFlipped) - std::swap(newNode.width, newNode.height); - - assert(disjointRects.Disjoint(newNode)); - SplitFreeRectAlongAxis(freeRect, newNode, bestSplitHorizontal); - - rects.erase(rects.begin() + bestRect); - - if (merge) - MergeFreeList(); - - usedRectangles.push_back(newNode); -#ifdef _DEBUG - disjointRects.Add(newNode); -#endif - } - - freeRectangles.erase(freeRectangles.begin()); - } -} -*/ - -Rect GuillotineBinPack::Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice, - GuillotineSplitHeuristic splitMethod) -{ - // Find where to put the new rectangle. - int 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.Delete(freeNodeIndex); - - // Perform a Rectangle Merge step if desired. - if (merge) - MergeFreeList(); - - // Remember the new used rectangle. - usedRectangles.Push(newRect); - - // Check that we're really producing correct packings here. -#ifdef _DEBUG - assert(disjointRects.Add(newRect) == true); -#endif - 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(unsigned 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 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 = MIN(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 = MAX(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); -} - -Rect GuillotineBinPack::FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, int *nodeIndex) -{ - Rect bestNode; - memset(&bestNode, 0, sizeof(Rect)); - - int bestScore = INT_MAX; - - /// Try each free rectangle to find the best one for placement. - for(unsigned 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 = INT_MIN; - *nodeIndex = i; -#ifdef _DEBUG - assert(disjointRects.Disjoint(bestNode)); -#endif - 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 = 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; -#ifdef _DEBUG - assert(disjointRects.Disjoint(bestNode)); -#endif - } - } - // 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(bottom); - if (right.width > 0 && right.height > 0) - freeRectangles.Push(right); - -#ifdef _DEBUG - assert(disjointRects.Disjoint(bottom)); - assert(disjointRects.Disjoint(right)); -#endif -} - -void GuillotineBinPack::MergeFreeList() -{ -#ifdef _DEBUG - DisjointRectCollection test; - for(unsigned 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(unsigned i = 0; i < freeRectangles.Size(); ++i) - for(unsigned 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.Delete(j); - --j; - } - else if (freeRectangles[i].y + freeRectangles[i].height == freeRectangles[j].y) - { - freeRectangles[i].height += freeRectangles[j].height; - freeRectangles.Delete(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.Delete(j); - --j; - } - else if (freeRectangles[i].x + freeRectangles[i].width == freeRectangles[j].x) - { - freeRectangles[i].width += freeRectangles[j].width; - freeRectangles.Delete(j); - --j; - } - } - } - -#ifdef _DEBUG - test.Clear(); - for(unsigned i = 0; i < freeRectangles.Size(); ++i) - assert(test.Add(freeRectangles[i]) == true); -#endif -} diff --git a/src/GuillotineBinPack.h b/src/GuillotineBinPack.h deleted file mode 100644 index 54cd77a9e..000000000 --- a/src/GuillotineBinPack.h +++ /dev/null @@ -1,135 +0,0 @@ -/** @file GuillotineBinPack.h - @author Jukka Jylänki - - @brief Implements different bin packer algorithms that use the GUILLOTINE data structure. - - This work is released to Public Domain, do whatever you want with it. -*/ -#pragma once - -#include "tarray.h" - -#include "Rect.h" - -/** GuillotineBinPack implements different variants of bin packer algorithms that use the GUILLOTINE data structure - to keep track of the free space of the bin where rectangles may be placed. */ -class GuillotineBinPack -{ -public: - /// The initial bin size will be (0,0). Call Init to set the bin size. - GuillotineBinPack(); - - /// Initializes a new bin of the given size. - GuillotineBinPack(int width, int height); - - /// (Re)initializes the packer to an empty bin of width x height units. Call whenever - /// you need to restart with a new bin. - void Init(int width, int height); - - /// Specifies the different choice heuristics that can be used when deciding which of the free subrectangles - /// to place the to-be-packed rectangle into. - enum FreeRectChoiceHeuristic - { - RectBestAreaFit, ///< -BAF - RectBestShortSideFit, ///< -BSSF - RectBestLongSideFit, ///< -BLSF - RectWorstAreaFit, ///< -WAF - RectWorstShortSideFit, ///< -WSSF - RectWorstLongSideFit ///< -WLSF - }; - - /// Specifies the different choice heuristics that can be used when the packer needs to decide whether to - /// subdivide the remaining free space in horizontal or vertical direction. - enum GuillotineSplitHeuristic - { - SplitShorterLeftoverAxis, ///< -SLAS - SplitLongerLeftoverAxis, ///< -LLAS - SplitMinimizeArea, ///< -MINAS, Try to make a single big rectangle at the expense of making the other small. - SplitMaximizeArea, ///< -MAXAS, Try to make both remaining rectangles as even-sized as possible. - SplitShorterAxis, ///< -SAS - SplitLongerAxis ///< -LAS - }; - - /// Inserts a single rectangle into the bin. The packer might rotate the rectangle, in which case the returned - /// struct will have the width and height values swapped. - /// @param merge If true, performs free Rectangle Merge procedure after packing the new rectangle. This procedure - /// tries to defragment the list of disjoint free rectangles to improve packing performance, but also takes up - /// some extra time. - /// @param rectChoice The free rectangle choice heuristic rule to use. - /// @param splitMethod The free rectangle split heuristic rule to use. - Rect Insert(int width, int height, bool merge, FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod); - - /// Inserts a list of rectangles into the bin. - /// @param rects The list of rectangles to add. This list will be destroyed in the packing process. - /// @param dst The outputted list of rectangles. Note that the indices will not correspond to the input indices. - /// @param merge If true, performs Rectangle Merge operations during the packing process. - /// @param rectChoice The free rectangle choice heuristic rule to use. - /// @param splitMethod The free rectangle split heuristic rule to use. - void Insert(TArray &rects, TArray &dst, bool merge, - FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod); - -// Implements GUILLOTINE-MAXFITTING, an experimental heuristic that's really cool but didn't quite work in practice. -// void InsertMaxFitting(TArray &rects, TArray &dst, bool merge, -// FreeRectChoiceHeuristic rectChoice, GuillotineSplitHeuristic splitMethod); - - /// Computes the ratio of used/total surface area. 0.00 means no space is yet used, 1.00 means the whole bin is used. - float Occupancy() const; - - /// Returns the internal list of disjoint rectangles that track the free area of the bin. You may alter this vector - /// any way desired, as long as the end result still is a list of disjoint rectangles. - TArray &GetFreeRectangles() { return freeRectangles; } - - /// Returns the list of packed rectangles. You may alter this vector at will, for example, you can move a Rect from - /// this list to the Free Rectangles list to free up space on-the-fly, but notice that this causes fragmentation. - TArray &GetUsedRectangles() { return usedRectangles; } - - /// Performs a Rectangle Merge operation. This procedure looks for adjacent free rectangles and merges them if they - /// can be represented with a single rectangle. Takes up Theta(|freeRectangles|^2) time. - void MergeFreeList(); - -#ifdef _DEBUG - void DelDisjoint(const Rect &r) { disjointRects.Del(r); } -#endif - -private: - int binWidth; - int binHeight; - - /// Stores a list of all the rectangles that we have packed so far. This is used only to compute the Occupancy ratio, - /// so if you want to have the packer consume less memory, this can be removed. - TArray usedRectangles; - - /// Stores a list of rectangles that represents the free area of the bin. This rectangles in this list are disjoint. - TArray freeRectangles; - -#ifdef _DEBUG - /// Used to track that the packer produces proper packings. - DisjointRectCollection disjointRects; -#endif - - /// Goes through the list of free rectangles and finds the best one to place a rectangle of given size into. - /// Running time is Theta(|freeRectangles|). - /// @param nodeIndex [out] The index of the free rectangle in the freeRectangles array into which the new - /// rect was placed. - /// @return A Rect structure that represents the placement of the new rect into the best free rectangle. - Rect FindPositionForNewNode(int width, int height, FreeRectChoiceHeuristic rectChoice, int *nodeIndex); - - static int ScoreByHeuristic(int width, int height, const Rect &freeRect, FreeRectChoiceHeuristic rectChoice); - // The following functions compute (penalty) score values if a rect of the given size was placed into the - // given free rectangle. In these score values, smaller is better. - - static int ScoreBestAreaFit(int width, int height, const Rect &freeRect); - static int ScoreBestShortSideFit(int width, int height, const Rect &freeRect); - static int ScoreBestLongSideFit(int width, int height, const Rect &freeRect); - - static int ScoreWorstAreaFit(int width, int height, const Rect &freeRect); - static int ScoreWorstShortSideFit(int width, int height, const Rect &freeRect); - static int ScoreWorstLongSideFit(int width, int height, const Rect &freeRect); - - /// Splits the given L-shaped free rectangle into two new free rectangles after placedRect has been placed into it. - /// Determines the split axis by using the given heuristic. - void SplitFreeRectByHeuristic(const Rect &freeRect, const Rect &placedRect, GuillotineSplitHeuristic method); - - /// Splits the given L-shaped free rectangle into two new free rectangles along the given fixed split axis. - void SplitFreeRectAlongAxis(const Rect &freeRect, const Rect &placedRect, bool splitHorizontal); -}; diff --git a/src/Rect.h b/src/Rect.h deleted file mode 100644 index 8b7ba1e2a..000000000 --- a/src/Rect.h +++ /dev/null @@ -1,94 +0,0 @@ -/** @file Rect.h - @author Jukka Jylänki - - This work is released to Public Domain, do whatever you want with it. -*/ -#pragma once - -#include - -struct RectSize -{ - int width; - int height; -}; - -struct Rect -{ - int x; - int y; - int width; - int height; -}; - -/// Performs a lexicographic compare on (rect short side, rect long side). -/// @return -1 if the smaller side of a is shorter than the smaller side of b, 1 if the other way around. -/// If they are equal, the larger side length is used as a tie-breaker. -/// If the rectangles are of same size, returns 0. -int CompareRectShortSide(const Rect &a, const Rect &b); - -/// Performs a lexicographic compare on (x, y, width, height). -int NodeSortCmp(const Rect &a, const Rect &b); - -/// Returns true if a is contained in b. -bool IsContainedIn(const Rect &a, const Rect &b); - -#ifdef _DEBUG -class DisjointRectCollection -{ -public: - TArray rects; - - bool Add(const Rect &r) - { - // Degenerate rectangles are ignored. - if (r.width == 0 || r.height == 0) - return true; - - if (!Disjoint(r)) - return false; - rects.Push(r); - return true; - } - - bool Del(const Rect &r) - { - for(unsigned i = 0; i < rects.Size(); ++i) - { - if(r.x == rects[i].x && r.y == rects[i].y && r.width == rects[i].width && r.height == rects[i].height) - { - rects.Delete(i); - return true; - } - } - return false; - } - - void Clear() - { - rects.Clear(); - } - - bool Disjoint(const Rect &r) const - { - // Degenerate rectangles are ignored. - if (r.width == 0 || r.height == 0) - return true; - - for(unsigned i = 0; i < rects.Size(); ++i) - if (!Disjoint(rects[i], r)) - return false; - return true; - } - - static bool Disjoint(const Rect &a, const Rect &b) - { - if (a.x + a.width <= b.x || - b.x + b.width <= a.x || - a.y + a.height <= b.y || - b.y + b.height <= a.y) - return true; - return false; - } -}; -#endif diff --git a/src/SkylineBinPack.cpp b/src/SkylineBinPack.cpp deleted file mode 100644 index d63610370..000000000 --- a/src/SkylineBinPack.cpp +++ /dev/null @@ -1,412 +0,0 @@ -/** @file SkylineBinPack.cpp - @author Jukka Jylänki - - @brief Implements different bin packer algorithms that use the SKYLINE data structure. - - This work is released to Public Domain, do whatever you want with it. -*/ - -#include -#include -#include "templates.h" - -#include "SkylineBinPack.h" - -using namespace std; - -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_; - -#ifdef _DEBUG - disjointRects.Clear(); -#endif - - usedSurfaceArea = 0; - skyLine.Clear(); - SkylineNode node; - node.x = 0; - node.y = 0; - node.width = binWidth; - skyLine.Push(node); - - if (useWasteMap) - { - wasteMap.Init(width, height); - wasteMap.GetFreeRectangles().Clear(); - } -} - -void SkylineBinPack::Insert(TArray &rects, TArray &dst) -{ - dst.Clear(); - - while(rects.Size() > 0) - { - Rect bestNode; - int bestScore1 = INT_MAX; - int bestScore2 = INT_MAX; - int bestSkylineIndex = -1; - int bestRectIndex = -1; - for(unsigned i = 0; i < rects.Size(); ++i) - { - Rect newNode; - int score1; - int score2; - int index; - newNode = FindPositionForNewNodeMinWaste(rects[i].width, rects[i].height, score2, score1, index); -#ifdef _DEBUG - assert(disjointRects.Disjoint(newNode)); -#endif - if (newNode.height != 0) - { - if (score1 < bestScore1 || (score1 == bestScore1 && score2 < bestScore2)) - { - bestNode = newNode; - bestScore1 = score1; - bestScore2 = score2; - bestSkylineIndex = index; - bestRectIndex = i; - } - } - } - - if (bestRectIndex == -1) - return; - - // Perform the actual packing. -#ifdef _DEBUG - assert(disjointRects.Disjoint(bestNode)); - disjointRects.Add(bestNode); -#endif - AddSkylineLevel(bestSkylineIndex, bestNode); - usedSurfaceArea += rects[bestRectIndex].width * rects[bestRectIndex].height; - rects.Delete(bestRectIndex); - dst.Push(bestNode); - } -} - -Rect SkylineBinPack::Insert(int width, int height) -{ - // First try to pack this rectangle into the waste map, if it fits. - Rect node = wasteMap.Insert(width, height, true, GuillotineBinPack::RectBestShortSideFit, - GuillotineBinPack::SplitMaximizeArea); -#ifdef _DEBUG - assert(disjointRects.Disjoint(node)); -#endif - - 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; -#ifdef _DEBUG - assert(disjointRects.Disjoint(newNode)); - disjointRects.Add(newNode); -#endif - return newNode; - } - - return InsertBottomLeft(width, height); -} - -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 = MAX(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 = MIN(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; - 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 = MIN(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; - -#ifdef _DEBUG - assert(disjointRects.Disjoint(waste)); -#endif - wasteMap.GetFreeRectangles().Push(waste); - } -} - -void SkylineBinPack::AddWaste(const Rect &waste) -{ - wasteMap.GetFreeRectangles().Push(waste); -#ifdef _DEBUG - disjointRects.Del(waste); - wasteMap.DelDisjoint(waste); -#endif -} - -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(skylineNodeIndex, newNode); - - assert(newNode.x + newNode.width <= binWidth); - assert(newNode.y <= binHeight); - - for(unsigned 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.Delete(i); - --i; - } - else - break; - } - else - break; - } - MergeSkylines(); -} - -void SkylineBinPack::MergeSkylines() -{ - for(unsigned i = 0; i < skyLine.Size()-1; ++i) - if (skyLine[i].y == skyLine[i+1].y) - { - skyLine[i].width += skyLine[i+1].width; - skyLine.Delete(i+1); - --i; - } -} - -Rect SkylineBinPack::InsertBottomLeft(int width, int height) -{ - int bestHeight; - int bestWidth; - int bestIndex; - Rect newNode = FindPositionForNewNodeBottomLeft(width, height, bestHeight, bestWidth, bestIndex); - - if (bestIndex != -1) - { -#ifdef _DEBUG - assert(disjointRects.Disjoint(newNode)); -#endif - // Perform the actual packing. - AddSkylineLevel(bestIndex, newNode); - - usedSurfaceArea += width * height; -#ifdef _DEBUG - disjointRects.Add(newNode); -#endif - } - else - memset(&newNode, 0, sizeof(Rect)); - - return newNode; -} - -Rect SkylineBinPack::FindPositionForNewNodeBottomLeft(int width, int height, int &bestHeight, int &bestWidth, int &bestIndex) const -{ - bestHeight = INT_MAX; - bestIndex = -1; - // Used to break ties if there are nodes at the same level. Then pick the narrowest one. - bestWidth = INT_MAX; - Rect newNode = { 0, 0, 0, 0 }; - for(unsigned i = 0; i < 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; -#ifdef _DEBUG - assert(disjointRects.Disjoint(newNode)); -#endif - } - } -/* 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; -} - -Rect SkylineBinPack::InsertMinWaste(int width, int height) -{ - int bestHeight; - int bestWastedArea; - int bestIndex; - Rect newNode = FindPositionForNewNodeMinWaste(width, height, bestHeight, bestWastedArea, bestIndex); - - if (bestIndex != -1) - { -#ifdef _DEBUG - assert(disjointRects.Disjoint(newNode)); -#endif - // Perform the actual packing. - AddSkylineLevel(bestIndex, newNode); - - usedSurfaceArea += width * height; -#ifdef _DEBUG - disjointRects.Add(newNode); -#endif - } - else - memset(&newNode, 0, sizeof(newNode)); - - return newNode; -} - -Rect SkylineBinPack::FindPositionForNewNodeMinWaste(int width, int height, int &bestHeight, int &bestWastedArea, int &bestIndex) const -{ - bestHeight = INT_MAX; - bestWastedArea = INT_MAX; - bestIndex = -1; - Rect newNode; - memset(&newNode, 0, sizeof(newNode)); - for(unsigned i = 0; i < 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; -#ifdef _DEBUG - assert(disjointRects.Disjoint(newNode)); -#endif - } - } -/* 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); -} diff --git a/src/SkylineBinPack.h b/src/SkylineBinPack.h deleted file mode 100644 index 937c88de6..000000000 --- a/src/SkylineBinPack.h +++ /dev/null @@ -1,91 +0,0 @@ -/** @file SkylineBinPack.h - @author Jukka Jylänki - - @brief Implements different bin packer algorithms that use the SKYLINE data structure. - - This work is released to Public Domain, do whatever you want with it. -*/ -#pragma once - -#include "tarray.h" - -#include "Rect.h" -#include "GuillotineBinPack.h" - -/** Implements bin packing algorithms that use the SKYLINE data structure to store the bin contents. Uses - GuillotineBinPack as the waste map. */ -class SkylineBinPack -{ -public: - /// Instantiates a bin of size (0,0). Call Init to create a new bin. - SkylineBinPack(); - - /// Instantiates a bin of the given size. - SkylineBinPack(int binWidth, int binHeight, bool useWasteMap); - - /// (Re)initializes the packer to an empty bin of width x height units. Call whenever - /// you need to restart with a new bin. - void Init(int binWidth, int binHeight, bool useWasteMap); - - /// Inserts the given list of rectangles in an offline/batch mode, possibly rotated. - /// @param rects The list of rectangles to insert. This vector will be destroyed in the process. - /// @param dst [out] This list will contain the packed rectangles. The indices will not correspond to that of rects. - /// @param method The rectangle placement rule to use when packing. - void Insert(TArray &rects, TArray &dst); - - /// Inserts a single rectangle into the bin, possibly rotated. - Rect Insert(int width, int height); - - /// Adds a rectangle to the waste list. It must have been previously returned by - /// Insert or the results are undefined. - void AddWaste(const Rect &rect); - - /// Computes the ratio of used surface area to the total bin area. - float Occupancy() const; - -private: - int binWidth; - int binHeight; - -#ifdef _DEBUG - DisjointRectCollection disjointRects; -#endif - - /// Represents a single level (a horizontal line) of the skyline/horizon/envelope. - struct SkylineNode - { - /// The starting x-coordinate (leftmost). - int x; - - /// The y-coordinate of the skyline level line. - int y; - - /// The line width. The ending coordinate (inclusive) will be x+width-1. - int width; - }; - - TArray skyLine; - - unsigned long usedSurfaceArea; - - /// If true, we use the GuillotineBinPack structure to recover wasted areas into a waste map. - bool useWasteMap; - GuillotineBinPack wasteMap; - - Rect InsertBottomLeft(int width, int height); - Rect InsertMinWaste(int width, int height); - - Rect FindPositionForNewNodeBottomLeft(int width, int height, int &bestHeight, int &bestWidth, int &bestIndex) const; - Rect FindPositionForNewNodeMinWaste(int width, int height, int &bestHeight, int &bestWastedArea, int &bestIndex) const; - - bool RectangleFits(int skylineNodeIndex, int width, int height, int &y) const; - bool RectangleFits(int skylineNodeIndex, int width, int height, int &y, int &wastedArea) const; - int ComputeWastedArea(int skylineNodeIndex, int width, int height, int y) const; - - void AddWasteMapArea(int skylineNodeIndex, int width, int height, int y); - - void AddSkylineLevel(int skylineNodeIndex, const Rect &rect); - - /// Merges all skyline nodes that are at the same level. - void MergeSkylines(); -};