Cohen-Sutherland直线裁剪

编码实现的原理

首先对每条直线的端点赋予一组四位二进制的代码,也称为区域码(region code)

四位二进制代码的含义为TBRL,

  • T代表上边界,B代表下边界,R代表右边界,L代表左边界,在边界置为1,不在边界置为0

  • 如下示例,请忽略这很丑的字体:)

请忽略我的字体

对直线的裁剪

  • 裁剪分为三类情况

    • 直线完全在区域内,此类情况直线的两个端点区域码均为0000,可以直接保存该线段

在这里插入图片描述

  • 直线部分在区域内,与区域产生交点,此时至少有一个端点区域码不为0000,此时需要求出交点,分段裁剪红色是要保留的!!

在这里插入图片描述

  • 直线在区域之外, 这种最好整,直接裁掉
  • 裁剪的顺序为 左(L)->右(R)->下(B)->上(T)

交点的计算公式

  • 线段与裁剪边界的计算可以使用斜距式直线方程 y = k x + b y=kx+b
  • 假设端点的坐标为: ( x 0 , y 0 ) , ( x 1 , y 1 ) (x_0,y_0), (x_1,y_1) ,则可以得到方程:

    y = y 0 + m ( x x 0 ) y=y_0+m(x-x_0) ,其中 m = y 1 y 0 x 1 x 0 m= \frac{y_1-y_0}{x_1-x_0}\qquad
    同理:
    x = x 0 + y y 0 m x=x_0+\frac{y-y_0}{m}\qquad

  • 按左,右,下,上计算出的四个交点(从1到4为顺序)在这里插入图片描述

编码实现

	#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版>

猜你喜欢

转载自blog.csdn.net/qq_40552152/article/details/105107350