Bresenham直线算法与基本二维变换__OpenGL

Bresenham直线算法

首先针对斜率[0,1]的直线。
原理:保证从左往右,从下往上画。每次往右走一个单位准备取像素点;取像素点之前,判断是否需往上走一个单位再取。这里用变量e来做判断。
e初始是-dx,e更新的时候+2dy,然后再判断,要上移就-2dx。这是最终的e,它的演变过程如下。

初始
利用中点判断,把x(针对像素,必为整数)代入直线方程,得到的y的小数部分若>=0.5,像素点上移。
当然不必真的用直线方程计算。每当x+1,有y+k。y同样针对像素,必为整数。引入d来代替方程结果y,用d与0.5比较。
每次x++,d+=k,d就走到这条直线的下一判定处,大于0.5,y++,d-1。
(d代表实际y的小数部分,那么每次y++,有d-1,确保d的值可以直接和0.5比,而不是以后要和1.5,2.5,3.5…)

小升级
把和0.5比较的d,下移0.5,换成e来表示。d一开始是0,那么e一开始是-0.5,e的所有计算变化,与d相同,只是变成与0对比

大升级
刚才,e的相关方程有: e=-0.5, e=e+k,e=e-1
所有方程,两边乘以2Δx。
2eΔx=-Δx 2eΔx=2eΔx+2kΔx=2eΔx+2Δy  2eΔx=2eΔx-2Δx
把所有2eΔx换成e
e=-Δx      e=e+2Δy           e=e-2Δx
斜率[0,1]的直线,OVER

第一步,要保证x0不会在x1的右边。
斜率无穷大(竖线,x0=x1),从下往上画。
其他的斜率情况,往斜率[0,1]转换。
1.斜率[-1,0),即y1低于y0。那么,一镜像,就变成了斜率[0,1]的模样,终点换成镜像点。实际取点则取(x, 2 * y0 - y),换回来。
斜率[-1,0)

2.斜率大于1,同时交换起点终点各自的x和y,如(1,3)to(2,5)变成(3,1)to(5,2),交换后就能直接用斜率[0,1]的逻辑处理像素取点前的判定,实际取点则取(y,x),换回来。如计算取(4,2),实际取(2,4)。

3.斜率小于-1,交换xy,此时是右下到左上,再交换两点位置,变成斜率[-1,0)的情况。对号入座,替换符号就行。斜率小于-1

Bresenham直线_代码

#pragma region Line
void drawOrdinayLine(int x0, int y0, int x1, int y1, int flag) {
	int i;
	int x, y, dx, dy, e;
	dx = x1 - x0;
	dy = y1 - y0;
	e = -dx;
	x = x0; y = y0;
	for (i = 0; i <= dx; i++) {
		switch (flag) {
		case 0:
			glVertex2i(x, y);  break;
		case 1:
			glVertex2i(x, 2 * y0 - y); break;//增量为(y-y0)则,实际增量应取相反方向为y0-(y-y0)=2y0-y
		case 2:
			glVertex2i(y, x); break;//这里也要交换
		case 3:
			glVertex2i(2 * y0 - y, x); break;
		}
		x++;
		e = e + 2 * dy;
		if (e >= 0) {
			y++;
			e = e - 2 * dx;
		}
	}
}
void Swap(int *a, int *b) {
	*a ^= *b;
	*b ^= *a;
	*a ^= *b;
}
void BresenhamLine(int x0, int y0, int x1, int y1) {
	int i;
	if (x0 > x1) {//总是从左端开始
		Swap(&x0, &x1);
		Swap(&y0, &y1);
	}
	if (x0 == x1) {
		if (y0 > y1) {//从y0开始“往上”画,所以不允许y1更低;
			Swap(&y0, &y1);
		}
		for (i = y0; i < y1; i++) {
			glVertex2i(x0, i);
		}
		return;
	}
	float k = (1.0*(y1 - y0)) / (x1 - x0);//根据斜率调整取点
	if (0 <= k && k <= 1) {  //直接画
		drawOrdinayLine(x0, y0, x1, y1, 0);
	} else if (-1 <= k && k < 0) {//以y=y0作B点的对称点
		drawOrdinayLine(x0, y0, x1, y1 + 2 * (y0 - y1), 1);
	} else if (k > 1) {//交换x,y的坐标值。
		Swap(&x0, &y0);//可以直接交换传入函数的参数,这为了直观。
		Swap(&x1, &y1);
		drawOrdinayLine(x0, y0, x1, y1, 2);
	} else if (k < -1) {
		//交换xy
		Swap(&x0, &y0);
		Swap(&x1, &y1);
		//交换两个点
		Swap(&x0, &x1);
		Swap(&y0, &y1);
		drawOrdinayLine(x0, y0, x1, y1 + 2 * (y0 - y1), 3);
	}
}
#pragma endregion

