算法来源于计算机图形学(第三版)
对于每条线段,针对其两端点,都赋与“区域码”的四位二进制码,每一位用来标识端点相对于相应裁剪矩形边界的里面还是外面。可以按任意的次序引用窗口边界,下图给出了从右到左编号,从1至4的顺序。
在上图中,位1表示裁剪窗口的左边界,位2表示右边界,位3表示下边界,位4表示上边界。任何码位的值为1表示端点在相应窗口的左边界的外面。类似的,码位为0表示在内部或者边界上。
每一裁剪窗口边界将二维空间划分成内部的两个半空间。四个窗口边界一起生成了九个区域,如下图所示
四个窗口边界一起生成了九个区域,上图列出了这些区域的二维二进码。
区域码的位值通过将端点的坐标值(x, y)与裁剪窗口边界相比较而确定。如果x<xwmin,则位置为1,其他各位的值与此类似,此外,通过使用位处理操作和下列两步操作来更高效地确定区域码的值:
1 计算端点坐标与裁剪边界的差。
2 用各差值计算的符号位来设置区域码中相应的值。
根据区域码,可将线段分为三种情况:
1 两个端点的区域码均为0000的,完全在窗口界内。可用逻辑或判断是否为0000,若为真,则完全位于区域之内。
2 两个端点的端点的区域码中,有一对相同位置为1的则完全落在裁剪区域之外。可用逻辑与判断是否为0000,若为真,则完全位于裁剪区域之外。
3 其他情况需要进一步判断与边界是否有相交。
对于不能判断为完全在窗口外或者窗口内的线段,需要测试其与窗口边界的交点。这些线段可能穿过或不穿过窗口内部,需要多次求交运算,求交次数依赖于选择裁剪边界的次序。每次处理一条裁剪窗口边界后,裁掉其中一部分后,余下部分对照窗口的其余边界进行检查,该过程一直进行到线段完全被裁剪掉或余下的线段部分完全在裁剪窗口内。在后面的讨论中,我们假定窗口边界的处理次序是:左,右,下,上。要检查一线段是否与某裁剪边界相交,我们可以检查其两端点区域码的相应位。如果其中一个是1而另一个是0,则相交。
下图给出了两条不能马上判断出完全在窗口内或窗口外的线段。p1和p2的线段区域码是0100和1001。因此,p1在左边界之内而p2在左边界之外,接着我们计算交点位置p2'并裁掉p2到p2'的部分。余下的线段部分位于右边界的内部,因此我们接下来检查下边界。端点p1在下裁剪边界之下面p2'在其上,因此求出在该边界上的交点p1'。我们清除从p1到p1'的部分,再处理窗口的上边界。在其上我们确定交点位置p2''。最后一步是裁掉上边界之上的部分,并保存从p1'到p2''的内部段。对于第二条线段,可以发现p3在左边界的外部而p4在左边界的内部。因此我们计算交点p3',并清除p3到p3'的段。通过对端点p3'和p4区域码的测试,我们发现余下的线段部分在裁剪窗口之下将其清除。
使用这一方法裁剪线段时,很可能要计算与所有四条裁剪边界的交点,这依赖于线段端点如何处理以及按什么 样的边界次序。为了减少求交计算,需要对该算法进行改进。
线段与裁剪边界的交点计算可以使用斜率截距式的直线方程。对于端点坐标为(x0, y0)和(xend, yend)的直线段,与垂直边界交点的y坐标 可以由下列等式计算得到:
y = y0 + m(x-x0)
其中x值置为xwmin,线段的斜率根据m = (yend-y0)/(xend-x0)进行计算。同样我们要寻找与水平边界相交的交点,其x坐标可以按下列等式进行计算:
x = x0 + (y-y0)/m 其中,y设为ywmin或ywmax。
源码实现:
class wcPt2D {
public:
GLfloat x, y;
};
inline GLint round(const GLfloat a) {return GLint(a+0.5);}
const GLint winLeftBitCode = 0x01;
const GLint winRightBitCode = 0x02;
const GLint winBottomBitCode = 0x04;
const GLint winTopBitCode = 0x08;
inline GLint inside(GLint code) { return GLint(!code); }
inline GLint reject(GLint code1, GLint code2) { return GLint(code1 & code2); }
inline GLint accept(GLint code1, GLint code2) {return GLint(!(code1|code2)); }
GLubyte encode(wcPt2D pt, wcPt2D winMin, wcPt2D winMax)
{
GLubyte code = 0x00;
if(pt.x < winMin.x)
code = code|winLeftBitCode;
if(pt.x > winMax.x)
code = code|winRightBitCode;
if(pt.y < winMin.y)
code = code|winBottomBitCode;
if(pt.y > winMax.y)
code = code|winTopBitCode;
return code;
}
void swapPts(wcPt2D *p1, wcPt2D *p2)
{
wcPt2D tmp;
tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void swapCodes(GLubyte *c1, GLubyte *c2)
{
GLubyte tmp;
tmp = *c1;
*c1 = *c2;
*c2 = tmp;
}
void lineClipCohSuth(wcPt2D winMin, wcPt2D winMax, wcPt2D p1, wcPt2D p2)
{
GLubyte code1, code2;
GLint done = false, plotLine = false;
GLfloat m;
while(!done){
code1 = encode(p1, winMin, winMax);
code2 = encode(p2, winMin, winMax);
if(accept(code1, code2)){
done = true;
plotLine = true;
}else if(reject(code1, code2))
done = true;
else {
if(inside(code1)){
swapPts(&p1, &p2);
swapCodes(&code1, &code2);
}
if(p2.x != p1.x)
m = (p2.y - p1.y)/(p2.x - p1.y);
if(code1 & winLeftBitCode){
p1.y += (winMin.x - p1.x)*m;
p1.x = winMin.x;
}else if(code1 & winRightBitCode){
p1.y += (winMax.x - p1.x)*m;
p1.x = winMax.x;
}else if(code1 & winBottomBitCode){
if(p2.x != p1.x)
p1.x += (winMin.y - p1.y)/m;
p1.y = winMin.y;
}else if(code1 & winTopBitCode){
if(p2.x != p1.x)
p1.x += (winMax.y - p1.y)/m;
p1.y = winMax.y;
}
}
}
if(plotLine)
LineBres(round(p1.x), round(p1.y), round(p2.x), round(p2.y));
}