线段裁剪是图形学中一个非常重要的东西。
线段裁剪简单来说,就是截取能在屏幕中显示出来的部分,这样做可以减少计算机在渲染过程中的计算量,加快渲染速度。
下面开始实现线段裁剪函数bool ClipLine(int* x1, int* y1, int* x2, int* y2)。
1.划分区域,对区域编码
划分区域是为了更好的区分线段所在位置,为裁剪做准备:
线段端点在中心不必处理,若在其他区域,便需要裁剪至中心范围内
编码由来参照下面的代码
#define _C 0x00 //0000 中心
#define _N 0x01 //0001 北
#define _S 0x02 //0010 南
#define _W 0x04 //0100 西
#define _E 0x08 //1000 东
#define _EN (_E | _N) //1001 东北
#define _ES (_E | _S) //1010 东南
#define _WS (_W | _S) //0110 西南
#define _WN (_W | _N) //0101 西北
2.下面就是判断两点的所属区域
//缓存,取得两点值,裁剪过程中不能修改原值
int port1_x = *x1;
int port1_y = *y1;
int port2_x = *x2;
int port2_y = *y2;
//这里的判断隐藏了一个技巧,取<= >=是可以避免下面判断0值问题
char port1 = _C;
//判断点1的位置
if (port1_x <= 0)
port1 |= _W;
else if (port1_x >= _CLIENT_W)
port1 |= _E;
if (port1_y <= 0)
port1 |= _N;
else if (port1_y >= _CLIENT_H)
port1 |= _S;
char port2 = _C;
//判断点2的位置
if (port2_x <= 0)
port2 |= _W;
else if (port2_x >= _CLIENT_W)
port2 |= _E;
if (port2_y <= 0)
port2 |= _N;
else if (port2_y >= _CLIENT_H)
port2 |= _S;
3.进行初判断
得到点1、点2的位置后,是可以直接去初次判断某些无法裁剪的情况
比如:
(1)只要线段两端点都在中心(0000)处,不需要裁剪,可以直接绘制
(2)当线段两端点有共同区域时,是不可能绘制出来显示到屏幕中的
//两点在中心,无需裁剪
if (port1 == _C && port2 == _C)
return false;
//同时包含相同方向,比如东南和东北,都包括东,无需裁剪
if ((port1 & port2) != 0)
return false;
4.进行裁剪
(1)正方向(正北、正南、正东、正西)上的裁剪如下
case _W:
{
port1_x = 0;
port1_y = (int)(*y2 + (double)(*x2 * (*y1 - *y2)) / (*x2 - *x1) + 0.5);
break;
}
PS:上面的公式中后面+0.5的意义主要是为了double转int时四舍五入,如果不加0.5,编译器会在转换时向下取整,即1.9转为int时为1。
上面举了一个正西的例子,其他三个方向类似推导即可
case _E:
{
port1_x = _CLIENT_W - 1;
port1_y = (int)(*y1 - (double)((*x1 - (_CLIENT_W - 1)) * (*y1 - *y2)) / (*x1 - *x2) + 0.5);
break;
}
case _N:
{
port1_x = (int)(*x1 - *y1 * (double)(*x2 - *x1) / (*y2 - *y1) + 0.5);
port1_y = 0;
break;
}
case _S:
{
port1_x = (int)(*x2 - (double)((*x2 - *x1) * (_CLIENT_H - 1 - *y2)) / (*y1 - *y2) + 0.5);
port1_y = _CLIENT_H - 1;
break;
}
(2)非正方向(东北、西北、东南、西南)上的裁剪
代码如下:
case _WN:
{
//按西