diff --git a/apps/sample/space_packer/space_packer.cpp b/apps/sample/space_packer/space_packer.cpp index 919d6c05..bfa2b6bd 100644 --- a/apps/sample/space_packer/space_packer.cpp +++ b/apps/sample/space_packer/space_packer.cpp @@ -115,7 +115,6 @@ int main( int /*argc*/, char **/*argv*/ ) packingParam.costFunction = RasterizedOutline2Packer::Parameters::LowestHorizon; packingParam.doubleHorizon = true; - packingParam.cellSize = 4; packingParam.rotationNum = 16; //number of rasterizations in 90° RasterizedOutline2Packer::Pack(outline2Vec,containerSize,trVec,packingParam); diff --git a/apps/sample/trimesh_texture/trimesh_texture.cpp b/apps/sample/trimesh_texture/trimesh_texture.cpp index c3281d4e..f226a6f2 100644 --- a/apps/sample/trimesh_texture/trimesh_texture.cpp +++ b/apps/sample/trimesh_texture/trimesh_texture.cpp @@ -123,8 +123,10 @@ int main(int ,char ** ) RasterizedOutline2Packer::Parameters packingParam; packingParam.costFunction = RasterizedOutline2Packer::Parameters::LowestHorizon; packingParam.doubleHorizon = true; - packingParam.cellSize = 4; + packingParam.innerHorizon = true; + packingParam.permutations = false; packingParam.rotationNum = 16; //number of rasterizations in 90° + packingParam.gutterWidth = 2; RasterizedOutline2Packer::Pack(outline2Vec,containerSize,trVec,packingParam); Outline2Dumper::dumpOutline2VecPNG("PostPackRR.png",outline2Vec,trVec,pp); diff --git a/vcg/space/rasterized_outline2_packer.h b/vcg/space/rasterized_outline2_packer.h index 1ff81fcb..a0f0149b 100644 --- a/vcg/space/rasterized_outline2_packer.h +++ b/vcg/space/rasterized_outline2_packer.h @@ -41,7 +41,7 @@ private: //points: the points which make the polygon std::vector points; - //top: a vector containing the number of cells (for the i-th column starting from left) from the + //deltaY: a vector containing the number of cells (for the i-th column starting from left) from the //FIRST NON-EMTPY cell at the bottom to the LAST NON-EMPTY CELL at the top (there is one top vector for each rasterization) std::vector< std::vector > deltaY; @@ -49,7 +49,7 @@ private: //until the first NON-EMPTY cell is found (there is one bottom vector for each rasterization) std::vector< std::vector > bottom; - //right: a vector containing the number of cells (for the i-th row starting from bottom) from the + //deltaX: a vector containing the number of cells (for the i-th row starting from bottom) from the //FIRST NON-EMTPY cell at the left to the LAST NON-EMPTY CELL at the right (there is one right vector for each rasterization) std::vector< std::vector > deltaX; @@ -75,8 +75,8 @@ public: std::vector& getDeltaX(int i) { return deltaX[i]; } std::vector& getLeft(int i) { return left[i]; } int& getDiscreteArea(int i) { return discreteAreas[i]; } - void addPoint(Point2f& newpoint) { points.push_back(newpoint); } - void setPoints(std::vector& newpoints) { points = newpoints; } + void addPoint(const Point2f& newpoint) { points.push_back(newpoint); } + void setPoints(const std::vector& newpoints) { points = newpoints; } //resets the state of the poly and resizes all the states vectors void resetState(int totalRasterizationsNum) { @@ -170,8 +170,6 @@ public: } discreteAreas[rast_i] = discreteArea; } - - }; template @@ -197,39 +195,60 @@ class RasterizedOutline2Packer typedef typename vcg::Box2 Box2x; typedef typename vcg::Point2 Point2x; typedef typename vcg::Similarity2 Similarity2x; + + static constexpr int INVALID_POSITION = -1; + public: + class Parameters { public: - //size of one cell of the grid (square cells at the moment) - int cellSize; - //the number of rasterizations to create for each polygon; It must be a multiple of 4. - int rotationNum; - - //THE PACKING ALGO TO USE: - //0 - BOTTOM PACKING: it just uses bottom horizon and computes cost using the num of empty cells between the bottom side of the poly and the bottom horizon - //1 - BOTTOM PACKING WITH PENALTY: it uses both bottom and left horizons, and it makes so that polys are placed the closest possible to the left horizon - //2 - CORNER PACKING: 1) tries to drop poly from right to left and computes cost relative to the left horizon - // 2) tries to drop poly from top to bottom and computes cost relative to the bottom horizon - // 3) chooses the X,Y which minimize the cost - // NOTE: IN THIS ALGO THE COST HAVE TO INCLUDE THE PENALTY, OTHERWISE THE TWO STRATEGIES (dropping from right and from top) - // WILL COMPETE CAUSING A LOW PACKING EFFICIENCY - enum costFuncEnum { + // The cost function used by the greedy algorithm when evaluating the next best move + // MinWastedSpace Chooses the placement that minimizes the wasted space. The wasted + // space is defined as the area difference between the horizon after + // and and before placing the polygon, MINUS the polygon area. + // LowestHorizon Chooses the placement that minimizes the maximum horizon increase + // MixedCost Left for compatibility reasons. This should behave similarly to + // MinWastedSpace, while also penalizing placements using one horizon + // that result in too much wasted space relative to the other horizon. + enum CostFuncEnum { MinWastedSpace, LowestHorizon, MixedCost }; - costFuncEnum costFunction; + + CostFuncEnum costFunction; + + // if true, the packing algorithm evaluates polygon 'drops' from both + // principal directions bool doubleHorizon; + // if true, the packing algorithm keeps track of a secondary horizon used + // to place polygons in between previously placed ones + bool innerHorizon; + + // if true, the packing algorithms tries a small number of random + // permutations of the polygon sequence. This can result in a higher + // packing efficiency, but increases the running time of the algorithm + // proportionally to the number of permutations tested + bool permutations; + + //the number of rasterizations to create for each polygon; It must be a multiple of 4. + int rotationNum; + + //the width (in pixels) of the gutter added around the outline + int gutterWidth; + ///default constructor Parameters() { costFunction = LowestHorizon; doubleHorizon=true; + innerHorizon=false; + permutations=false; rotationNum = 16; - cellSize = 8; + gutterWidth = 0; } }; @@ -239,12 +258,25 @@ public: { private: + + using CostFuncEnum = typename Parameters::CostFuncEnum; //the bottomHorizon stores the length of the i-th row in the current solution std::vector mLeftHorizon; //the bottomHorizon stores the height of the i-th column in the current solution std::vector mBottomHorizon; + // secondary horizons, these keep track of the space between the bottom of the + // grid and the already placed polygons + std::vector mSecLeftHorizon; + std::vector mSecBottomHorizon; + + std::vector mInnerBottomHorizon; + std::vector mInnerBottomExtent; + + std::vector mInnerLeftHorizon; + std::vector mInnerLeftExtent; + //the size of the packing grid vcg::Point2i mSize; @@ -256,6 +288,16 @@ public: { mBottomHorizon.resize(size.X(), 0); mLeftHorizon.resize(size.Y(), 0); + + mSecBottomHorizon.resize(size.X(), size.Y() - 1); + mSecLeftHorizon.resize(size.Y(), size.X() - 1); + + mInnerBottomHorizon.resize(size.X(), 0); + mInnerBottomExtent.resize(size.X(), 0); + + mInnerLeftHorizon.resize(size.Y(), 0); + mInnerLeftExtent.resize(size.Y(), 0); + params = par; mSize = Point2i(size.X(), size.Y()); } @@ -267,9 +309,9 @@ public: //returns the score relative to the left horizon of that poly in that particular position, taking into account the choosen algo int getCostX(RasterizedOutline2& poly, Point2i pos, int rast_i) { switch (params.costFunction) { - case 0: return emptyCellBetweenPolyAndLeftHorizon(poly, pos, rast_i); - case 1: return maxXofPoly(poly, pos, rast_i); - case 2: return costXWithPenaltyOnY(poly, pos, rast_i); + case CostFuncEnum::MinWastedSpace: return emptyCellBetweenPolyAndLeftHorizon(poly, pos, rast_i); + case CostFuncEnum::LowestHorizon: return maxXofPoly(poly, pos, rast_i); + case CostFuncEnum::MixedCost: return costXWithPenaltyOnY(poly, pos, rast_i); } return 0; } @@ -277,9 +319,9 @@ public: //returns the score relative to the bottom horizon of that poly in that particular position, taking into account the choosen algo int getCostY(RasterizedOutline2& poly, Point2i pos, int rast_i) { switch (params.costFunction) { - case 0: return emptyCellBetweenPolyAndBottomHorizon(poly, pos, rast_i); - case 1: return maxYofPoly(poly, pos, rast_i); - case 2: return costYWithPenaltyOnX(poly, pos, rast_i); + case CostFuncEnum::MinWastedSpace: return emptyCellBetweenPolyAndBottomHorizon(poly, pos, rast_i); + case CostFuncEnum::LowestHorizon: return maxYofPoly(poly, pos, rast_i); + case CostFuncEnum::MixedCost: return costYWithPenaltyOnX(poly, pos, rast_i); } return 0; } @@ -288,44 +330,111 @@ public: //this returns the Y at which the wasted space is minimum //i.e. the Y at which the polygon touches the horizon int dropY(RasterizedOutline2& poly, int col, int rast_i) { - int tmp = INT_MAX; - int adjacentIndex = 0; std::vector& bottom = poly.getBottom(rast_i); - - //look for for index of the column at which the poly touches the bottom horizon first + int y_max = -INT_MAX; for (size_t i = 0; i < bottom.size(); ++i) { - int diff = bottom[i] - mBottomHorizon[col + i]; - if (diff < tmp) { - adjacentIndex = i; - tmp = diff; + int y = mBottomHorizon[col + i] - bottom[i]; + if (y > y_max) { + if (y + poly.gridHeight(rast_i) >= mSize.Y()) + return INVALID_POSITION; + y_max = y; } } - //return the lowest Y of the dropped poly - return mBottomHorizon[col + adjacentIndex] - bottom[adjacentIndex]; + return y_max; + } + + int dropYInner(RasterizedOutline2& poly, int col, int rast_i) { + std::vector& bottom = poly.getBottom(rast_i); + std::vector& deltaY = poly.getDeltaY(rast_i); + int y_max = -INT_MAX; + for (size_t i = 0; i < bottom.size(); ++i) { + int y = mInnerBottomHorizon[col + i] - bottom[i]; + if (y > y_max) { + if (y + poly.gridHeight(rast_i) >= mSize.Y()) + return INVALID_POSITION; + y_max = y; + } + } + // check if the placement is feasible + for (size_t i = 0; i < bottom.size(); ++i) { + if (y_max + bottom[i] < mBottomHorizon[col + i] + && y_max + bottom[i] + deltaY[i] > mInnerBottomHorizon[col + i] + mInnerBottomExtent[col + i]) + return INVALID_POSITION; + } + return y_max; + } + + int pushY(RasterizedOutline2& poly, int col, int rast_i) { + std::vector& bottom = poly.getBottom(rast_i); + std::vector& deltaY = poly.getDeltaY(rast_i); + int y_min = INT_MAX; + for (size_t i = 0; i < bottom.size(); ++i) { + int y = mSecBottomHorizon[col + i] - (bottom[i] + deltaY[i]); + if (y < y_min) { + if (y < 0) + return INVALID_POSITION; + y_min = y; + } + } + return y_min; } //given a poly and the row at which it is placed, //this returns the X at which the wasted space is minimum //i.e. the X at which the polygon touches the left horizon int dropX(RasterizedOutline2& poly, int row, int rast_i) { - int tmp = INT_MAX; - int adjacentIndex = 0; std::vector& left = poly.getLeft(rast_i); - - //look for for index of the column at which the poly touches the left horizon first + int x_max = -INT_MAX; for (size_t i = 0; i < left.size(); ++i) { - int diff = left[i] - mLeftHorizon[row + i]; - if (diff < tmp) { - adjacentIndex = i; - tmp = diff; + int x = mLeftHorizon[row + i] - left[i]; + if (x > x_max) { + if (x + poly.gridWidth(rast_i) >= mSize.X()) + return INVALID_POSITION; + x_max = x; } } - //and return the lowest X of the dropped poly - return mLeftHorizon[row + adjacentIndex] - left[adjacentIndex]; + return x_max; + } + + int dropXInner(RasterizedOutline2& poly, int row, int rast_i) { + std::vector left = poly.getLeft(rast_i); + std::vector deltaX = poly.getDeltaX(rast_i); + int x_max = -INT_MAX; + for (size_t i = 0; i < left.size(); ++i) { + int x = mInnerLeftHorizon[row + i] - left[i]; + if (x > x_max) { + if (x + poly.gridWidth(rast_i) >= mSize.X()) + return INVALID_POSITION; + x_max = x; + } + } + // sanity check + for (size_t i = 0; i < left.size(); ++i) { + if (x_max + left[i] < mLeftHorizon[row + i] + && x_max + left[i] + deltaX[i] > mInnerLeftHorizon[row + i] + mInnerLeftExtent[row + i]) + return INVALID_POSITION; + } + return x_max; + } + + int pushX(RasterizedOutline2& poly, int row, int rast_i) { + std::vector& left = poly.getLeft(rast_i); + std::vector& deltaX = poly.getDeltaX(rast_i); + int x_min = INT_MAX; + for (size_t i = 0; i < left.size(); ++i) { + int x = mSecLeftHorizon[row + i] - (left[i] + deltaX[i]); + if (x < x_min) { + if (x < 0) + return INVALID_POSITION; + x_min = x; + } + } + return x_min; } int costYWithPenaltyOnX(RasterizedOutline2& poly, Point2i pos, int rast_i) { std::vector& left = poly.getLeft(rast_i); + std::vector& deltaX = poly.getDeltaX(rast_i); //get the standard cost on X axis int score = emptyCellBetweenPolyAndBottomHorizon(poly, pos, rast_i); @@ -342,32 +451,35 @@ public: if (pos.X() + left[i] < mLeftHorizon[pos.Y() + i]) //number of cells between us and the RIGHT end the packing field score -= mSize.X() - pos.X() - left[i]; + //score -= (pos.X() + left[i] + deltaX[i]); else //the number of cells between the bottom side of the poly at the (posY+i)-th row and the value of the horizon in that row score += pos.X() + left[i] - mLeftHorizon[pos.Y() + i]; } return score; } - //returns the number of empty cells between poly's bottom side and the bottom horizon + /* Returns the number of empty cells between the poly's bottom side and the + * bottom horizon. If the poly is below the bottom horizon, it returns the + * distance between the poly's bottom and the grid bottom inverted in sign, + * therefore leaving more space to possibly fit other polygons. */ int emptyCellBetweenPolyAndBottomHorizon(RasterizedOutline2& poly, Point2i pos, int rast_i) { std::vector& bottom = poly.getBottom(rast_i); int score = 0; - int min = INT_MAX; - - //count the number of empty cells between poly's bottom side and the bottom horizon for (size_t i = 0; i < bottom.size(); ++i) { - int diff = bottom[i] - mBottomHorizon[pos.X() + i]; - score += diff; - if (diff < min) min = diff; + if (pos.Y() + bottom[i] < mBottomHorizon[pos.X() + i]) + score -= pos.Y() + bottom[i]; + else + //count the number of empty cells between poly's bottom side and the bottom horizon + score += pos.Y() + bottom[i] - mBottomHorizon[pos.X() + i]; } - score += (-min*bottom.size()); - return score; } + int costXWithPenaltyOnY(RasterizedOutline2& poly, Point2i pos, int rast_i) { std::vector& bottom = poly.getBottom(rast_i); + std::vector& deltaY = poly.getDeltaY(rast_i); //get the standard cost on X axis int score = emptyCellBetweenPolyAndLeftHorizon(poly, pos, rast_i); @@ -384,6 +496,7 @@ public: if (pos.Y() + bottom[i] < mBottomHorizon[pos.X() + i]) //number of cells between us and the TOP side the packing field score -= (mSize.Y() - pos.Y() - bottom[i]); + //score -= (pos.Y() + bottom[i] + deltaY[i]); else //the number of cells between the left side of the poly at the (posX+i)-th column and the value of the horizon in that column score += pos.X() + bottom[i] - mBottomHorizon[pos.X() + i]; } @@ -392,30 +505,59 @@ public: int maxYofPoly(RasterizedOutline2& poly, Point2i pos, int rast_i) { - return pos.Y() + poly.gridHeight(rast_i); + //return pos.Y() + poly.gridHeight(rast_i); + + int cost = -INT_MAX; + std::vector& bottom = poly.getBottom(rast_i); + std::vector& deltaY = poly.getDeltaY(rast_i); + for (unsigned i = 0; i < bottom.size(); ++i) { + int currentCost; + if (pos.Y() + bottom[i] + deltaY[i] < mBottomHorizon[pos.X() + i]) { + currentCost = -(pos.Y() + bottom[i]); + } else { + currentCost = pos.Y() + bottom[i] + deltaY[i]; + } + if (currentCost > cost) + cost = currentCost; + } + return cost; + } int maxXofPoly(RasterizedOutline2& poly, Point2i pos, int rast_i) { - return pos.X() + poly.gridWidth(rast_i); + //return pos.X() + poly.gridWidth(rast_i); + + int cost = -INT_MAX; + std::vector& left = poly.getLeft(rast_i); + std::vector& deltaX = poly.getDeltaX(rast_i); + for (unsigned i = 0; i < left.size(); ++i) { + int currentCost = 0; + if (pos.X() + left[i] + deltaX[i] < mLeftHorizon[pos.Y() + i]) { + currentCost = -(pos.X() + left[i]); + } else { + currentCost = pos.X() + left[i] + deltaX[i]; + } + if (currentCost > cost) + currentCost = cost; + } + return cost; } - //returns the number of empty cells between poly's left side and the left horizon + /* Returns the number of empty cells between the poly's left side and the + * left horizon. If the poly is below the left horizon, it returns the + * distance between the poly's and grid left side inverted in sign. */ int emptyCellBetweenPolyAndLeftHorizon(RasterizedOutline2& poly, Point2i pos, int rast_i) { std::vector& left = poly.getLeft(rast_i); - int score = 0; - int min = INT_MAX; - //count the number of empty cells between poly's left side and the left horizon for (size_t i = 0; i < left.size(); ++i) { - int diff = left[i] - mLeftHorizon[pos.Y() + i]; - score += diff; - if (diff < min) min = diff; + if (pos.X() + left[i] < mLeftHorizon[pos.Y() + i]) + score -= pos.X() + left[i]; + else + score += pos.X() + left[i] - mLeftHorizon[pos.Y() + i]; } - score += (-min*left.size()); - return score; } @@ -429,21 +571,94 @@ public: //update bottom horizon for (int i = 0; i < poly.gridWidth(rast_i); i++) { - //tmpHor = the highest Y reached by the poly, RELATIVE TO the packing field coords system int tmpHor = pos.Y() + bottom[i] + deltaY[i]; - //only update the horizon if it's higher than this value - if (tmpHor > mBottomHorizon[pos.X() + i]) mBottomHorizon[pos.X() + i] = tmpHor; + if (tmpHor > mBottomHorizon[pos.X() + i]) { + // check if we create a bigger gap than the one currently tracked + // as the inner horizon. If we do, the current bottom horizon + // becomes the new inner horizon + int gapExtent = pos.Y() + bottom[i] - mBottomHorizon[pos.X() + i]; + if (gapExtent < 0) { + // This can happen if the poly was placed using the left horizon + // and ends up filling both the inner and outer space at the same time + // just update the inner horizon extent... + if (mInnerBottomHorizon[pos.X() + i] < pos.Y() + bottom[i] + && mInnerBottomHorizon[pos.X() + i] + mInnerBottomExtent[pos.X() + i] > pos.Y() + bottom[i]) + mInnerBottomExtent[pos.X() + i] = pos.Y() + bottom[i] - mInnerBottomHorizon[pos.X() + i]; + } + else if (gapExtent > mInnerBottomExtent[pos.X() + i]) { + mInnerBottomHorizon[pos.X() + i] = mBottomHorizon[pos.X() + i]; + mInnerBottomExtent[pos.X() + i] = gapExtent; + } + // then update the bottom horizon + mBottomHorizon[pos.X() + i] = tmpHor; + } else { + // if the poly fills the space between the currently tracked + // inner bottom horizon and its extent, update the gap. + // Note that this update is local, since we only track the inner horizon and + // its extent. If bigger gaps exist, we lose track of them. + int bottomExtent = pos.Y() + bottom[i] - mInnerBottomHorizon[pos.X() + i]; + int topExtent = mInnerBottomHorizon[pos.X() + i] + mInnerBottomExtent[pos.X() + i] - tmpHor; + if (bottomExtent >= 0 && topExtent >= 0) { + if (bottomExtent > topExtent) { + mInnerBottomExtent[pos.X() + i] = bottomExtent; + } else { + mInnerBottomHorizon[pos.X() + i] = tmpHor; + mInnerBottomExtent[pos.X() + i] = topExtent; + } + } else { + // this is a tricky situation where the poly partially intersects the inner horizon + // TODO: properly update the extents, for now I just clear the inner horizon + mInnerBottomHorizon[pos.X() + i] = 0; + mInnerBottomExtent[pos.X() + i] = 0; + } + } + + int secBtmHor = pos.Y() + bottom[i]; + if (secBtmHor < mSecBottomHorizon[pos.X() + i]) + mSecBottomHorizon[pos.X() + i] = secBtmHor; } + /* if (params.costFunction != Parameters::MixedCost && !params.doubleHorizon) return; + */ - //update leftHorizon + //update left horizon for (int i = 0; i < poly.gridHeight(rast_i); i++) { - //tmpHor = the highest X reached by the poly, RELATIVE TO the packing field coords system int tmpHor = pos.X() + left[i] + deltaX[i]; - //only update the horizon if it's higher than this value - if (tmpHor > mLeftHorizon[pos.Y() + i]) mLeftHorizon[pos.Y() + i] = tmpHor; + if (tmpHor > mLeftHorizon[pos.Y() + i]) { + int gapExtent = pos.X() + left[i] - mLeftHorizon[pos.Y() + i]; + if (gapExtent < 0) { + if (mInnerLeftHorizon[pos.Y() + i] < pos.X() + left[i] + && mInnerLeftHorizon[pos.Y() + i] + mInnerLeftExtent[pos.Y() + i] > pos.X() + left[i]) + mInnerLeftExtent[pos.Y() + i] = pos.X() + left[i] - mInnerLeftHorizon[pos.Y() + i]; + } + else if (gapExtent > mInnerLeftExtent[pos.Y() + i]) { + mInnerLeftHorizon[pos.Y() + i] = mLeftHorizon[pos.Y() + i]; + mInnerLeftExtent[pos.Y() + i] = gapExtent; + } + mLeftHorizon[pos.Y() + i] = tmpHor; + } else { + int leftExtent = pos.X() + left[i] - mInnerLeftHorizon[pos.Y() + i]; + int rightExtent = mInnerLeftHorizon[pos.Y() + i] + mInnerLeftExtent[pos.Y() + i] - tmpHor; + if (leftExtent >= 0 && rightExtent >= 0) { + if (leftExtent > rightExtent) { + mInnerLeftExtent[pos.Y() + i] = leftExtent; + } else { + mInnerLeftHorizon[pos.Y() + i] = tmpHor; + mInnerLeftExtent[pos.Y() + i] = rightExtent; + } + } else { + // this is a tricky situation where the poly partially intersects the inner horizon + // TODO: properly update the extents, for now I just clear the inner horizon + mInnerLeftHorizon[pos.Y() + i] = 0; + mInnerLeftExtent[pos.Y() + i] = 0; + } + } + + int secLeftHor = pos.X() + left[i]; + if (secLeftHor < mSecLeftHorizon[pos.Y() + i]) + mSecLeftHorizon[pos.Y() + i] = secLeftHor; } } }; @@ -454,27 +669,26 @@ public: std::vector &trVec, const Parameters &packingPar) { - std::vector containerSizes(1,containerSize); - std::vector polyToContainer; - return Pack(polyPointsVec,containerSizes,trVec,polyToContainer,packingPar); + std::vector containerSizes(1, containerSize); + std::vector polyToContainer; + return Pack(polyPointsVec, containerSizes, trVec, polyToContainer, packingPar); } - static bool Pack(std::vector< std::vector< Point2x> > &polyPointsVec, + static bool Pack(std::vector> &polyPointsVec, const std::vector &containerSizes, std::vector &trVec, std::vector &polyToContainer, const Parameters &packingPar) { - - int containerNum=containerSizes.size(); + int containerNum = containerSizes.size(); float gridArea = 0; //if containerSize isn't multiple of cell size, crop the grid (leaving containerSize as it is) for (int i = 0; i < containerNum; i++) { - Point2i gridSize(containerSizes[i].X() / packingPar.cellSize, - containerSizes[i].Y() / packingPar.cellSize); + Point2i gridSize(containerSizes[i].X(), + containerSizes[i].Y()); - gridArea += (gridSize.X()*packingPar.cellSize * gridSize.Y()*packingPar.cellSize); + gridArea += (gridSize.X() * gridSize.Y()); } float totalArea = 0; @@ -485,44 +699,138 @@ public: } //we first set it to the "optimal" scale - float optimalScale = sqrt(gridArea/ totalArea); - float currScale = optimalScale; - float latestFailScale = 0; + float optimalScale = sqrt(gridArea / totalArea); - bool ret = false; - //we look for the first scale factor which makes the packing algo succeed - //we will use this value in the bisection method afterwards - ret = PolyPacking(polyPointsVec, containerSizes, trVec, polyToContainer, packingPar, currScale); - while (!ret) { - latestFailScale = currScale; - currScale *= 0.60; - ret = PolyPacking(polyPointsVec, containerSizes, trVec, polyToContainer, packingPar, currScale); - } - //if it managed to pack with the optimal scale (VERY unlikely), just leave - if (currScale == optimalScale) return true; - //BISECTION METHOD - float latestSuccessScale = currScale; - float tmpScale = (latestSuccessScale + latestFailScale) / 2; - while ( (latestFailScale / latestSuccessScale) - 1 > 0.001 - || ((latestFailScale / latestSuccessScale) - 1 < 0.001 && !ret) ) { + std::vector> trials; - tmpScale = (latestSuccessScale + latestFailScale) / 2; - ret = PolyPacking(polyPointsVec, containerSizes, trVec, polyToContainer, packingPar, tmpScale); - if (ret) latestSuccessScale = tmpScale; - else latestFailScale = tmpScale; + //create the vector of polys, starting for the poly points we received as parameter + std::vector polyVec(polyPointsVec.size()); + for(size_t i=0;i oldPoints = polyPointsVec[j]; - for (size_t k = 0; k < oldPoints.size(); k++) { - oldPoints[k].Scale(latestSuccessScale, latestSuccessScale); + { + // Build a permutation that holds the indexes of the polys ordered by their area + std::vector perm(polyVec.size()); + for(size_t i = 0; i < polyVec.size(); i++) + perm[i] = i; + sort(perm.begin(), perm.end(), ComparisonFunctor(polyVec)); + + trials.push_back(perm); + + // if using permutations, determine the number of random permutations and compute them + if (packingPar.permutations) { + int minObjNum = std::min(5, int(perm.size())); + float largestArea = tri::OutlineUtil::Outline2Area(polyPointsVec[perm[0]]); + float thresholdArea = largestArea * 0.5; + std::size_t i; + for (i = 0; i < polyVec.size(); ++i) + if (tri::OutlineUtil::Outline2Area(polyPointsVec[perm[i]]) < thresholdArea) + break; + int numPermutedObjects = std::max(minObjNum, int(i)); + //int permutationCount = numPermutedObjects < 5 ? 20 : numPermutedObjects * 20; + int permutationCount = numPermutedObjects * 5; + std::cout << "PACKING: trying " << permutationCount << " random permutations of the largest " + << numPermutedObjects << " elements" << std::endl; + std::srand(12345); + for (int k = 0; k < permutationCount; ++k) { + std::random_shuffle(perm.begin(), perm.begin() + numPermutedObjects); + trials.push_back(perm); + } } - finalArea += tri::OutlineUtil::Outline2Area(oldPoints); } -// printf("PACKING EFFICIENCY: %f with scale %f\n", finalArea/gridArea, latestSuccessScale); + + double bestEfficiency = 0; + for (std::size_t i = 0; i < trials.size(); ++i) { + + float currScale = optimalScale; + float latestFailScale = 0; + + std::vector trVecIter; + std::vector polyToContainerIter; + + bool ret = false; + //we look for the first scale factor which makes the packing algo succeed + //we will use this value in the bisection method afterwards + ret = PolyPacking(polyPointsVec, containerSizes, trVecIter, polyToContainerIter, packingPar, currScale, polyVec, trials[i]); + while (!ret) { + //printf("Initial packing failed %d\n", k++); + latestFailScale = currScale; + currScale *= 0.60; + ret = PolyPacking(polyPointsVec, containerSizes, trVecIter, polyToContainerIter, packingPar, currScale, polyVec, trials[i]); + } + + //if it managed to pack with the optimal scale (VERY unlikely), skip bisection + float latestSuccessScale = currScale; + //int cnt = 1; + assert(currScale <= optimalScale); + if (currScale < optimalScale) { + //BISECTION METHOD + float tmpScale = (latestSuccessScale + latestFailScale) / 2; + while ( (latestFailScale / latestSuccessScale) - 1 > 0.001 + || ((latestFailScale / latestSuccessScale) - 1 < 0.001 && !ret) ) { + + tmpScale = (latestSuccessScale + latestFailScale) / 2; + ret = PolyPacking(polyPointsVec, containerSizes, trVecIter, polyToContainerIter, packingPar, tmpScale, polyVec, trials[i]); + if (ret) latestSuccessScale = tmpScale; + else latestFailScale = tmpScale; + //cnt++; + } + } + + float finalArea = 0; + //compute occupied area + for (size_t j = 0; j < polyPointsVec.size(); j++) { + std::vector oldPoints = polyPointsVec[j]; + for (size_t k = 0; k < oldPoints.size(); k++) { + oldPoints[k].Scale(latestSuccessScale, latestSuccessScale); + } + finalArea += tri::OutlineUtil::Outline2Area(oldPoints); + } + + //printf("PACKING EFFICIENCY: %f with scale %f after %d attempts\n", finalArea/gridArea, latestSuccessScale, cnt); + + double efficiency = finalArea / gridArea; + if (efficiency > bestEfficiency) { + trVec = trVecIter; + polyToContainer = polyToContainerIter; + bestEfficiency = efficiency; + } + + } + + return true; + } + + /* Function parameters: + * outline2Vec (IN) vector of outlines to pack + * containerSizes (IN) vector of container (grid) sizes + * trVec (OUT) vector of transformations that must be applied to the objects + * polyToContainer (OUT) vector of outline-to-container mappings. If polyToContainer[i] == -1 + * then outline i did not fit in the packing grids, and the transformation trVec[i] is meaningless + * + * The idea is that this function packs what it can in the given space without transforming the + * outlines, and returns enough information to the caller in order to decide what to do */ + static bool + PackBestEffort(std::vector> &outline2Vec, + const std::vector &containerSizes, + std::vector &trVec, + std::vector &polyToContainer, + const Parameters &packingPar) + { + std::vector polyVec(outline2Vec.size()); + for(size_t i=0;i perm(polyVec.size()); + for(size_t i = 0; i < polyVec.size(); i++) + perm[i] = i; + sort(perm.begin(), perm.end(), ComparisonFunctor(polyVec)); + + PolyPacking(outline2Vec, containerSizes, trVec, polyToContainer, packingPar, 1.0, polyVec, perm, true); + return true; } @@ -533,7 +841,10 @@ public: std::vector &trVec, std::vector &polyToContainer, const Parameters &packingPar, - float scaleFactor) + float scaleFactor, + std::vector& polyVec, + const std::vector& perm, + bool bestEffort = false) { int containerNum = containerSizes.size(); @@ -545,33 +856,19 @@ public: std::vector gridSizes; std::vector packingFields; for (int i=0; i < containerNum; i++) { - //if containerSize isn't multiple of cell size, crop the grid (leaving containerSize as it is) - gridSizes.push_back(Point2i(containerSizes[i].X() / packingPar.cellSize, - containerSizes[i].Y() / packingPar.cellSize)); + gridSizes.push_back(Point2i(containerSizes[i].X(), + containerSizes[i].Y())); packingfield one(gridSizes[i], packingPar); packingFields.push_back(one); } - //create the vector of polys, starting for the poly points we received as parameter - std::vector polyVec(outline2Vec.size()); - for(size_t i=0;i perm(polyVec.size()); - for(size_t i=0;i(polyVec)); - -// printf("BEGIN OF PACKING\n"); - // **** First Step: Rasterize all the polygons **** for (size_t i = 0; i < polyVec.size(); i++) { polyVec[i].resetState(packingPar.rotationNum); for (int rast_i = 0; rast_i < packingPar.rotationNum/4; rast_i++) { //create the rasterization (i.e. fills bottom/top/grids/internalWastedCells arrays) - RASTERIZER_TYPE::rasterize(polyVec[i],scaleFactor, rast_i,packingPar.rotationNum,packingPar.cellSize); + RASTERIZER_TYPE::rasterize(polyVec[i], scaleFactor, rast_i, packingPar.rotationNum, packingPar.gutterWidth); } } @@ -585,6 +882,8 @@ public: int bestPolyY = -1; int bestContainer = -1; //the container where the poly fits best + bool placedUsingSecondaryHorizon = false; + //try all the rasterizations and choose the best fitting one for (int rast_i = 0; rast_i < packingPar.rotationNum; rast_i++) { @@ -595,48 +894,74 @@ public: //look for the best position, dropping from top for (int col = 0; col < maxCol; col++) { - //get the Y at which the poly touches the horizontal horizon - int currPolyY = packingFields[grid_i].dropY(polyVec[i],col, rast_i); - - if (currPolyY + polyVec[i].gridHeight(rast_i) > gridSizes[grid_i].Y()) { - //skip this column, as the poly would go outside the grid if placed here - continue; + int currPolyY; + currPolyY = packingFields[grid_i].dropY(polyVec[i],col, rast_i); + if (currPolyY != INVALID_POSITION) { + assert(currPolyY + polyVec[i].gridHeight(rast_i) < gridSizes[grid_i].Y() && "drop"); + int currCost = packingFields[grid_i].getCostX(polyVec[i], Point2i(col, currPolyY), rast_i) + + packingFields[grid_i].getCostY(polyVec[i], Point2i(col, currPolyY), rast_i); + if (currCost < bestCost) { + bestContainer = grid_i; + bestCost = currCost; + bestRastIndex = rast_i; + bestPolyX = col; + bestPolyY = currPolyY; + placedUsingSecondaryHorizon = false; + } } - - int currCost = packingFields[grid_i].getCostX(polyVec[i], Point2i(col, currPolyY), rast_i) + - packingFields[grid_i].getCostY(polyVec[i], Point2i(col, currPolyY), rast_i); - - //if this rasterization is better than what we found so far - if (currCost < bestCost) { - bestContainer = grid_i; - bestCost = currCost; - bestRastIndex = rast_i; - bestPolyX = col; - bestPolyY = currPolyY; + if (packingPar.innerHorizon) { + currPolyY = packingFields[grid_i].dropYInner(polyVec[i],col, rast_i); + if (currPolyY != INVALID_POSITION) { + assert(currPolyY + polyVec[i].gridHeight(rast_i) < gridSizes[grid_i].Y() && "drop_inner"); + int currCost = packingFields[grid_i].getCostX(polyVec[i], Point2i(col, currPolyY), rast_i) + + packingFields[grid_i].getCostY(polyVec[i], Point2i(col, currPolyY), rast_i); + if (currCost < bestCost) { + bestContainer = grid_i; + bestCost = currCost; + bestRastIndex = rast_i; + bestPolyX = col; + bestPolyY = currPolyY; + placedUsingSecondaryHorizon = true; + } + } } } - if (!packingPar.doubleHorizon) continue; + if (!packingPar.doubleHorizon) + continue; for (int row = 0; row < maxRow; row++) { - //get the Y at which the poly touches the horizontal horizon - int currPolyX = packingFields[grid_i].dropX(polyVec[i],row, rast_i); - - if (currPolyX + polyVec[i].gridWidth(rast_i) > gridSizes[grid_i].X()) { - //skip this column, as the poly would go outside the grid if placed here - continue; + int currPolyX; + currPolyX = packingFields[grid_i].dropX(polyVec[i],row, rast_i); + if (currPolyX != INVALID_POSITION) { + assert(currPolyX + polyVec[i].gridWidth(rast_i) < gridSizes[grid_i].X() && "drop"); + int currCost = packingFields[grid_i].getCostY(polyVec[i], Point2i(currPolyX, row), rast_i) + + packingFields[grid_i].getCostX(polyVec[i], Point2i(currPolyX, row), rast_i); + if (currCost < bestCost) { + bestContainer = grid_i; + bestCost = currCost; + bestRastIndex = rast_i; + bestPolyX = currPolyX; + bestPolyY = row; + placedUsingSecondaryHorizon = false; + } } - int currCost = packingFields[grid_i].getCostY(polyVec[i], Point2i(currPolyX, row), rast_i) + - packingFields[grid_i].getCostX(polyVec[i], Point2i(currPolyX, row), rast_i); - - //if this rasterization fits better than those we tried so far - if (currCost < bestCost) { - bestContainer = grid_i; - bestCost = currCost; - bestRastIndex = rast_i; - bestPolyX = currPolyX; - bestPolyY = row; + if (packingPar.innerHorizon) { + currPolyX = packingFields[grid_i].dropXInner(polyVec[i],row, rast_i); + if (currPolyX != INVALID_POSITION) { + assert(currPolyX + polyVec[i].gridWidth(rast_i) < gridSizes[grid_i].X() && "drop_inner"); + int currCost = packingFields[grid_i].getCostY(polyVec[i], Point2i(currPolyX, row), rast_i) + + packingFields[grid_i].getCostX(polyVec[i], Point2i(currPolyX, row), rast_i); + if (currCost < bestCost) { + bestContainer = grid_i; + bestCost = currCost; + bestRastIndex = rast_i; + bestPolyX = currPolyX; + bestPolyY = row; + placedUsingSecondaryHorizon = true; + } + } } } } @@ -645,63 +970,60 @@ public: //if we couldn't find a valid position for the poly return false, as we couldn't pack with the current scaleFactor if (bestRastIndex == -1) { // printf("Items didn't fit using %f as scaleFactor\n", scaleFactor); - return false; + if (bestEffort) { + polyToContainer[i] = -1; + trVec[i] = {}; + } else { + return false; + } + } else { + //we found the best position for a given poly, + //let's place it, so that the horizons are updated accordingly + packingFields[bestContainer].placePoly(polyVec[i], Point2i(bestPolyX, bestPolyY), bestRastIndex); + + //create the rotated bb which we will use to set the similarity translation prop + float angleRad = float(bestRastIndex)*(M_PI*2.0)/float(packingPar.rotationNum); + Box2f bb; + std::vector points = polyVec[i].getPoints(); + for(size_t i=0;i points = polyVec[i].getPoints(); - for(size_t i=0;i& vec) { - for (size_t i = 0; i < vec.size(); i++) { - printf("%d", vec[i]); - } - printf("\n"); - } }; // end class } // end namespace vcg -#endif // NEW_POLYPACKER_H +#endif // __RASTERIZED_OUTLINE2_PACKER_H__ diff --git a/wrap/qt/outline2_rasterizer.cpp b/wrap/qt/outline2_rasterizer.cpp index 55f102bb..36b9b01e 100644 --- a/wrap/qt/outline2_rasterizer.cpp +++ b/wrap/qt/outline2_rasterizer.cpp @@ -3,6 +3,8 @@ #include #include +#include + using namespace vcg; using namespace std; @@ -10,9 +12,11 @@ void QtOutline2Rasterizer::rasterize(RasterizedOutline2 &poly, float scale, int rast_i, int rotationNum, - int cellSize) + int gutterWidth) { + gutterWidth *= 2; // since the brush is centered on the outline multiply the given value by 2 + float rotRad = M_PI*2.0f*float(rast_i) / float(rotationNum); //get polygon's BB, rotated according to the input parameter @@ -24,20 +28,19 @@ void QtOutline2Rasterizer::rasterize(RasterizedOutline2 &poly, bb.Add(pp); } - ///CREATE ITS GRID. The grid has to be a multiple of CELLSIZE because this grid's cells have size CELLSIZE - //we'll make so that sizeX and sizeY are multiples of CELLSIZE: - //1) we round it to the next integer - //2) add the number which makes it a multiple of CELLSIZE (only if it's not multiple already) + //create the polygon to print it + QVector points; + vector newpoints = poly.getPoints(); + for (size_t i = 0; i < newpoints.size(); i++) { + points.push_back(QPointF(newpoints[i].X(), newpoints[i].Y())); + } + + // Compute the raster space size by rounding up the scaled bounding box size + // and adding the gutter width. int sizeX = (int)ceil(bb.DimX()*scale); int sizeY = (int)ceil(bb.DimY()*scale); - if (sizeX % cellSize != 0) sizeX += (cellSize - ((int)ceil(bb.DimX()*scale) % cellSize)); - if (sizeY % cellSize != 0) sizeY += (cellSize - ((int)ceil(bb.DimY()*scale) % cellSize)); - - //security measure: add a dummy column/row thus making the image bigger, and crop it afterwards - //(if it hasn't been filled with anything) - //this is due to the fact that if we have a rectangle which has bb 39.xxx wide, then it won't fit in a 40px wide QImage!! The right side will go outside of the image!! :/ - sizeX+=cellSize; - sizeY+=cellSize; + sizeX += gutterWidth; + sizeY += gutterWidth; QImage img(sizeX,sizeY,QImage::Format_RGB32); QColor backgroundColor(Qt::transparent); @@ -46,99 +49,154 @@ void QtOutline2Rasterizer::rasterize(RasterizedOutline2 &poly, ///SETUP OF DRAWING PROCEDURE QPainter painter; painter.begin(&img); - QBrush br; - br.setStyle(Qt::SolidPattern); - QPen qp; - qp.setWidthF(0); - qp.setColor(Qt::yellow); - painter.setBrush(br); - painter.setPen(qp); + { + QBrush br; + br.setStyle(Qt::SolidPattern); + br.setColor(Qt::yellow); - painter.resetTransform(); - painter.translate(QPointF(-(bb.min.X()*scale) , -(bb.min.Y()*scale) )); - painter.rotate(math::ToDeg(rotRad)); - painter.scale(scale,scale); + QPen qp; + qp.setWidthF(0); + qp.setWidth(gutterWidth); + qp.setCosmetic(true); + qp.setColor(Qt::yellow); + qp.setJoinStyle(Qt::MiterJoin); + qp.setMiterLimit(0); - //create the polygon to print it - QVector points; - vector newpoints = poly.getPoints(); - for (size_t i = 0; i < newpoints.size(); i++) { - points.push_back(QPointF(newpoints[i].X(), newpoints[i].Y())); + painter.setBrush(br); + painter.setPen(qp); + + painter.resetTransform(); + painter.translate(QPointF(-(bb.min.X()*scale) + gutterWidth/2.0f, -(bb.min.Y()*scale) + gutterWidth/2.0f)); + painter.rotate(math::ToDeg(rotRad)); + painter.scale(scale,scale); + + painter.drawPolygon(QPolygonF(points)); } - painter.drawPolygon(QPolygonF(points)); + painter.end(); + // workaround/hack to avoid ``disappearing'' primitives: use a cosmetic pen to + // draw the poly boundary. + // The proper way to do this would be to use conservative reasterization, which + // Qt doesn't seem to support + std::vector lines; + for (int i = 1; i < points.size(); ++i) { + lines.push_back(points[i-1]); + lines.push_back(points[i]); + } + lines.push_back(points.back()); + lines.push_back(points.front()); - //CROPPING: it is enough to check for the (end - cellSize - 1)th row/col of pixels, if they're all black we can eliminate the last 8columns/rows of pixels - bool cropX = true; - bool cropY = true; - for (int j=0; j(img.scanLine(i)); + for (int j = 0; j < img.width(); ++j) { + if (line[j] != backgroundColor.rgb()) { + if (j < minX) minX = j; + if (j > maxX) maxX = j; + if (i < minY) minY = i; + if (i > maxY) maxY = i; } } - else { - if (((QRgb*)line)[img.width() - (cellSize - 1) - 1] != backgroundColor.rgb()) { - cropX = false; + } + */ + + int minX = img.width(); + int minY = img.height(); + int maxX = 0; + int maxY = 0; + + for (int i = 0; i < img.height(); ++i) { + const QRgb *line = reinterpret_cast(img.scanLine(i)); + for (int j = 0; j < img.width(); ++j) { + if (line[j] != backgroundColor.rgb()) { + minY = i; break; } } - if (!cropY) break; + if (minY < img.height()) break; } - - if (cropX || cropY) { - painter.end(); - img = img.copy(0, 0, img.width() - cellSize * cropX, img.height() - cellSize * cropY); - painter.begin(&img); - painter.setBrush(br); - painter.setPen(qp); + for (int i = img.height() - 1; i >= 0; --i) { + const QRgb *line = reinterpret_cast(img.scanLine(i)); + for (int j = 0; j < img.width(); ++j) { + if (line[j] != backgroundColor.rgb()) { + maxY = i; + break; + } + } + if (maxY > 0) break; } - - //draw the poly for the second time, this time it is centered to the image - img.fill(backgroundColor); - - painter.resetTransform(); - painter.translate(QPointF(-(bb.min.X()*scale) + (img.width() - ceil(bb.DimX()*scale))/2.0, -(bb.min.Y()*scale) + (img.height() - ceil(bb.DimY()*scale))/2.0)); - painter.rotate(math::ToDeg(rotRad)); - painter.scale(scale,scale); - //create the polygon to print it - QVector points2; - vector newpoints2 = poly.getPoints(); - for (size_t i = 0; i < newpoints2.size(); i++) { - points2.push_back(QPointF(newpoints2[i].X(), newpoints2[i].Y())); + for (int i = minY; i <= maxY; ++i) { + const QRgb *line = reinterpret_cast(img.scanLine(i)); + for (int j = 0; j < minX; ++j) + if (line[j] != backgroundColor.rgb() && j < minX) { + minX = j; + break; + } + for (int j = img.width() - 1; j >= maxX; --j) + if (line[j] != backgroundColor.rgb() && j > maxX) { + maxX = j; + break; + } + } + + assert (minX <= maxX && minY <= maxY); + + int imgW = (maxX - minX) + 1; + int imgH = (maxY - minY) + 1; + + { + QImage imgcp = img.copy(0, 0, img.width(), img.height()); + img = imgcp.copy(minX, minY, imgW, imgH); } - painter.drawPolygon(QPolygonF(points2)); //create the first grid, which will then be rotated 3 times. //we will reuse this grid to create the rasterizations corresponding to this one rotated by 90/180/270° vector > tetrisGrid; QRgb yellow = QColor(Qt::yellow).rgb(); - int gridWidth = img.width() / cellSize; - int gridHeight = img.height() / cellSize; - int x = 0; - tetrisGrid.resize(gridHeight); - for (int k = 0; k < gridHeight; k++) { - tetrisGrid[k].resize(gridWidth, 0); + tetrisGrid.resize(img.height()); + for (int k = 0; k < img.height(); k++) { + tetrisGrid[k].resize(img.width(), 0); } for (int y = 0; y < img.height(); y++) { - int gridY = y / cellSize; const uchar* line = img.scanLine(y); - x = 0; - int gridX = 0; - while(x < img.width()) { - gridX = x/cellSize; - if (tetrisGrid[gridY][gridX] == 1) { - x+= cellSize - (x % cellSize); //align with the next x - continue; - } - if (((QRgb*)line)[x] == yellow) tetrisGrid[gridY][gridX] = 1; - ++x; + for(int x = 0; x < img.width(); ++x) { + if (((QRgb*)line)[x] == yellow) + tetrisGrid[y][x] = 1; } } @@ -154,8 +212,6 @@ void QtOutline2Rasterizer::rasterize(RasterizedOutline2 &poly, //initializes bottom/left/deltaX/deltaY vectors of the poly, for the current rasterization poly.initFromGrid(rast_i + rotationOffset*j); } - - painter.end(); } // rotates the grid 90 degree clockwise (by simple swap) diff --git a/wrap/qt/outline2_rasterizer.h b/wrap/qt/outline2_rasterizer.h index 6345c31d..68e01578 100644 --- a/wrap/qt/outline2_rasterizer.h +++ b/wrap/qt/outline2_rasterizer.h @@ -16,9 +16,8 @@ class QtOutline2Rasterizer public: static void rasterize(vcg::RasterizedOutline2 &poly, float scaleFactor, - int rast_i, int rotationNum, int cellSize); + int rast_i, int rotationNum, int gutterWidth); static std::vector > rotateGridCWise(std::vector< std::vector >& inGrid); - }; #endif // QTPOLYRASTERIZER_H