【寒江雪】凸多边形矩形裁切算法

最近都在准备着考试,没有写博客,今天忙里偷闲,写点关于裁切算法的文字。

这次要干的事情是给定一个凸多边形的点描述和矩形的点描述,按着矩形来对多边形区域进行裁切。

大概如下图所示:

图片

蓝色矩形是裁剪框,相当于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);

    }

}

 

效果图:

图片

 

【多说一句】

这里是在矩形框下的裁切,如果需要在多边形边下的裁切,则需要对边进行定义,哪边是可见的,哪边是不可见的,还有求交点的方式,这几块都可以找到合适的算法来做判别。

 

另外,上述算法只能作用于凸多边形,凹多边形可能会引起不适,用前需谨慎。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值