直线算法部分OVER
另外,不觉得这个算法比起中点画线法有哪里提升,不过增量计算是更简单了。

基本二维变换

这就写一下缩放、平移、旋转。
所有的基本二维变换,用矩阵的乘法就可以完成,只需知道该给变换矩阵赋什么样的值。
初始点的向量矩阵,与变换矩阵相乘,得到变换后的结果。
书上的公式用的是列向量。若用行向量的话只需要把变换矩阵转置,然后交换左右,得到的结果也是行向量。一定要注意左右。

lll
这里都是针对(0,0)原点来运算的。而要针对任意点变换,书上介绍的方法是:平移到原点,变换,平移回去。
若要针对不同方向缩放,而非沿着xy轴缩放,同理:旋转一个角度(根据需求方向),针对xy轴缩放,旋转回去。
先执行的,为左。
连续变换也很简单,如平移/旋转,变换矩阵是可加的,把平移量或者旋转角度相加即可,连续缩放则将缩放因子相乘。
这里用一个五角星验证。

	Point a, b, c, d, e;
	a.x = 60 - 20 * sin(36 * 3.14 / 180); a.y = 60 - 20 * cos(36 * 3.14 / 180);
	b.x = 60; b.y = 80;
	c.x = 60 + 20 * sin(36 * 3.14 / 180); c.y = 60 - 20 * cos(36 * 3.14 / 180);
	d.x = 60 - 20 * cos(18 * 3.14 / 180); d.y = 60 + 20 * sin(18 * 3.14 / 180);
	e.x = 60 + 20 * cos(18 * 3.14 / 180); e.y = 60 + 20 * sin(18 * 3.14 / 180);

在这里插入图片描述

全部代码

#include <GL/glut.h>
#include <stdio.h>
#include <math.h>
#include <iostream>
using namespace std;

