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.
420 lines
16 KiB
420 lines
16 KiB
/* |
|
* RectsBinPack.h |
|
* |
|
* 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. |
|
*/ |
|
|
|
/** |
|
Initial version created by: |
|
|
|
@author Jukka Jylänki |
|
|
|
@brief Implements different bin packer algorithms that use the MAXRECTS, SKYLINE and GUILLOTINE data structures. |
|
|
|
This work is released to Public Domain, do whatever you want with it. |
|
*/ |
|
|
|
#ifndef _MVS_RECTSBINPACK_H_ |
|
#define _MVS_RECTSBINPACK_H_ |
|
|
|
|
|
// I N C L U D E S ///////////////////////////////////////////////// |
|
|
|
|
|
// D E F I N E S /////////////////////////////////////////////////// |
|
|
|
|
|
// S T R U C T S /////////////////////////////////////////////////// |
|
|
|
namespace MVS { |
|
|
|
// implements the MAXRECTS data structure and different bin packing algorithms that use this structure |
|
class MaxRectsBinPack |
|
{ |
|
public: |
|
// A simple rectangle |
|
typedef cv::Rect Rect; |
|
// A rectangle that stores an index of origin |
|
typedef struct { |
|
Rect rect; |
|
uint32_t patchIdx; |
|
} RectWIdx; |
|
/// A list of rectangles |
|
typedef CLISTDEF0(Rect) RectArr; |
|
/// A list of rectangles along their original indices |
|
typedef CLISTDEF0(RectWIdx) RectWIdxArr; |
|
|
|
/// Instantiates a bin of size (0,0). Call Init to create a new bin. |
|
MaxRectsBinPack(); |
|
|
|
/// Instantiates a bin of the given size. |
|
MaxRectsBinPack(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 heuristic rules that can be used when deciding where to place a new rectangle. |
|
enum FreeRectChoiceHeuristic { |
|
RectBestShortSideFit, ///< -BSSF: Positions the rectangle against the short side of a free rectangle into which it fits the best. |
|
RectBestLongSideFit, ///< -BLSF: Positions the rectangle against the long side of a free rectangle into which it fits the best. |
|
RectBestAreaFit, ///< -BAF: Positions the rectangle into the smallest free rect into which it fits. |
|
RectBottomLeftRule, ///< -BL: Does the Tetris placement. |
|
RectContactPointRule, ///< -CP: Chooses the placement where the rectangle touches other rects as much as possible. |
|
RectLast |
|
}; |
|
|
|
/// Inserts the given list of rectangles in an offline/batch mode, possibly rotated. |
|
/// @param rects [IN/OUT] The list of rectangles to insert; the rectangles will be modified with the new coordinates in the process. |
|
/// @param method The rectangle placement rule to use when packing. |
|
/// returns true if all rectangles were inserted |
|
RectWIdxArr Insert(RectWIdxArr& rects, FreeRectChoiceHeuristic method=RectBestShortSideFit); |
|
|
|
/// Inserts a single rectangle into the bin, possibly rotated. |
|
Rect Insert(int width, int height, FreeRectChoiceHeuristic method=RectBestShortSideFit); |
|
|
|
/// Computes the ratio of used surface area to the total bin area. |
|
float Occupancy() const; |
|
|
|
/// Computes an approximate texture atlas size. |
|
static int ComputeTextureSize(const RectArr& rects, int mult=0); |
|
static int ComputeTextureSize(const RectWIdxArr& rects, int mult=0); |
|
|
|
/// Returns true if a is contained/on the border in b. |
|
static inline bool IsContainedIn(const Rect& a, const Rect& b) { |
|
return a.x >= b.x && a.y >= b.y |
|
&& a.x+a.width <= b.x+b.width |
|
&& a.y+a.height <= b.y+b.height; |
|
} |
|
|
|
protected: |
|
int binWidth; |
|
int binHeight; |
|
|
|
RectArr usedRectangles; |
|
RectArr freeRectangles; |
|
|
|
/// Computes the placement score for placing the given rectangle with the given method. |
|
/// @param score1 [out] The primary placement score will be outputted here. |
|
/// @param score2 [out] The secondary placement score will be outputted here. This is used to break ties. |
|
/// @return This struct identifies where the rectangle would be placed if it were placed. |
|
Rect ScoreRect(int width, int height, FreeRectChoiceHeuristic method, int &score1, int &score2) const; |
|
|
|
/// Places the given rectangle into the bin. |
|
void PlaceRect(const Rect &node); |
|
|
|
/// Computes the placement score for the -CP variant. |
|
int ContactPointScoreNode(int x, int y, int width, int height) const; |
|
|
|
Rect FindPositionForNewNodeBottomLeft(int width, int height, int &bestY, int &bestX) const; |
|
Rect FindPositionForNewNodeBestShortSideFit(int width, int height, int &bestShortSideFit, int &bestLongSideFit) const; |
|
Rect FindPositionForNewNodeBestLongSideFit(int width, int height, int &bestShortSideFit, int &bestLongSideFit) const; |
|
Rect FindPositionForNewNodeBestAreaFit(int width, int height, int &bestAreaFit, int &bestShortSideFit) const; |
|
Rect FindPositionForNewNodeContactPoint(int width, int height, int &contactScore) const; |
|
|
|
/// @return True if the free node was split. |
|
bool SplitFreeNode(Rect freeNode, const Rect &usedNode); |
|
|
|
/// Goes through the free rectangle list and removes any redundant entries. |
|
void PruneFreeList(); |
|
}; |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
#ifndef _RELEASE |
|
class DisjointRectCollection |
|
{ |
|
public: |
|
typedef cv::Rect Rect; |
|
std::vector<Rect> 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_back(r); |
|
return true; |
|
} |
|
|
|
void Clear() |
|
{ |
|
rects.clear(); |
|
} |
|
|
|
bool Disjoint(const Rect &r) const |
|
{ |
|
// Degenerate rectangles are ignored. |
|
if (r.width == 0 || r.height == 0) |
|
return true; |
|
|
|
for (size_t 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 |
|
|
|
|
|
// 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: |
|
// A simple rectangle |
|
typedef cv::Rect Rect; |
|
/// A list of rectangles |
|
typedef CLISTDEF0(Rect) RectArr; |
|
/// A list of rectangles along their original indices |
|
typedef CLISTDEF0(MaxRectsBinPack::RectWIdx) RectWIdxArr; |
|
|
|
/// 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 |
|
RectLast |
|
}; |
|
|
|
/// 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 |
|
SplitLast |
|
}; |
|
|
|
/// 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 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. |
|
RectWIdxArr Insert(RectWIdxArr& rects, 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. |
|
std::vector<Rect> &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. |
|
std::vector<Rect> &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(); |
|
|
|
protected: |
|
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. |
|
std::vector<Rect> usedRectangles; |
|
|
|
/// Stores a list of rectangles that represents the free area of the bin. This rectangles in this list are disjoint. |
|
std::vector<Rect> freeRectangles; |
|
|
|
#ifndef _RELEASE |
|
/// 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, size_t& 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); |
|
}; |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
// implements bin packing algorithms that use the SKYLINE data structure to store the bin contents; |
|
// uses GuillotineBinPack as the waste map |
|
class SkylineBinPack |
|
{ |
|
public: |
|
// A simple rectangle |
|
typedef cv::Rect Rect; |
|
/// A list of rectangles |
|
typedef CLISTDEF0(Rect) RectArr; |
|
/// A list of rectangles along their original indices |
|
typedef CLISTDEF0(MaxRectsBinPack::RectWIdx) RectWIdxArr; |
|
|
|
/// 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); |
|
|
|
/// Defines the different heuristic rules that can be used to decide how to make the rectangle placements. |
|
enum LevelChoiceHeuristic |
|
{ |
|
LevelBottomLeft, |
|
LevelMinWasteFit, |
|
LevelLast |
|
}; |
|
|
|
/// Inserts the given list of rectangles in an offline/batch mode, possibly rotated. |
|
/// @param rects [in/out] The list of rectangles to insert. This vector will be update in the process. |
|
/// @param method The rectangle placement rule to use when packing. |
|
RectWIdxArr Insert(RectWIdxArr& rects, LevelChoiceHeuristic method); |
|
|
|
/// Inserts a single rectangle into the bin, possibly rotated. |
|
Rect Insert(int width, int height, LevelChoiceHeuristic method); |
|
|
|
Rect ScoreRect(int width, int height, LevelChoiceHeuristic method, int &score1, int &score2, int &index) const; |
|
|
|
/// Computes the ratio of used surface area to the total bin area. |
|
float Occupancy() const; |
|
|
|
protected: |
|
int binWidth; |
|
int binHeight; |
|
|
|
#ifndef _RELEASE |
|
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; |
|
}; |
|
|
|
std::vector<SkylineNode> 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 FindPositionForNewNodeMinWaste(int width, int height, int &bestHeight, int &bestWastedArea, int &bestIndex) const; |
|
Rect FindPositionForNewNodeBottomLeft(int width, int height, int &bestHeight, int &bestWidth, 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(); |
|
}; |
|
/*----------------------------------------------------------------*/ |
|
|
|
|
|
typedef MaxRectsBinPack RectsBinPack; |
|
/*----------------------------------------------------------------*/ |
|
|
|
} // namespace MVS |
|
|
|
#endif // _MVS_RECTSBINPACK_H_
|
|
|