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:
parent
e09bc0763a
commit
19adc39387
apps/sample
vcg/space
wrap/qt
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -465,16 +680,15 @@ public:
|
|||
std::vector<int> &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);
|
||||
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;
|
||||
|
@ -486,31 +700,83 @@ public:
|
|||
|
||||
//we first set it to the "optimal" scale
|
||||
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__
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue