1.了解Sutherland-Hodgman多边形裁剪算法的原理
2.利用C++语言编程实现Sutherland-Hodgman多边形裁剪算法。
Visual Studio 2022版
Windows 10/11
Sutherland-Hodgman多边形裁剪算法采用多边形的节点表示方法,即利用多边形的所有顶点作为节点,将其连接或填充颜色表示整个多边形。对于节点而言,其以一定顺序连接成向量,前一边的终点是下一边的起点;节点组成的多边形是封闭的,即最后一边的终点是第一边的始点;经裁剪后可能变形,但要求仍然保持有序和封闭。Sutherland-Hodgman多边形裁剪算法也被称为逐边裁剪算法,把被裁减的多边形按照边的顺序依次与窗口的各边界作比较,改变节点的位置,将窗口外的节点挪到窗口边界处,裁剪去除多余的部分,算法最后的输出是定义裁剪后区域的一组顶点,将这些顶点相连就可以绘制出裁剪后的多边形。
对于节点连成的边向量SP与窗口各边界比较的过程,可以做如下判断:
(1)如果S及P均在窗口边界之内,则终点P点保留;
(2)如果S在窗口内,P在窗口外,则应计算出SP与边界的交点I,并保留此交点作为终点。
(3)如果S及P均在窗口边界之外,则排除P点。
(4)如果S在窗口外,P在窗口内,则应计算出SP与边界的交点I,并保留此交点作为起点,同时保留终点P。
而要实现这一步操作,就需要实现点与直线位置关系的判断和两直线交点坐标的求取。
对于点与直线位置关系的判断,可以采用向量叉积的方法进行计算:
设向量两个端点坐标分别为P1(x1,y1)、P2(x2,y2),平面上一点坐标为P0(x0,y0):
则向量P1P
的表达式为:
P1P2=x2-x1,y2-y1
P1P0=x0-x1,y0-y1
则向量P1P2×P1P0=P1P2∙P1P0∙sinθ=(x2-x1)y0-y1-y2-y1x0-x1
的值遵循右手螺旋定则,四指从P1P0
转到P1P2,观察大拇指方向,其对应的数学表达式为:
若(x2-x1)y0-y1-y2-y1x0-x1<0,则点在向量左侧平面;
若(x2-x1)y0-y1-y2-y1x0-x1>0,则点在向量右侧平面;
若(x2-x1)y0-y1-y2-y1x0-x1=0,则点在向量上;
而对于两直线,其端点坐标分别为x1,y1、x2,y2,要求其交点坐标,首先求出两直线的斜率k1、k2,接着分情况进行求取:
(1)若一条直线与坐标轴平行,很容易求得计算公式如下:
y=y1+k1x2-x1
x=x2
(2)若两条直线与坐标轴均不平行,利用解析几何可以推导出计算公式如下:
x=(y2-y1+k1×x1-k2×x2)/(k2-k1)
y=(k1×k2×x2-x1+k2×y1-k1×y2)/(k2-k1)
至此,就可以实现完整的算法,最后可以得到裁剪后多边形各个端点的坐标值。
//头文件
#include<iostream>
#include<graphics.h>
#include<conio.h>
using namespace std;
struct vertex
{
float x;
float y;
};//多边形节点结构体
vertex sp[40], cw[20];//sp为待裁剪多边形数组,cw为裁剪多边形数组
int n_sp, n;//n_sp待裁剪多边形节点数量,n为裁剪多边形节点个数
void draw_poly(vertex vlist[], int n)
{
for (int i = 0; i < n; i++)
line(vlist[i].x, vlist[i].y, vlist[(i + 1) % n].x, vlist[(i + 1) % n].y);
}//画矩形
int in_out(float x, float y, int x1, int y1, int x2, int y2)
{
float p = (y - y1) * (x2 - x1) - (x - x1) * (y2 - y1);//向量叉积判别式
if (p < 0)
return 0; //直线左侧,即多边形外侧,返回false
return 1; //直线右侧,即多边形内侧,返回true
}//判断点与直线之间的关系,输入为点坐标x、y,起点坐标x1、y1,终点坐标x2、y2,若点在(x1,y1)->(x2,y2)向量左侧,则返回0,若在右侧,则返回1
void intersection_line(float& x, float& y, float x1, float y1, float x2, float y2, int xa, int ya, int xb, int yb) {
if (x2 == x1) {
float m2 = (yb - ya) / (xb - xa);
x = x1;
y = ya - m2 * (xa - x1);
}//若x1==x2,则直接用y值减deltax乘以斜率计算交点
else if (xb == xa) {
float m1 = (y2 - y1) / (x2 - x1);
x = xa;
y = y1 + m1 * (xa - x1);
}//若xa==xb,则直接用y值减deltax乘以斜率计算交点
else {
float m1 = (y2 - y1) / (x2 - x1);
float m2 = (yb - ya) / (xb - xa);
x = (ya - y1 + m1 * x1 - m2 * xa) / (m1 - m2);
y = (m1 * m2*(xa - x1) + m2 * y1 - m1 * ya) / (m2 - m1);
}//若两直线都不与y轴平行,则采用解析方法推导计算
}//求两个直线交点坐标,输入参数是传参形式的x值和y值,两条直线两个端点的x坐标、y坐标
void edge_clip(int cw_x1, int cw_y1, int cw_x2, int cw_y2) {
vertex temp[40];//裁剪后的节点坐标
int j = -1;//遍历顺序
int p1, p2;//存储节点位置判断结果
for (int i = 0; i < n_sp; i++)//依次遍历待裁剪多边形的节点
{
p1 = in_out(sp[i].x, sp[i].y, cw_x1, cw_y1, cw_x2, cw_y2);
p2 = in_out(sp[(i + 1) % n_sp].x, sp[(i + 1) % n_sp].y, cw_x1, cw_y1, cw_x2, cw_y2);//判断待裁剪多边形相邻两个顶点与裁剪多边形边的关系
if (p1 == 1 && p2 == 0) {
j++;
intersection_line(temp[j].x, temp[j].y, sp[i].x, sp[i].y, sp[(i + 1) % n_sp].x, sp[(i + 1) % n_sp].y, cw_x1, cw_y1, cw_x2, cw_y2);
}//若起点在裁剪多边形边的内部,终点在外部,则求出交点坐标作为终点坐标,并继续判断下一条边
else if (p1 == 0 && p2 == 1) {
j++;
intersection_line(temp[j].x, temp[j].y, sp[i].x, sp[i].y, sp[(i + 1) % n_sp].x, sp[(i + 1) % n_sp].y, cw_x1, cw_y1, cw_x2, cw_y2);
j++;
temp[j].x = sp[(i + 1) % n_sp].x;
temp[j].y = sp[(i + 1) % n_sp].y;
}//若起点在裁剪多边形边的外部,终点在内部,求出交点坐标,并将终点坐标赋给裁剪出多边形的下一个坐标,继续判断下一条边
else if (p1 == 1 && p2 == 1) {
j++;
temp[j].x = sp[(i + 1) % n_sp].x;
temp[j].y = sp[(i + 1) % n_sp].y;
}//两个点都在多边形内部,直接显示即可
else { ; }//两个点都在多边形外部,不用进行任何操作
}
for (int i = 0; i <= j; i++) {
sp[i].x = temp[i].x;
sp[i].y = temp[i].y;
}//将裁剪多边形的结果存储在sp数组中
n_sp = j + 1;//裁剪后多边形边数
}//输入待裁剪多边形边,对其进行裁剪,返回裁剪以后的端点坐标
//主函数
int main()
{
n = 5;
cw[0].x = 100; cw[0].y = 100;
cw[1].x = 200; cw[1].y = 100;
cw[2].x = 200; cw[2].y = 200;
cw[3].x = 150; cw[3].y = 300;
cw[4].x = 100; cw[4].y = 200;
initgraph(600, 600);
draw_poly(cw, n);//画出裁剪矩形
n_sp = 4;
sp[0].x = 120; sp[0].y = 50;
sp[1].x = 180; sp[1].y = 50;
sp[2].x = 180; sp[2].y =150;
sp[3].x = 120; sp[2].y = 150;
for (int i = 0; i < n; i++) {
edge_clip(cw[i].x, cw[i].y, cw[(i + 1) % n].x, cw[(i + 1) % n].y);
}//进行裁剪
draw_poly(sp, n_sp);//画出裁剪后的多边形
_getch();
closegraph();
return 0;
}
1.Sutherland-Hodgman多边形裁剪算法
(1)被裁剪多边形顶点在裁剪多边形外部
(2)被裁剪多边形完全位于裁剪多边形内部
(3)其他情况实验
本次上机实验主要编程实现了Sutherland-Hodgman多边形裁剪算法,对于Sutherland-Hodgman多边形裁剪算法,这个算法也叫做逐边裁剪法,其算法思路是将多边形看成是多条向量连接而成的闭合图形,每一条边都是一个首尾相连的向量,对这些向量进行逐个判断和处理,最终再得到一个多个向量首尾相连而成的多边形,这个多边形的范围完全在窗口之内,这样就实现了裁剪。
而对于C++编程实现主要是先对待裁剪多边形的每一条边与裁剪多边形的其中一条边进行交点求解并更新交点,接着按照左下右上的顺序对逐个裁剪多边形的边进行判断,并更新待裁剪多边形顶点坐标,最终得到裁剪后的多边形所有顶点坐标,并且最后将所有顶点坐标进行相连即可得到裁剪后的多边形。
而对于这Sutherland-Hodgman多边形裁剪算法和上次实验中的Cohen-Sutherland直线裁剪算法而言,其都实现了裁剪功能,但是基本原理确实天差地别,裁剪算法的核心就是待裁剪元素与裁剪元素端点位置关系的判断和边界外部点的处理,Cohen-Sutherland直线裁剪算法主要采用编码的方式对点线位置进行判断,而Sutherland-Hodgman多边形裁剪算法主要采用向量右手螺旋定则判断的方法对点线位置进行判断,其对窗口外部点的处理都是采用直接赋值的方法。而对于处理的对象,二者也是不同的,Cohen-Sutherland直线裁剪算法针对的是窗口和一条直线,而Sutherland-Hodgman多边形裁剪算法是针对两个多边形,利用一个多边形的范围对另一个多边形进行裁剪。
在算法效率方面,Sutherland-Hodgman多边形裁剪算法,其主要功能函数中存在两个并行的单层循环,其他函数中仅存在并行循环,因此算法的时间复杂度为O(n),算法效率较高。
本次上机实验对于Sutherland-Hodgman多边形裁剪算法验证了多边形与窗口部分相交和多边形完全处于窗口内部等不同的情况,也能实现相应功能,说明了程序的准确性和普适性。
Sutherland-Hodgman多边形裁剪算法具有很广阔的应有前景,在GIS中,地理实体在计算机中经常以线和面的形式存储和表示,而往往在进行一些分析时,需要对其进行一次处理,例如在一大片区域中按形状截取研究区域、根据地理界线裁剪区域等,都需要用到多边形裁剪算法,虽然大部分GIS软件对其都有相应程序对其进行处理,但是掌握这些算法思维也是必要的,在我们可能需要自己编写程序时会发挥很大作用,也会对于我们的编程能力和编程思维起到很大的推动作用。