float deltax, deltay;
float intervalx, intervaly;
float _angle;
struct Point {
	int x;
	int y;
};
/*采用行向量矩阵,把P'=M*P的变换矩阵M和列向量P转置,CT=(AB)T=BT*AT,得到的结果也是行向量*/
void MultiplyMatrixAB(int a[3][3], float b[3][3], float c[3][3], int row) {
	int i, j, k, sum;
	for (i = 0; i < row; i++) {
		for (k = 0; k < 3; k++) {
			sum = 0;
			for (j = 0; j < 3; j++)
				sum += a[i][j] * b[j][k];
			c[i][k] = sum;
		}
	}
}
void Scaling2D(Point shape[], int n, Point Rshape[]) {
	/*这里是左边P乘以右边S,左边是仅有一行,相当于是把计算设计时的P'=S*P的P转置后放在左边。
	右边S也是相当于转置过的矩阵,在这无法体现。旋转变化时,rotate[0][1]就取正sin,rotate[1][0]=-sin
	用列向量的公式是第一行第二列取-sin,第二行第一列取sin。只要正确转置就行。*/
	float scale[3][3] = { 1,0,0,0,1,0,0,0,1 };
	scale[0][0] = deltax;
	scale[1][1] = deltay;
	int pnt[1][3] = { 0,0,1 };
	int i;
	float result[1][3] = { 0 };
	for (i = 0; i < n; i++) {//有n个点要变换,循环n次
		pnt[0][0] = shape[i].x;//shape存储了变形前的坐标,每个点依次变换
		pnt[0][1] = shape[i].y;
		MultiplyMatrixAB(pnt, scale, result, 1);//数组的值存在堆中,数组传递给函数的变量是一个指针,被调函数内通过指针直接改变存储的值
		Rshape[i].x = result[0][0];//Rshape存储了变形后的坐标
		Rshape[i].y = result[0][1];
	}
}
void Translation2D(Point shape[], int n, int deltax, int deltay, Point Rshape[]) {
	float trans[3][3] = { 1,0,0,0,1,0,0,0,1 };
	trans[2][0] = deltax;//同样,相比于笔记的公式,转个置。
	trans[2][1] = deltay;
	int pnt[1][3] = { 0,0,1 };
	int i;
	float result[1][3] = { 0 };
	for (i = 0; i < n; i++) {
		pnt[0][0] = shape[i].x;
		pnt[0][1] = shape[i].y;
		MultiplyMatrixAB(pnt, trans, result, 1);
		Rshape[i].x = result[0][0];
		Rshape[i].y = result[0][1];
	}
}
void Rotation2D(Point shape[], int n, float angle, Point Rshape[]) {
	angle *= 3.14 / 180;
	float rotate[3][3] = { 0,0,0,0,0,0,0,0,1 };
	rotate[0][0] = rotate[1][1] = cos(angle);
	rotate[0][1] = sin(angle);
	rotate[1][0] = -rotate[0][1];//取负肯定比再sin一次快。
	int pnt[1][3] = { 0,0,1 };
	int i;
	float result[1][3] = { 0 };
	for (i = 0; i < n; i++) {
		pnt[0][0] = shape[i].x;
		pnt[0][1] = shape[i].y;
		MultiplyMatrixAB(pnt, rotate, result, 1);
		Rshape[i].x = result[0][0];
		Rshape[i].y = result[0][1];
	}
}
/*Bresenham画线法*/
#pragma region Line
void drawOrdinayLine(int x0, int y0, int x1, int y1, int flag) {
	int i;
	int x, y, dx, dy, e;
	dx = x1 - x0;
	dy = y1 - y0;
	e = -dx;
	x = x0; y = y0;
	for (i = 0; i <= dx; i++) {
		switch (flag) {
		case 0:
			glVertex2i(x, y);  break;
		case 1:
			glVertex2i(x, 2 * y0 - y); break;//增量为(y-y0)则,实际增量应取相反方向为y0-(y-y0)=2y0-y
		case 2:
			glVertex2i(y, x); break;//这里也要交换
		case 3:
			glVertex2i(2 * y0 - y, x); break;
		}
		x++;
		e = e + 2 * dy;
		if (e >= 0) {
			y++;
			e = e - 2 * dx;
		}
	}
}
void Swap(int *a, int *b) {
	*a ^= *b;
	*b ^= *a;
	*a ^= *b;
}
void BresenhamLine(int x0, int y0, int x1, int y1) {
	int i;
	if (x0 > x1) {//总是从左端开始
		Swap(&x0, &x1);
		Swap(&y0, &y1);
	}
	if (x0 == x1) {
		if (y0 > y1) {//从y0开始“往上”画,所以不允许y1更低;
			Swap(&y0, &y1);
		}
		for (i = y0; i < y1; i++) {
			glVertex2i(x0, i);
		}
		return;
	}
	float k = (1.0*(y1 - y0)) / (x1 - x0);//根据斜率调整取点
	if (0 <= k && k <= 1) {  //直接画
		drawOrdinayLine(x0, y0, x1, y1, 0);
	} else if (-1 <= k && k < 0) {//以y=y0作B点的对称点
		drawOrdinayLine(x0, y0, x1, y1 + 2 * (y0 - y1), 1);
	} else if (k > 1) {//交换x,y的坐标值。
		Swap(&x0, &y0);//可以直接交换传入函数的参数,这为了直观。
		Swap(&x1, &y1);
		drawOrdinayLine(x0, y0, x1, y1, 2);
	} else if (k < -1) {
		//交换xy
		Swap(&x0, &y0);
		Swap(&x1, &y1);
		//交换两个点
		Swap(&x0, &x1);
		Swap(&y0, &y1);
		drawOrdinayLine(x0, y0, x1, y1 + 2 * (y0 - y1), 3);
	}
}
#pragma endregion
void Transform2DSegment() {
	glClear(GL_COLOR_BUFFER_BIT);
	glColor3f(0.0, 1.0, 1.0);
	glBegin(GL_POINTS);
	//绘制带小箭头的坐标系
	BresenhamLine(10, 200, 400, 200);
	BresenhamLine(395, 195, 400, 200);
	BresenhamLine(400, 200, 395, 205);
	BresenhamLine(200, 400, 200, 10);
	BresenhamLine(200, 400, 205, 395);
	BresenhamLine(200, 400, 195, 395);
	//试验
	Point a, b, c, d, e;
	a.x = 60 - 20 * sin(36 * 3.14 / 180); a.y = 60 - 20 * cos(36 * 3.14 / 180);
	b.x = 60; b.y = 80;
	c.x = 60 + 20 * sin(36 * 3.14 / 180); c.y = 60 - 20 * cos(36 * 3.14 / 180);
	d.x = 60 - 20 * cos(18 * 3.14 / 180); d.y = 60 + 20 * sin(18 * 3.14 / 180);
	e.x = 60 + 20 * cos(18 * 3.14 / 180); e.y = 60 + 20 * sin(18 * 3.14 / 180);

	Point shape[5] = { a,b,c,d,e };
	Point Rshape[5] = { 0 };
	Scaling2D(shape, 5, Rshape);
	Translation2D(Rshape, 5, intervalx, intervaly, Rshape);
	Rotation2D(Rshape, 5, _angle, Rshape);
	//到第一象限显示
	for (int i = 0; i < 5; i++) {
		shape[i].x = 200 + shape[i].x;
		Rshape[i].x = 200 + Rshape[i].x;
		shape[i].y = 200 + shape[i].y;
		Rshape[i].y = 200 + Rshape[i].y;
	}
	//输出结果
	for (int i = 0; i < 4; i++) {
		BresenhamLine(shape[i].x, shape[i].y, shape[i + 1].x, shape[i + 1].y);
		BresenhamLine(Rshape[i].x, Rshape[i].y, Rshape[i + 1].x, Rshape[i + 1].y);
	}
	BresenhamLine(shape[0].x, shape[0].y, shape[4].x, shape[4].y);
	BresenhamLine(Rshape[0].x, Rshape[0].y, Rshape[4].x, Rshape[4].y);
	glEnd();
	glFlush();
}
void main(int argc, char **argv) {
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
	cout << "输入X轴与Y轴的缩放因子" << endl;
	cin >> deltax >> deltay; 
	cout << "输入X轴与Y轴的坐标平移量" << endl;
	cin >> intervalx >> intervalx;
	cout << "输入旋转角度-360~360" << endl;
	cin >> _angle;
	glutInitWindowPosition(50, 100);
	glutInitWindowSize(500, 400);
	glutCreateWindow("变,都可以变");
	glClearColor(0.5, 0.5, 0.5, 0);
	glMatrixMode(GL_PROJECTION);
	gluOrtho2D(0.0, 500.0, 0.0, 500.0);
	glutDisplayFunc(Transform2DSegment);
	glutMainLoop();
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值