最近都在准备着考试,没有写博客,今天忙里偷闲,写点关于裁切算法的文字。
这次要干的事情是给定一个凸多边形的点描述和矩形的点描述,按着矩形来对多边形区域进行裁切。
大概如下图所示:
蓝色矩形是裁剪框,相当于Photoshop里的选区,红色多边形是图像,裁剪后在红色以外的部分被剪掉,相交边用橙色线条表示。(注意:这里是按区域裁切,不是对线条裁切);
那么我们应该按照怎样的思路来裁切呢?考虑到我们输入的是多边形的点描述,两点构成一条线段,参照矩形框直线裁剪算法,直线段要么都在矩形内,要么都在矩形外,要么与矩形相交三种情况,不过这和直线裁切算法略有区别,原因是直线只有两点,多边形有多点
因此,我们假设有两个顶点SP,P点表示待裁定的点,S点是P点的前一点,思路如下
情况1:直线段都在裁剪边可见侧
对于这种情况,保留P点(图中未标出)
情况2:直线段都在裁剪边不可见侧
对于这种情况,不保留P点(图中未标出)
情况3:S在直线段可见侧,P点不在可见侧
保留S,P与裁剪边的交点
情况4:S在不可见侧而P点在可见侧
保留S,P与裁剪边的交点和P点
看到图中只有一条裁剪边,可以知道对于四条裁剪边要分别进行裁剪。为了便于区分,我们从底边开始为矩形的点进行编号,如下图:
这样编号是为了在程序中使用结构体来表示边,0号点和1号点可以很好地区分底边和顶边,左边和右边
举个例子:
底边:1号点的横坐标x比0号点的大
顶边:1号点的横坐标x比0号点的小
(讲个故事:有一天,我在丛林中偶然捡到一本计算机图形学基础入门秘籍,不知是哪位前辈留在这地方,翻到了图形裁切,然而这一部分伪代码,在判断一个点是否在可见侧的时候,传入的参数是边,判断该边是底边还是顶边,是左边还是右边,再来判断点的位置,根本没有写清楚上下左右边的性质以及为何这么编码,简直就是一言不合就贴代码的,我看着伪代码,一脸懵逼度过了好几个小时,论证猜想,最终猜到编写这本秘籍的前辈留下的文字所表达的意思,最终编码调试,还是很有意思,应该猜得对90%吧,毕竟前辈……)
最终代码如下:
调用部分:
VOID CGraphicDlg::PolygonClip_SutherlandHodgman(vector<Vertex>& inVertexArray, float LEFT, float RIGHT, float TOP, float DOWN, COLORREF color)
{
ClipEdge edge[4] = { {LEFT,TOP,LEFT,DOWN },{LEFT,DOWN,RIGHT,DOWN},{RIGHT,DOWN,RIGHT,TOP},{RIGHT,TOP,LEFT,TOP} };
for (int i = 0; i < 4; i++) {
vector<Vertex> out;
SutherlandHodgman(inVertexArray, out, edge[i]);
inVertexArray.clear();
for (int j = 0; j < out.size(); j++) {
inVertexArray.push_back(out[j]);
}
}
vector<Point> in;
for (int i = 0; i < inVertexArray.size(); i++) {
int singalX = inVertexArray[i].x >= 0 ? 1 : -1;
int singalY = inVertexArray[i].y >= 0 ? 1 : -1;
in.push_back(Point{ singalX*((int)((abs(inVertexArray[i].x)) + 0.5f)),singalY*((int)(abs(inVertexArray[i].y) + 0.5f))});
}
Polygon(in, color, 1, 0);
return VOID();
}
裁剪部分:
void CGraphicDlg::SutherlandHodgman(vector<Vertex>& inVertexArray, vector<Vertex>& outVertexArray, ClipEdge & clipBoundary)
{
Vertex S, P;
S = inVertexArray[inVertexArray.size() - 1];
for (int j = 0; j<inVertexArray.size(); j++) {
P = inVertexArray[j];
if (Inside(P, clipBoundary)) {
if (Inside(S, clipBoundary)) {
//SP都在窗口内
outVertexArray.push_back(P);
}
else {
//S在窗口外,情况4
Vertex temp;
Intersect(S, P, clipBoundary, temp);
outVertexArray.push_back(temp);
outVertexArray.push_back(P);
}
}
else if (Inside(S, clipBoundary)) {
Vertex temp;
Intersect(S, P, clipBoundary, temp);
outVertexArray.push_back(temp);
}
S = P;
}
}
判断点的位置:
bool CGraphicDlg::Inside(Vertex & TestPt, ClipEdge & clipBoundary)
{
if (clipBoundary.p[1].x > clipBoundary.p[0].x) {
//窗口下边
return TestPt.y >= clipBoundary.p[0].y;
}
else if(clipBoundary.p[0].x>clipBoundary.p[1].x) {
//窗口上边
return TestPt.y <= clipBoundary.p[0].y;
}
else if (clipBoundary.p[1].y > clipBoundary.p[0].y) {
//窗口右边
return TestPt.x <= clipBoundary.p[0].x;
}
else if (clipBoundary.p[0].y > clipBoundary.p[1].y) {
//窗口左边
return TestPt.x >= clipBoundary.p[0].x;
}
return false;
}
求交部分:
void CGraphicDlg::Intersect(Vertex & S, Vertex & P, ClipEdge ClipBoundary, Vertex & intersectPt)
{
if (ClipBoundary.p[0].y == ClipBoundary.p[1].y) {
intersectPt.y = ClipBoundary.p[0].y;
intersectPt.x = S.x + (ClipBoundary.p[0].y - S.y)*(P.x - S.x) / (P.y - S.y);
}
else {
intersectPt.x = ClipBoundary.p[0].x;
intersectPt.y = S.y + (ClipBoundary.p[0].x - S.x)*(P.y - S.y) / (P.x - S.x);
}
}
效果图:
【多说一句】
这里是在矩形框下的裁切,如果需要在多边形边下的裁切,则需要对边进行定义,哪边是可见的,哪边是不可见的,还有求交点的方式,这几块都可以找到合适的算法来做判别。
另外,上述算法只能作用于凸多边形,凹多边形可能会引起不适,用前需谨慎。