/**************************************************************************** * VCGLib o o * * Visual and Computer Graphics Library o o * * _ O _ * * Copyright(C) 2013 \/)\/ * * Visual Computing Lab /\/| * * ISTI - Italian National Research Council | * * \ * * All rights reserved. * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 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 General Public License (http://www.gnu.org/licenses/gpl.txt) * * for more details. * * * ****************************************************************************/ #ifndef __RASTERIZED_OUTLINE2_PACKER_H__ #define __RASTERIZED_OUTLINE2_PACKER_H__ #include #include namespace vcg { class RasterizedOutline2 { private: //the grid is the "bounding grid" of the polygon, which is returned by the rasterization process //this is a vector of "bounding grids", there is one for each rasterization (different rotation or whatever) std::vector < std::vector< std::vector > > grids; //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 //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; //bottom: a vector containing the number of EMPTY cells found starting from the bottom //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 //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; //left: a vector containing the number of EMPTY cells found starting from the left (at the i-th row starting from the bottom) //until the first NON-EMPTY cell is found (there is one left vector for each rasterization) std::vector< std::vector > left; //the area, measured in cells, of the discrete representations of the polygons std::vector discreteAreas; public: RasterizedOutline2() { } int gridHeight(int i) { return grids.at(i).size(); } int gridWidth( int i) { return grids.at(i).at(0).size(); } std::vector& getPoints() { return points; } const std::vector& getPointsConst() const{ return points; } std::vector< std::vector >& getGrids(int rast_i) { return grids[rast_i]; } //get top/bottom/left/right vectors of the i-th rasterization std::vector& getDeltaY(int i) { return deltaY[i]; } std::vector& getBottom(int i) { return bottom[i]; } 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; } //resets the state of the poly and resizes all the states vectors void resetState(int totalRasterizationsNum) { discreteAreas.clear(); deltaY.clear(); bottom.clear(); deltaX.clear(); left.clear(); grids.clear(); discreteAreas.resize(totalRasterizationsNum); deltaY.resize(totalRasterizationsNum); bottom.resize(totalRasterizationsNum); deltaX.resize(totalRasterizationsNum); left.resize(totalRasterizationsNum); grids.resize(totalRasterizationsNum); } void initFromGrid(int rast_i) { std::vector< std::vector >& tetrisGrid = grids[rast_i]; int gridWidth = tetrisGrid[0].size(); int gridHeight = tetrisGrid.size(); //compute bottom, //where bottom[i] = empty cells from the bottom in the column i for (int col = 0; col < gridWidth; col++) { int bottom_i = 0; for (int row = gridHeight - 1; row >= 0; row--) { if (tetrisGrid[row][col] == 0) { bottom_i++; } else { bottom[rast_i].push_back(bottom_i); break; } } } if (bottom[rast_i].size() == 0) assert("ERROR: EMPTY BOTTOM VECTOR"==0); //compute top //IT ASSUMES THAT THERE IS AT LEAST ONE NON-0 ELEMENT (which should always be the case, even if the poly is just a point) //deltaY[i] = for the column i, it stores the number of cells which are between the bottom and the top side of the poly for (int col = 0; col < gridWidth; col++) { int deltay_i = gridHeight - bottom[rast_i][col]; for (int row = 0; row < gridHeight; row++) { if (tetrisGrid[row][col] == 0) { deltay_i--; } else { break; } } deltaY[rast_i].push_back(deltay_i); } if (deltaY[rast_i].size() == 0) assert("ERROR: EMPTY deltaY VECTOR"==0); //same meaning as bottom, but for the left side //we want left/right sides vector to be ordered so that index 0 is at poly's bottom int left_i; for (int row = gridHeight-1; row >= 0; --row) { //for (int row = 0; row < gridHeight; ++row) { left_i = 0; for (int col = 0; col < gridWidth; col++) { if (tetrisGrid[row][col] == 0) ++left_i; else { left[rast_i].push_back(left_i); break; } } } if (left[rast_i].size() == 0) assert("ERROR: EMPTY leftSide VECTOR"==0); //we want left/right sides vector to be ordered so that index 0 is at poly's bottom int deltax_i; for (int row = gridHeight-1; row >= 0; --row) { //for (int row = 0; row < gridHeight; ++row) { deltax_i = gridWidth - left[rast_i][gridHeight - 1 - row]; for (int col = gridWidth - 1; col >= 0; --col) { if (tetrisGrid[row][col] == 0) --deltax_i; else { break; } } deltaX[rast_i].push_back(deltax_i); } if (deltaX[rast_i].size() == 0) assert("ERROR: EMPTY rightSide VECTOR"==0); //compute the discreteArea: IT IS THE AREA (measured in grid cells) BETWEEN THE TOP AND BOTTOM SIDES... int discreteArea = 0; for (size_t i = 0; i < deltaY[rast_i].size(); i++) { discreteArea += deltaY[rast_i][i]; } discreteAreas[rast_i] = discreteArea; } }; template class ComparisonFunctor { public: std::vector & v; inline ComparisonFunctor( std::vector & nv ) : v(nv) { } inline bool operator() ( int a, int b ) { float area1 = tri::OutlineUtil::Outline2Area(v[a].getPoints()); float area2 = tri::OutlineUtil::Outline2Area(v[b].getPoints()); return area1 > area2; } }; template class RasterizedOutline2Packer { typedef typename vcg::Box2 Box2x; typedef typename vcg::Point2 Point2x; typedef typename vcg::Similarity2 Similarity2x; 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 { MinWastedSpace, LowestHorizon, MixedCost }; costFuncEnum costFunction; bool doubleHorizon; ///default constructor Parameters() { costFunction = LowestHorizon; doubleHorizon=true; rotationNum = 16; cellSize = 8; } }; //THE CLASS WHICH HANDLES THE PACKING AND THE UPDATED STATE OF THE PACKING ALGORITHMS class packingfield { private: //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; //the size of the packing grid vcg::Point2i mSize; //packing parameters Parameters params; public: packingfield(vcg::Point2i size, const Parameters& par) { mBottomHorizon.resize(size.X(), 0); mLeftHorizon.resize(size.Y(), 0); params = par; mSize = Point2i(size.X(), size.Y()); } std::vector& bottomHorizon() { return mBottomHorizon; } std::vector& leftHorizon() { return mLeftHorizon; } vcg::Point2i& size() { return mSize; } //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); } return 0; } //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); } return 0; } //given a poly and the column at which it is placed, //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 for (size_t i = 0; i < bottom.size(); ++i) { int diff = bottom[i] - mBottomHorizon[col + i]; if (diff < tmp) { adjacentIndex = i; tmp = diff; } } //return the lowest Y of the dropped poly return mBottomHorizon[col + adjacentIndex] - bottom[adjacentIndex]; } //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 for (size_t i = 0; i < left.size(); ++i) { int diff = left[i] - mLeftHorizon[row + i]; if (diff < tmp) { adjacentIndex = i; tmp = diff; } } //and return the lowest X of the dropped poly return mLeftHorizon[row + adjacentIndex] - left[adjacentIndex]; } int costYWithPenaltyOnX(RasterizedOutline2& poly, Point2i pos, int rast_i) { std::vector& left = poly.getLeft(rast_i); //get the standard cost on X axis int score = emptyCellBetweenPolyAndBottomHorizon(poly, pos, rast_i); //apply a penalty if the poly is the poly is far from the left horizon //thus preferring poly which are closer to the left horizon for (size_t i = 0; i < left.size(); ++i) { //ASSUMPTION: if the poly is (partially/fully) under the left horizon, //then we will count this as a good thing (subtracting a quantity from the cost) but since we don't have //a grid holding the current state of the packing field, we don't know the position of the polygons at our left side, //so we ASSUME that there isn't any polygon between the poly we're considering and the Y axis of the packing field, //and count the number of cells between us and the RIGHT end the packing field //(NOTE: ^^^^^^^ this implies that the closer we are to the left horizon, the lower the cost will get) 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]; 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 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; } score += (-min*bottom.size()); return score; } int costXWithPenaltyOnY(RasterizedOutline2& poly, Point2i pos, int rast_i) { std::vector& bottom = poly.getBottom(rast_i); //get the standard cost on X axis int score = emptyCellBetweenPolyAndLeftHorizon(poly, pos, rast_i); //apply a penalty if the poly is the poly is far from the bottom horizon //thus preferring poly which are closer to the bottom horizon for (size_t i = 0; i < bottom.size(); ++i) { //ASSUMPTION: if the poly is (partially/fully) under the bottom horizon, //then we will count this as a good thing (subtracting a quantity from the cost) but since we don't have //a grid holding the current state of the packing field, we don't know the position of the polygons beneath us, //so we ASSUME that there isn't any polygon between the poly we're considering and the X axis of the packing field, //and count the number of cells between us and the TOP end the packing field //(NOTE: ^^^^^^^ this implies that the closer we are to the bottom horizon, the lower the cost will get) 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]); 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]; } return score; } int maxYofPoly(RasterizedOutline2& poly, Point2i pos, int rast_i) { return pos.Y() + poly.gridHeight(rast_i); } int maxXofPoly(RasterizedOutline2& poly, Point2i pos, int rast_i) { return pos.X() + poly.gridWidth(rast_i); } //returns the number of empty cells between poly's left side and the left horizon 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; } score += (-min*left.size()); return score; } //updates the horizons according to the chosen position void placePoly(RasterizedOutline2& poly, Point2i pos, int rast_i) { std::vector& bottom = poly.getBottom(rast_i); std::vector& deltaY = poly.getDeltaY(rast_i); std::vector& left = poly.getLeft(rast_i); std::vector& deltaX = poly.getDeltaX(rast_i); //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 (params.costFunction != Parameters::MixedCost && !params.doubleHorizon) return; //update leftHorizon 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; } } }; static bool Pack(std::vector< std::vector< Point2x> > &polyPointsVec, Point2i containerSize, std::vector &trVec, const Parameters &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, const std::vector &containerSizes, std::vector &trVec, std::vector &polyToContainer, const Parameters &packingPar) { 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); gridArea += (gridSize.X()*packingPar.cellSize * gridSize.Y()*packingPar.cellSize); } float totalArea = 0; for (size_t j = 0; j < polyPointsVec.size(); j++) { float curArea = tri::OutlineUtil::Outline2Area(polyPointsVec[j]); if(curArea<0) tri::OutlineUtil::ReverseOutline2(polyPointsVec[j]); totalArea += fabs(curArea); } //we first set it to the "optimal" scale float optimalScale = sqrt(gridArea/ totalArea); float currScale = optimalScale; float latestFailScale = 0; 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) ) { tmpScale = (latestSuccessScale + latestFailScale) / 2; ret = PolyPacking(polyPointsVec, containerSizes, trVec, polyToContainer, packingPar, tmpScale); if (ret) latestSuccessScale = tmpScale; else latestFailScale = tmpScale; } 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\n", finalArea/gridArea, latestSuccessScale); } //tries to pack polygons using the given gridSize and scaleFactor //stores the result, i.e. the vector of similarities, in trVec static bool PolyPacking(std::vector< std::vector< Point2x> > &outline2Vec, const std::vector &containerSizes, std::vector &trVec, std::vector &polyToContainer, const Parameters &packingPar, float scaleFactor) { int containerNum = containerSizes.size(); polyToContainer.clear(); polyToContainer.resize(outline2Vec.size()); trVec.resize(outline2Vec.size()); //create packing fields, one for each container 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)); 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); } } // **** Second Step: iterate on the polys, and try to find the best position **** for (size_t currPoly = 0; currPoly < polyVec.size(); currPoly++) { int i = perm[currPoly]; int bestRastIndex = -1; int bestCost = INT_MAX; int bestPolyX = -1; int bestPolyY = -1; int bestContainer = -1; //the container where the poly fits best //try all the rasterizations and choose the best fitting one for (int rast_i = 0; rast_i < packingPar.rotationNum; rast_i++) { //try to fit the poly in all containers, in all valid positions for (int grid_i = 0; grid_i < containerNum; grid_i++) { int maxCol = gridSizes[grid_i].X() - polyVec[i].gridWidth(rast_i); int maxRow = gridSizes[grid_i].Y() - polyVec[i].gridHeight(rast_i); //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 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.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 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 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; } //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& 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