From 19adc39387fc62688da0e4b2c9179955037c7a48 Mon Sep 17 00:00:00 2001 From: Andrea Maggiordomo Date: Thu, 31 Jan 2019 14:28:24 +0100 Subject: [PATCH] Updated RasterizedOutline2Packer Added parameters to control the gutter size of the outlines, the possibility to track space between previously placed polygons when evaluating new moves (inner horizons), and the possibility to try a small number of permutations of the packing sequence in order to improve the overall efficiency. Cleaned up QtOutline2Rasterizer. Updated the relevant samples. --- apps/sample/space_packer/space_packer.cpp | 1 - .../trimesh_texture/trimesh_texture.cpp | 4 +- vcg/space/rasterized_outline2_packer.h | 752 +++++++++++++----- wrap/qt/outline2_rasterizer.cpp | 226 ++++-- wrap/qt/outline2_rasterizer.h | 3 +- 5 files changed, 682 insertions(+), 304 deletions(-) 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