编码实现的原理
首先对每条直线的端点赋予一组四位二进制的代码
,也称为区域码(region code)
四位二进制代码的含义为TBRL
,
-
T代表上边界,B代表下边界,R代表右边界,L代表左边界,在边界置为1
,不在边界置为0
-
如下示例,请忽略这很丑的字体:)
对直线的裁剪
-
裁剪分为三类情况
-
直线完全在区域内
,此类情况直线的两个端点区域码
均为0000
,可以直接保存该线段
-
直线部分在区域内
,与区域产生交点
,此时至少
有一个端点区域码不为0000
,此时需要求出交点
,分段裁剪红色是要保留的!!
-
直线在区域之外
, 这种最好整,直接裁掉
-
裁剪的顺序为 左(L)->右(R
)->下(B)->上(T)
交点的计算公式
编码实现
#include <GL/glut.h>
#include <math.h>
#include <stdio.h>
class Point {
public:
float x, y;
};
int winWidth = 800, winHeight = 600;
bool hasLine = true, cut = false;
float offset = 10;
Point clippingWindow[4], line[2], bound[4][2], inside[2]; // 记录line位于裁剪窗口的端点
// 初始化裁剪窗口的四个顶点坐标
void initClippingWindow() {
// 左上角
clippingWindow[0].x = -100;
clippingWindow[0].y = 100;
// 左下角
clippingWindow[1].x = -100;
clippingWindow[1].y = -100;
// 右下角
clippingWindow[2].x = 100;
clippingWindow[2].y = -100;
// 右上角
clippingWindow[3].x = 100;
clippingWindow[3].y = 100;
}
// 绘制裁剪窗口
void drawClippingWindow(){
glColor3f(0, 0, 0);
glBegin(GL_LINE_LOOP);
for (int i = 0; i < 4; i++)
{
glVertex2f(clippingWindow[i].x, clippingWindow[i].y);
}
glEnd();
}
// 更新边界
void updateBound(void) {
// 左边界
bound[0][0] = clippingWindow[0];
bound[0][1] = clippingWindow[1];
// 右边界
bound[1][0] = clippingWindow[2];
bound[1][1] = clippingWindow[3];
// 下边界
bound[2][0] = clippingWindow[1];
bound[2][1] = clippingWindow[2];
// 上边界
bound[3][0] = clippingWindow[0];
bound[3][1] = clippingWindow[3];
}
// 初始化线段
void initLine() {
line[0].x = 70;
line[0].y = 0;
line[1].x = 120;
line[1].y = 115;
}
// 用指定颜色画线段
void drawLine(Point p1, Point p2, float red, float green, float blue) {
glLineWidth(5);
glColor3f(red, green, blue);
glBegin(GL_LINES);
glVertex2f(p1.x, p1.y);
glVertex2f(p2.x, p2.y);
glEnd();
}
// 生成端点的区域码
int encode(Point point, Point clippingWindow[4]) {
int code = 0x0;
if (point.x < clippingWindow[1].x)
{
code = code | 0x1;
}
if (point.x > clippingWindow[3].x)
{
code = code | 0x2;
}
if (point.y < clippingWindow[1].y)
{
code = code | 0x4;
}
if (point.y > clippingWindow[3].y)
{
code = code | 0x8;
}
return code;
}
// 求line1与 line2 的交点
Point getIntersection(Point line1[2], Point line2[2]) {
float dx1 = line1[1].x - line1[0].x, dy1 = line1[1].y - line1[0].y;
float dx2 = line2[1].x - line2[0].x, dy2 = line2[1].y - line2[0].y;
Point intersection;
if (dx1 != 0 && dx2 != 0) // 及两条直线均有斜率
{
// 求直线的参数: y = ax+b
float a1 = dy1 / dx1, b1 = line1[0].y - a1 * line1[0].x;
float a2 = dy2 / dx2, b2 = line2[0].y - a2 * line2[0].x;
intersection.x = (b2 - b1) / (a1 - a2);
intersection.y = a1 * intersection.x + b1;
}
else if (dx1 ==0 && dx2 != 0) // line1垂直于x轴
{
float a2 = dy2 / dx2, b2 = line2[0].y - a2 * line2[0].x;
intersection.x = line1[0].x;
intersection.y = a2 * intersection.x + b2;
}
else if (dx1 != 0 && dx2 == 0) // line2垂直于x轴
{
float a1 = dy1 / dx1, b1 = line1[0].y - a1 * line1[0].x;
intersection.x = line2[0].x;
intersection.y = a1 * intersection.x + b1;
}
else { // 如果都垂直于x轴,则平行无交点
intersection.x = NAN;
intersection.y = NAN;
}
return intersection;
}
// Cohen-Sutherland线段裁剪算法
void cohenSutherland(Point clippingWindow[4], Point line[2], int mode) {
int code0 = encode(line[0], clippingWindow);
int code1 = encode(line[1], clippingWindow);
if (code0 == 0 && code1 == 0)
{
drawLine(line[0], line[1], 0, 1, 0);
}
else {
Point inside[2]; // 记录线段位于裁剪窗口内的两个端点
inside[0] = line[0];
inside[1] = line[1];
// 4次循环处理4个边界, 左->右->下->上
for (int i = 0; i < 4; i++)
{
int temp = (int)pow(2, i);
int current0 = (code0 & temp) >> i;
int current1 = (code1 & temp) >> i;
if (current0 == current1) // 两个端点都在边界的同一侧
{
if (current0 == 1) // 两个端点都在边界的外侧
{
if (mode == 0)
drawLine(inside[0], inside[1], 1, 0, 0);
return;
}
else // 两个端点都在边界的内侧
continue;
}
else { // 两个端点在边界的两侧
Point p = getIntersection(inside, bound[i]);
if (p.x != NAN && p.y != NAN)
{
if (current0 == 1) // 端点inside[0]在边界的外侧,则用交点p换掉端点inside[0]
{
if (mode == 0)
drawLine(p, inside[0], 1, 0, 0);
inside[0] = p;
code0 = encode(inside[0], clippingWindow);
}
else // 端点inside[1]在边界的外侧,则用交点p换掉端点inside[1]
{
if (mode == 0)
drawLine(p, inside[1], 1, 0, 0);
inside[1] = p;
code1 = encode(inside[1], clippingWindow);
}
}
}
}
// 绘制裁剪窗口内的线段
drawLine(inside[0], inside[1], 0, 1, 0);
}
}
// 绘制裁剪窗口与线段
void display() {
glClear(GL_COLOR_BUFFER_BIT);
drawClippingWindow();
cohenSutherland(clippingWindow, line, 0);
glFlush();
}
int main(int argc, char** argv) {
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
glutInitWindowPosition(500,100);
glutInitWindowSize(winWidth, winHeight);
glutCreateWindow("cohen-sutherland 裁剪算法");
glClearColor(1, 1, 1, 0);
glMatrixMode(GL_PROJECTION);
gluOrtho2D(-winWidth / 2, winWidth / 2, -winHeight / 2, winHeight / 2);
initClippingWindow();
updateBound();
initLine();
glutDisplayFunc(display);
glutMainLoop();
}
结果如图
参考书籍:<计算机图形学 第4版>