在射击类游戏开发中经常会遇到“物体(如子弹)直线飞行问题”,即:已知物体的起点与终点,如何计算出物体的直线运动路径,当然,这里的物体位置大多是整数。查了几篇帖子,原来这是计算机图形学中的一个经典算法--Bresenham直线算法。
几篇帖子收集总结一下,主要来源是 http://en.wikipedia.org/ 和 http://www.koders.com/
The Bresenham line algorithm is an algorithm which determines which points in an n-dimensional raster should be plotted in order to form a close approximation to a straight line between two given points. It is commonly used to draw lines on a computer screen, as it uses only integer addition, subtraction and bit shifting, all of which are very cheap operations in standard computer architectures. It is one of the earliest algorithms developed in the field of computer graphics. A minor extension to the original algorithm also deals with drawing circles.
Bresenham直线演算法是用来描绘由两点所决定的直线的算法,它会计算出一条线段在 n 维光栅上最接近的点。这个演算法不适用浮点数计算,而只用到较为快速的整数加法、减法和位元移位,常用于绘制电脑画面中的直线。是计算机图形学中最先发展出来的经典算法。经过少量的延伸之后,原本用来画直线的演算法也可用来画圆。
如图为Bresenham直线演算法描绘的直线,下面简述之。
假设我们需要由 (x0, y0) 这一点,绘画一直线至右下角的另一点(x1, y1),x,y分别代表其水平及垂直座标,并且 x1 - x0 > y1 - y0。在此我们使用电脑系统常用的座标系,即x座标值沿x轴向右增长,y座标值沿y轴向下增长。
因此x及y之值分别向右及向下增加,而两点之水平距离为x1 ? x0且垂直距离为y1-y0。由此得之,该线的斜率必定介乎于1至0之间。而此算法之目的,就是找出在x0与x1之间,第x行相对应的第y列,从而得出一像素点,使得该像素点的位置最接近原本的线。
对于由(x0, y0)及(x1, y1)两点所组成之直线,公式如下:
因此,对于每一点的x,其y的值是
因为x及y皆为整数,但并非每一点x所对应的y皆为整数,故此没有必要去计算每一点x所对应之y值。反之由于此线之斜率介乎于1至0之间,故此我们只需要找出当x到达那一个数值时,会使y上升1,若x尚未到此值,则y不变。至于如何找出相关的x值,则需依靠斜率。 斜率之计算方法为m = (y1 - y0) / (x1 - x0)。由于此值不变,故可于运算前预先计算,减少运算次数。
要实行此算法,我们需计算每一像素点与该线之间的误差。于上述例子中,误差应为每一点x中,其相对的像素点之y值与该线实际之y值的差距。每当x的值增加1,误差的值就会增加m。每当误差的值超出0.5,线就会比较靠近下一个映像点,因此y的值便会加1,且误差减1。
下列伪代码是这算法的简单表达(其中的plot(x,y)绘画该点,abs返回的是绝对值)。虽然用了代价较高的浮点运算,但很容易就可以改用整数运算(详见最佳化一节):
function line(x0, x1, y0, y1) int deltax := x1 - x0 int deltay := y1 - y0 real error := 0 real deltaerr := deltay / deltax // 假設 deltax != 0 (非垂直線), // 注意:需保留除法運算結果的小數部份 int y := y0 for x from x0 to x1 plot(x,y) error := error + deltaerr if abs(error) ≥ 0.5 then y := y + 1 error := error - 1.0
虽然以上的演算法只能绘画由右上至左下,且斜率小于或等于1的直线,但我们可以扩展此演算法,使之可绘画任何的直线。第一个扩展是绘画反方向,即由左下至右上的直线。这可以简单地透过在x0 > x1时交换起点和终点来做到。第二个扩展是绘画斜率为负的直线。可以检查y0 ≥ y1是否成立;若该不等式成立,误差超出0.5时y的值改为加-1。最后,我们还需要扩展该演算法,使之可以绘画斜率绝对值大于1的直线。要做到这点,我们可以利用大斜率直线对直线y=x的反射是一条小斜率直线的事实,在整个计算过程中交换 x 和 y,并一并将plot的参数顺序交换。扩展后的伪代码如下:
function line(x0, x1, y0, y1) boolean steep := abs(y1 - y0) > abs(x1 - x0) if steep then swap(x0, y0) swap(x1, y1) if x0 > x1 then swap(x0, x1) swap(y0, y1) int deltax := x1 - x0 int deltay := abs(y1 - y0) real error := 0 real deltaerr := deltay / deltax int ystep int y := y0 if y0 < y1 then ystep := 1 else ystep := -1 for x from x0 to x1 if steep then plot(y,x) else plot(x,y) error := error + deltaerr if error ≥ 0.5 then y := y + ystep error := error - 1.0
以上的程序可以处理任何的直线,实作了完整的Bresenham直线演算法。
以上的程序有一个问题:电脑处理浮点运算的速度比较慢,而error与deltaerr的计算是浮点运算。此外,error的值经过多次浮点数加法之后,可能有累积误差。使用整数运算可令演算法更快、更准确。只要将所有以上的分数数值乘以deltax,我们就可以用整数来表示它们。唯一的问题是程序中的常数0.5—我们可以透过改变error的初始方法,以及将error的计算由递增改为递减来解决。新的程序如下:
function line(x0, x1, y0, y1) boolean steep := abs(y1 - y0) > abs(x1 - x0) if steep then swap(x0, y0) swap(x1, y1) if x0 > x1 then swap(x0, x1) swap(y0, y1) int deltax := x1 - x0 int deltay := abs(y1 - y0) int error := deltax / 2 int ystep int y := y0 if y0 < y1 then ystep := 1 else ystep := -1 for x from x0 to x1 if steep then plot(y,x) else plot(x,y) error := error - deltay if error < 0 then y := y + ystep error := error + deltax
Jack E. Bresenham于1962年在IBM发明了此演算法。据他本人表示,他于1963年在丹佛举行的美国计算机协会全国大会上发表了该演算法,论文则登载于1965年的《IBM系统期刊》 (IBM Systems Journal) 之中。Bresenham直线演算法其后被修改为能够画圆,修改后的演算法有时被称为“Bresenham画圆演算法”或中点画圆演算法。
/* * Copyright (c) 2002 Shaven Puppy Ltd * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'Shaven Puppy' nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.shavenpuppy.jglib.algorithms; /** * Bresenham's famous line drawing algorithm. Works for 2D. */ public final class Bresenham { /** The start and end of the line */ private int x1, y1, x2, y2; /** Used for calculation */ private int dx, dy, error, x_inc, y_inc, xx, yy, length, count; /** General case algorithm */ private static final Bresenham bresenham = new Bresenham(); /** * Construct a Bresenham algorithm. */ public Bresenham() { } /** * Plot a line between (x1,y1) and (x2,y2). To step through the line use next(). * @return the length of the line (which will be 1 more than you are expecting). */ public int plot(int x1, int y1, int x2, int y2) { this.x1 = x1; this.x2 = x2; this.y1 = y1; this.y2 = y2; // compute horizontal and vertical deltas dx = x2 - x1; dy = y2 - y1; // test which direction the line is going in i.e. slope angle if (dx >= 0) { x_inc = 1; } else { x_inc = -1; dx = -dx; // need absolute value } // test y component of slope if (dy >= 0) { y_inc = 1; } else { y_inc = -1; dy = -dy; // need absolute value } xx = x1; yy = y1; if (dx > 0) error = dx >> 1; else error = dy >> 1; count = 0; length = Math.max(dx, dy) + 1; return length; } /** * Get the next point in the line. You must not call next() if the * previous invocation of next() returned false. * * Retrieve the X and Y coordinates of the line with getX() and getY(). * * @return true if there is another point to come. */ public boolean next() { // now based on which delta is greater we can draw the line if (dx > dy) { // adjust the error term error += dy; // test if error has overflowed if (error >= dx) { error -= dx; // move to next line yy += y_inc; } // move to the next pixel xx += x_inc; } else { // adjust the error term error += dx; // test if error overflowed if (error >= dy) { error -= dy; // move to next line xx += x_inc; } // move to the next pixel yy += y_inc; } count ++; return count < length; } /** * @return the current X coordinate */ public int getX() { return xx; } /** * @return the current Y coordinate */ public int getY() { return yy; } /** * Plot a line between (x1,y1) and (x2,y2). The results are placed in x[] and y[], which must be large enough. * @return the length of the line or the length of x[]/y[], whichever is smaller */ public static final int plot(final int x1, final int y1, final int x2, final int y2, final int x[], final int y[]) { int length = Math.min(x.length, Math.min(y.length, bresenham.plot(x1, y1, x2, y2))); for (int i = 0; i < length; i ++) { bresenham.next(); x[i] = bresenham.getX(); y[i] = bresenham.getY(); } return length; /* int dx; // difference in x's int dy; // difference in y's int error = 0; // the discriminant i.e. error i.e. decision variable int x_inc; int y_inc; int index; // used for looping // compute horizontal and vertical deltas dx = x2 - x1; dy = y2 - y1; // test which direction the line is going in i.e. slope angle if (dx >= 0) { x_inc = 1; } else { x_inc = -1; dx = -dx; // need absolute value } // test y component of slope if (dy >= 0) { y_inc = 1; } else { y_inc = -1; dy = -dy; // need absolute value } int xx = x1, yy = y1; // now based on which delta is greater we can draw the line if (dx > dy) { error = dx >> 1; // draw the line for (index = 0; index <= dx && index < x.length; index++) { // remember the point x[index] = xx; y[index] = yy; // adjust the error term error += dy; // test if error has overflowed if (error >= dx) { error -= dx; // move to next line yy += y_inc; } // move to the next pixel xx += x_inc; } return Math.min(x.length, dx); } else { error = dy >> 1; // draw the line for (index = 0; index <= dy && index < y.length; index++) { // remember the point x[index] = xx; y[index] = yy; // adjust the error term error += dx; // test if error overflowed if (error >= dy) { error -= dy; // move to next line xx += x_inc; } // move to the next pixel yy += y_inc; } return Math.min(y.length, dy); } */ } }
英文维基 http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
中文维基 http://zh.wikipedia.org/zh-sg/%E5%B8%83%E9%9B%B7%E6%A3%AE%E6%BC%A2%E5%A7%86%E7%9B%B4%E7%B7%9A%E6%BC%94%E7%AE%97%E6%B3%95
源码 http://www.koders.com/kv.aspx?fid=819389938BB7A31938069AC95B2D8F1E9E082730
二维图形的生成技术(课件) http://www.docin.com/p-57559557.html#
改进的Bresenham算法(课件) http://www.docin.com/p-27040137.html#
计算机图形学:直线算法(DDA,中点画线,Bresenham) http://hi.baidu.com/513980209/blog/item/4fd992cacb069f81c81768ff.html