首页 > 代码库 > 游戏中DDA算法和Bresenham算法的应用

游戏中DDA算法和Bresenham算法的应用

在角色扮演或即时战略游戏中,经常会将角色以最佳的方式走到指定地点。游戏场景的地面情况复杂,而且场面大,若采用盲目式搜索,例如盲目穷举法,则几乎要遍历整个场景,效率非常低,造成角色反应速度过慢,实践证明是一种不适合网络游戏寻路方法。而启发式搜索算法在障碍较少的情况下也显得效率过低。

DDA算法和Bresenham算法是游戏寻路中绘制直线的两种常用算法。

在列举这两算法之前,我先定义坐标的结构体代码:

struct PixelNode
{
	uint16_t x;
	uint16_t y;
};

数值微分画线算法(DDA)法是直线生成算法中最简单的一种,它是一种单步直线生成算法。它的算法是这样的:首先根据直线的斜率确定是以X方向步进还是以Y方向步进,然后沿着步进方向每步进一个点(象素),就沿另一个坐标变量k,k是直线的斜率,因为是对点阵设备输出的,所以需要对每次计算出来的一对坐标进行圆整。

        具体算法的实现,除了判断是按照X方向还是按照Y方向步进之外,还要考虑直线的方向,也就是起点和终点的关系。

DDA算法的描述以及原理:

设(x1,y1)和(x2,y2)分别为所求直线的起点和终点坐标,由直线的微分方程得 

可通过计算由x方向的增量△x引起y的改变来生成直线:

也可通过计算由y方向的增量△y引起x的改变来生成直线:

 
  选定x2-x1和y2-y1中较大者作为步进方向(假设x2-x1较大),取该方向上的增量为一个象素单位(△x=1)。

然后利用式(2-1)计算另一个方向的增量(△y=△x·m=m)。通过递推公式(2-2)至(2-5),把每次计算出的(xi+1,yi+1)

经取整后送到显示器输出,则得到扫描转换后的直线。   之所以取x2-x1和y2-y1中较大者作为步进方向,是考虑沿

着线段分布的象素应均匀,这在下图中可看出。 
  另外,算法实现中还应注意直线的生成方向,以决定Δx及Δy是取正值还是负值。

所以他的实现步骤是这样描述的:

    1、已知直线的两端点坐标:(x1,y1),(x2,y2)   

    2、已知画线的颜色:color 
    3、计算两个方向的变化量:dx=x2-x1                dy=y2-y1   

    4、求出两个方向最大变化量的绝对值: 
               steps=max(|dx|,|dy|)   

    5、计算两个方向的增量(考虑了生成方向):                xin=dx/steps                yin=dy/steps   

    6、设置初始象素坐标:x=x1,y=y1 

    7、用循环实现直线的绘制:    

for(i=1;i<=steps;i++)  
{ 
    	putpixel(x,y,color);/*在(x,y)处,以color色画点*/     
	x=x+xin;       
	y=y+yin;     
}

 下面给一段code来说明一下这个算法:

bool DDA::CalcLineNodes(int x1, int y1, int x2, int y2,vector<PixelNode>& pointList) { 
	float increx,increy,x,y;
	int steps,i;  
	if(x1 == x2 && y1 == y2){
		return false;
	}
	pointList.clear();
	if(abs(x2 - x1) > abs(y2 - y1)) {  
		steps= abs(x2 - x1);  
	}
	else { 
		steps = abs(y2-y1);
	}	
	increx = (float)(x2 - x1)/steps;  
	increy = (float)(y2 - y1)/steps; 
	x = x1;  
	y = y1;  
	for(i = 1;i <= steps;i ++)  { 
		//putpixel(x, y, color); //在(x,y)处,以color色画点   
		PixelNode p(x,y);
		pointList.push_back(p);
		x+=increx;
		y+=increy;  
   } 
   return true;
}

  但是实际上数值微分法(DDA法)产生的直线比较精确,而且逻辑简单,易于用硬件实现,但是步进量x,y和k必须用浮点数表示,每一步都要对x或y进行四舍五入后取整,不利于光栅化或点阵输出。生成直线的时间较长。

  bresenham算法的特点以及原理:

由直线的斜率确定选择在x方向或y方向上每次递增(减)1个单位,另一变量的递增(减)量为01,它取决于实际直线与最近光栅网格点的距离,这个距离的最大误差为0.5。 

假定直线斜率k0~1之间。此时,只需考虑x方向每次递增1个单位,决定y方向每次递增01

直线当前点为(xi,y)

    直线当前光栅点为(xi,yi)

下一个直线的点应为(xi+1,y+k)   下一个直线的光栅点为右光栅点(xi+1,yi)y方向递增量0)或为右上光栅点(xi+1,yi+1)y方向递增量1

记直线与它垂直方向最近的下光栅点的误差为d,有:d=(y+k)–yi,且

    0≤d≤1   d<0.5:下一个象素应取右光栅点(xi+1,yi)   d≥0.5:下一个象素应取右上光栅点(xi+1,yi+1)

在这里我给出部分实现的代码:

//返回列表中 包含起始点,不包含目标点
bool Bresenham::CalcLineNodes(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, std::vector<PixelNode>& rNodesVec)
{
	if (x1 == x2 && y1 == y2)
	{
		return false;
	}
	rNodesVec.clear();

    int dx = abs(x2 - x1);//△x 
    int dy = abs(y2 - y1);//△y
    int p = (2 * dy) - dx ; //P = 2△y - △x

    int PointX = x1;//起始坐标X
    int PointY = y1;//起始坐标Y

    int StepX = 0;
	int StepY = 0;

	if (y1 < y2)
	{
		StepY = 1;//y递增
	}
	else//y1 > y2
	{
		StepY = -1;//y递减
	}
	if (x1 < x2)
	{
		StepX = 1;//x递增	
	}
	else
	{
		StepX = -1;//x递减
	}

    // 达到第一个起始点
	PixelNode pt0;
	pt0.x = PointX;
	pt0.y = PointY;
	rNodesVec.push_back(pt0);
    
	if (dx > dy)//横向跨度大,以x为步长
	{
		int twoDy = 2 * dy ; // 2 △y
		int twoDyDx = 2 * (dy - dx) ; // 2(△y - △x)
		while (PointX != x2)
		{
			PointX += StepX;
				
			if (p < 0)
			{
				p += twoDy;
			}
			else
			{
				PointY += StepY;
				p += twoDyDx;
			}
			
			PixelNode pt;
			pt.x = PointX;
			pt.y = PointY;
			rNodesVec.push_back(pt);

		}
		
	}
	else//纵向跨度大,以y为步长
	{
		int twoDx = 2 * dx ; // 2 △x
		int twoDxDy = 2 * (dx - dy) ; // 2(△x - △y)
		
		while (PointY != y2)
		{
			PointY += StepY;

			if (p < 0)
			{
				p += twoDx;
			}
			else
			{
				PointX += StepX;
				p += twoDxDy;
			}

			PixelNode pt;
			pt.x = PointX;
			pt.y = PointY;
			rNodesVec.push_back(pt);
			
		}
	}

	return true;
}

游戏中DDA算法和Bresenham算法的应用