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.
This commit is contained in:
Andrea Maggiordomo 2019-01-31 14:28:24 +01:00
parent e09bc0763a
commit 19adc39387
5 changed files with 682 additions and 304 deletions

View File

@ -115,7 +115,6 @@ int main( int /*argc*/, char **/*argv*/ )
packingParam.costFunction = RasterizedOutline2Packer<float, QtOutline2Rasterizer>::Parameters::LowestHorizon;
packingParam.doubleHorizon = true;
packingParam.cellSize = 4;
packingParam.rotationNum = 16; //number of rasterizations in 90°
RasterizedOutline2Packer<float, QtOutline2Rasterizer>::Pack(outline2Vec,containerSize,trVec,packingParam);

View File

@ -123,8 +123,10 @@ int main(int ,char ** )
RasterizedOutline2Packer<float, QtOutline2Rasterizer>::Parameters packingParam;
packingParam.costFunction = RasterizedOutline2Packer<float, QtOutline2Rasterizer>::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<float, QtOutline2Rasterizer>::Pack(outline2Vec,containerSize,trVec,packingParam);
Outline2Dumper::dumpOutline2VecPNG("PostPackRR.png",outline2Vec,trVec,pp);

View File

@ -41,7 +41,7 @@ private:
//points: the points which make the polygon
std::vector<Point2f> 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<int> > 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<int> > 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<int> > deltaX;
@ -75,8 +75,8 @@ public:
std::vector<int>& getDeltaX(int i) { return deltaX[i]; }
std::vector<int>& 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<Point2f>& newpoints) { points = newpoints; }
void addPoint(const Point2f& newpoint) { points.push_back(newpoint); }
void setPoints(const std::vector<Point2f>& 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 <class ScalarType>
@ -197,39 +195,60 @@ class RasterizedOutline2Packer
typedef typename vcg::Box2<SCALAR_TYPE> Box2x;
typedef typename vcg::Point2<SCALAR_TYPE> Point2x;
typedef typename vcg::Similarity2<SCALAR_TYPE> 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<int> mLeftHorizon;
//the bottomHorizon stores the height of the i-th column in the current solution
std::vector<int> mBottomHorizon;
// secondary horizons, these keep track of the space between the bottom of the
// grid and the already placed polygons
std::vector<int> mSecLeftHorizon;
std::vector<int> mSecBottomHorizon;
std::vector<int> mInnerBottomHorizon;
std::vector<int> mInnerBottomExtent;
std::vector<int> mInnerLeftHorizon;
std::vector<int> 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<int>& 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<int>& bottom = poly.getBottom(rast_i);
std::vector<int>& 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<int>& bottom = poly.getBottom(rast_i);
std::vector<int>& 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<int>& 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<int> left = poly.getLeft(rast_i);
std::vector<int> 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<int>& left = poly.getLeft(rast_i);
std::vector<int>& 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<int>& left = poly.getLeft(rast_i);
std::vector<int>& 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<int>& 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<int>& bottom = poly.getBottom(rast_i);
std::vector<int>& 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<int>& bottom = poly.getBottom(rast_i);
std::vector<int>& 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<int>& left = poly.getLeft(rast_i);
std::vector<int>& 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<int>& 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<Similarity2x> &trVec,
const Parameters &packingPar)
{
std::vector<Point2i> containerSizes(1,containerSize);
std::vector<Point2i> containerSizes(1, containerSize);
std::vector<int> polyToContainer;
return Pack(polyPointsVec,containerSizes,trVec,polyToContainer,packingPar);
return Pack(polyPointsVec, containerSizes, trVec, polyToContainer, packingPar);
}
static bool Pack(std::vector< std::vector< Point2x> > &polyPointsVec,
static bool Pack(std::vector<std::vector<Point2x>> &polyPointsVec,
const std::vector<Point2i> &containerSizes,
std::vector<Similarity2x> &trVec,
std::vector<int> &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,32 +699,84 @@ public:
}
//we first set it to the "optimal" scale
float optimalScale = sqrt(gridArea/ totalArea);
float optimalScale = sqrt(gridArea / totalArea);
std::vector<std::vector<int>> trials;
//create the vector of polys, starting for the poly points we received as parameter
std::vector<RasterizedOutline2> polyVec(polyPointsVec.size());
for(size_t i=0;i<polyVec.size();i++) {
polyVec[i].setPoints(polyPointsVec[i]);
}
{
// Build a permutation that holds the indexes of the polys ordered by their area
std::vector<int> perm(polyVec.size());
for(size_t i = 0; i < polyVec.size(); i++)
perm[i] = i;
sort(perm.begin(), perm.end(), ComparisonFunctor<float>(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<SCALAR_TYPE>::Outline2Area(polyPointsVec[perm[0]]);
float thresholdArea = largestArea * 0.5;
std::size_t i;
for (i = 0; i < polyVec.size(); ++i)
if (tri::OutlineUtil<SCALAR_TYPE>::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);
}
}
}
double bestEfficiency = 0;
for (std::size_t i = 0; i < trials.size(); ++i) {
float currScale = optimalScale;
float latestFailScale = 0;
std::vector<Similarity2x> trVecIter;
std::vector<int> 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, trVec, polyToContainer, packingPar, currScale);
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, trVec, polyToContainer, packingPar, currScale);
ret = PolyPacking(polyPointsVec, containerSizes, trVecIter, polyToContainerIter, packingPar, currScale, polyVec, trials[i]);
}
//if it managed to pack with the optimal scale (VERY unlikely), just leave
if (currScale == optimalScale) return true;
//BISECTION METHOD
//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, trVec, polyToContainer, packingPar, tmpScale);
ret = PolyPacking(polyPointsVec, containerSizes, trVecIter, polyToContainerIter, packingPar, tmpScale, polyVec, trials[i]);
if (ret) latestSuccessScale = tmpScale;
else latestFailScale = tmpScale;
//cnt++;
}
}
float finalArea = 0;
@ -522,7 +788,49 @@ public:
}
finalArea += tri::OutlineUtil<SCALAR_TYPE>::Outline2Area(oldPoints);
}
// printf("PACKING EFFICIENCY: %f with scale %f\n", finalArea/gridArea, latestSuccessScale);
//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<std::vector<Point2x>> &outline2Vec,
const std::vector<Point2i> &containerSizes,
std::vector<Similarity2x> &trVec,
std::vector<int> &polyToContainer,
const Parameters &packingPar)
{
std::vector<RasterizedOutline2> polyVec(outline2Vec.size());
for(size_t i=0;i<polyVec.size();i++) {
polyVec[i].setPoints(outline2Vec[i]);
}
std::vector<int> perm(polyVec.size());
for(size_t i = 0; i < polyVec.size(); i++)
perm[i] = i;
sort(perm.begin(), perm.end(), ComparisonFunctor<float>(polyVec));
PolyPacking(outline2Vec, containerSizes, trVec, polyToContainer, packingPar, 1.0, polyVec, perm, true);
return true;
}
@ -533,7 +841,10 @@ public:
std::vector<Similarity2x> &trVec,
std::vector<int> &polyToContainer,
const Parameters &packingPar,
float scaleFactor)
float scaleFactor,
std::vector<RasterizedOutline2>& polyVec,
const std::vector<int>& perm,
bool bestEffort = false)
{
int containerNum = containerSizes.size();
@ -545,33 +856,19 @@ public:
std::vector<Point2i> gridSizes;
std::vector<packingfield> 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<RasterizedOutline2> polyVec(outline2Vec.size());
for(size_t i=0;i<polyVec.size();i++) {
polyVec[i].setPoints(outline2Vec[i]);
}
// Build a permutation that holds the indexes of the polys ordered by their area
std::vector<int> perm(polyVec.size());
for(size_t i=0;i<polyVec.size();i++) perm[i] = i;
sort(perm.begin(),perm.end(),ComparisonFunctor<float>(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 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;
placedUsingSecondaryHorizon = false;
}
}
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 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;
placedUsingSecondaryHorizon = false;
}
}
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,9 +970,13 @@ 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);
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);
@ -667,16 +996,16 @@ public:
//now we have bestPolyX/bestRastIndex
//we have to update the similarities vector accordingly!
float polyXInImgCoords = bestPolyX*packingPar.cellSize;
float polyXInImgCoords = bestPolyX;
float scaledBBWidth = bb.DimX()*scaleFactor;
float polyWidthInImgCoords = polyVec[i].gridWidth(bestRastIndex)*packingPar.cellSize;
float polyWidthInImgCoords = polyVec[i].gridWidth(bestRastIndex);
float offsetX = (polyWidthInImgCoords - ceil(scaledBBWidth))/2.0;
float scaledBBMinX = bb.min.X()*scaleFactor;
//note: bestPolyY is 0 if the poly is at the bottom of the grid
float imgHeight = containerSizes[bestContainer].Y();
float polyYInImgCoords = bestPolyY*packingPar.cellSize;
float polyHeightInImgCoords = polyVec[i].gridHeight(bestRastIndex)*packingPar.cellSize;
float polyYInImgCoords = bestPolyY;
float polyHeightInImgCoords = polyVec[i].gridHeight(bestRastIndex);
float topPolyYInImgCoords = polyYInImgCoords + polyHeightInImgCoords;
float scaledBBHeight = bb.DimY()*scaleFactor;
float offsetY = (polyHeightInImgCoords - ceil(scaledBBHeight))/2.0;
@ -686,22 +1015,15 @@ public:
trVec[i].rotRad = angleRad;
trVec[i].sca = scaleFactor;
}
}
//sort polyToContainer and trVec so that we have them ordered for dumping
// printf("SUCCESSFULLY PACKED with scaleFactor %f\n", scaleFactor);
return true;
}
static void printVector(std::vector<int>& 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__

View File

@ -3,6 +3,8 @@
#include <vcg/space/color4.h>
#include <wrap/qt/col_qt_convert.h>
#include <fstream>
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<QPointF> points;
vector<Point2f> 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);
br.setColor(Qt::yellow);
QPen qp;
qp.setWidthF(0);
qp.setWidth(gutterWidth);
qp.setCosmetic(true);
qp.setColor(Qt::yellow);
qp.setJoinStyle(Qt::MiterJoin);
qp.setMiterLimit(0);
painter.setBrush(br);
painter.setPen(qp);
painter.resetTransform();
painter.translate(QPointF(-(bb.min.X()*scale) , -(bb.min.Y()*scale) ));
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);
//create the polygon to print it
QVector<QPointF> points;
vector<Point2f> newpoints = poly.getPoints();
for (size_t i = 0; i < newpoints.size(); i++) {
points.push_back(QPointF(newpoints[i].X(), newpoints[i].Y()));
}
painter.drawPolygon(QPolygonF(points));
//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.height(); j++) {
const uchar* line = img.scanLine(j);
if (j == img.height() - (cellSize - 1) - 1 ) {
for (int x=0; x<img.width(); x++) {
if (((QRgb*)line)[x] != backgroundColor.rgb()) {
cropY = false;
break;
}
}
}
else {
if (((QRgb*)line)[img.width() - (cellSize - 1) - 1] != backgroundColor.rgb()) {
cropX = false;
break;
}
}
if (!cropY) break;
}
if (cropX || cropY) {
painter.end();
img = img.copy(0, 0, img.width() - cellSize * cropX, img.height() - cellSize * cropY);
// 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<QPointF> 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());
painter.begin(&img);
{
QBrush br;
br.setStyle(Qt::SolidPattern);
br.setColor(Qt::yellow);
QPen qp;
qp.setWidthF(0);
qp.setWidth(std::max(1, gutterWidth));
qp.setCosmetic(true);
qp.setColor(Qt::yellow);
painter.setBrush(br);
painter.setPen(qp);
}
//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.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);
//create the polygon to print it
QVector<QPointF> points2;
vector<Point2f> newpoints2 = poly.getPoints();
for (size_t i = 0; i < newpoints2.size(); i++) {
points2.push_back(QPointF(newpoints2[i].X(), newpoints2[i].Y()));
//painter.drawPoints(QPolygonF(points));
painter.drawLines(lines.data(), lines.size()/2);
}
painter.end();
// Cropping
/*
// Slower version
int minX = img.width();
int minY = img.height();
int maxX = -1;
int maxY = -1;
for (int i = 0; i < img.height(); ++i) {
const QRgb *line = reinterpret_cast<const QRgb*>(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;
}
}
}
*/
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<const QRgb*>(img.scanLine(i));
for (int j = 0; j < img.width(); ++j) {
if (line[j] != backgroundColor.rgb()) {
minY = i;
break;
}
}
if (minY < img.height()) break;
}
for (int i = img.height() - 1; i >= 0; --i) {
const QRgb *line = reinterpret_cast<const QRgb*>(img.scanLine(i));
for (int j = 0; j < img.width(); ++j) {
if (line[j] != backgroundColor.rgb()) {
maxY = i;
break;
}
}
if (maxY > 0) break;
}
for (int i = minY; i <= maxY; ++i) {
const QRgb *line = reinterpret_cast<const QRgb*>(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<vector<int> > 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)

View File

@ -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<std::vector<int> > rotateGridCWise(std::vector< std::vector<int> >& inGrid);
};
#endif // QTPOLYRASTERIZER_H