Update
This commit is contained in:
parent
9ca91fccc7
commit
5414a07fa2
7 changed files with 72 additions and 13 deletions
57
line.md
57
line.md
|
@ -63,7 +63,7 @@ TODO: 3D lines
|
|||
|
||||
## Line Drawing Algorithms
|
||||
|
||||
Drawing lines with computers is a subject of [computer graphics](graphics.md). On specific devices such as [vector monitors](vector_monitor.md) this may be a trivial task, however as most display devices nowadays work with [raster graphics](raster_graphics.md), let's from now on focus only on such devices.
|
||||
Drawing lines with computers is a subject of [computer graphics](graphics.md). On specific devices such as [vector monitors](vector_monitor.md) this may be a trivial task, however as most display devices nowadays work with [raster graphics](raster_graphics.md) ([pixel](pixel.md)s!), let's from now on focus only on such devices. It is worth spending some time on [optimizing](optimization.md) your line drawing function as it constitutes a very basic operation -- consider that you will for example be using it for [wireframe](wireframe.md) rendering of a large 3D scene which will require drawing tens of thousands lines each frame -- having a fast line drawing function here can significantly improve your [FPS](fps.md).
|
||||
|
||||
There are many [algorithms](algorithm.md) for line [rasterization](rasterization.md). They differ in attributes such as:
|
||||
|
||||
|
@ -88,6 +88,57 @@ XX XX a.
|
|||
accuracy accuracy + anti-aliasing
|
||||
```
|
||||
|
||||
One of the most basic line rasterization algorithms is the [DDA](dda.md) (Digital differential analyzer), however it is usually better to use at least the [Bresenham's line algorithm](bresenham.md) which is still simple and considerably improves on DDA.
|
||||
One of the most basic line rasterization algorithms is the [DDA](dda.md) (Digital differential analyzer), however it is usually better to use at least the [Bresenham's line algorithm](bresenham.md) which is still simple and considerably improves on DDA by not requiring multiplication or division (slow operations) and by only using integers (no [floating point](float.md)).
|
||||
|
||||
TODO: more algorithms, code example, general form (dealing with different direction etc.)
|
||||
If you just super quickly need to draw something resembling lines for debugging purposes or anything, you may just draw a few points between the two endpoints (idea: make a recursive function that takes point *A* and *B*, average them to get a middle point *M*, draws all three points and then recursively call itself on *A* and *M* and then on *M* and *B*, until the points are close enough -- with integers only the line will probably be warped as we get accumulating rounding errors in the middle point). You may just do something super dirty like [interpolate](interpolation.md) 1000 points between the endpoints with using floating point and draw them all. Just don't use this in anything serious I guess :)
|
||||
|
||||
Let's now take a more serious closer look at line drawing and how the above mentioned algorithms work: consider we want to draw a line between pixels *A = [ax,ay]* and *B = [bx,by]*. Let's also define *dx = bx - ax* and *dy = by - ay*.
|
||||
|
||||
The [naive](naive.md) approach that comes to newcomer's mind is usually this: iterate *x* from *ax* to *bx* and at each step draw the pixel *[x, ay + dy * (x - ax) / dx]*. This has many problems: obviously we are using many slow operations here such as multiplication and division, but most importantly we will in many cases end up with holes in the line we draw. Consider e.g. a line from *[0,0]* to *[2,10]* -- we will only draw 3 pixels (for *x = 0, 1 and 2*), but the whole line is actually 10 pixels high in vertical direction, so we at the very least need those 10 pixels. What's more, consider *dx = 0*, our algorithm will crash on division by zero. This just falls apart very quickly.
|
||||
|
||||
The most common way to deal with this shit is to always convert the line to some simple subcase (by somehow juggling, swapping and flipping the coordinates), usually a line going from left to right under a degree between -45 and 45 degrees (i.e. *abs(dx) >= abs(dy)*). With such a line we now may do what we couldn't before, i.e. just iterate *x* by 1 and at each step compute the corresponding *y*. Once we have these coordinates we somehow convert them back to the space of the original line and draw them.
|
||||
|
||||
Furthermore algorithms improve this on the basis of observation that really while stepping along the *x* line we don't have to compute *y* from scratch, we are just deciding whether *y* stays the same as in previous step or whether it moves by 1 pixel, so drawing a line now boils down to making one yes/no decision at each step. It turns out this decision can be made using only simple integer operations.
|
||||
|
||||
Bresenham's algorithm is based on the following idea: our line has a certain slope *s = dy / dx*; this slope for the common case (described above) will be between -1 and 1. At each step we move 1 pixel horizontally (*x*) and *s* (some fractional part) pixels vertically. We keep accumulating this vertical shift (often called an *error*) and once it jumps over 1, we jump in the vertical (*y*) direction and so on. E.g. if our line is 10 pixels wide (*dx*) and 3 pixels tall (*dy*), our slope is *s = 3/10*; now we start drawing pixels and our error is *3/10*, then *6/10*, the *9/10* and then *12/10*, jumping over 1, which tells us we have to shift vertically (after this we subtract 1 from the current error so we will continue with *2/10*). Now to get rid of fractions (floats) we may simply multiply everything by *dx*; in our case by 10, so we keep adding error *3/10 * 10 = 3* and instead of comparing the error to 1, we compare it to *1 * 10 = 10*.
|
||||
|
||||
All in all, here is a comfy line drawing function based on the above described principle, i.e. needing no floating point, multiplication or division:
|
||||
|
||||
```
|
||||
void drawLine(int ax, int ay, int bx, int by)
|
||||
{
|
||||
int *x = &ax, *y = &ay,
|
||||
stepX = -1 + 2 * (ax <= bx),
|
||||
stepY = -1 + 2 * (ay <= by);
|
||||
|
||||
int dx = stepX == 1 ? (bx - ax) : (ax - bx);
|
||||
int dy = stepY == 1 ? (by - ay) : (ay - by);
|
||||
|
||||
if (dy > dx)
|
||||
{ // swap everything
|
||||
y = &ax; x = &ay;
|
||||
stepX ^= stepY; stepY ^= stepX; stepX ^= stepY;
|
||||
dx ^= dy; dy ^= dx; dx ^= dy;
|
||||
}
|
||||
|
||||
int steps = dx + 1;
|
||||
bx = dx / 2; // use bx as error accumulator
|
||||
|
||||
while (steps)
|
||||
{
|
||||
drawPixel(ax,ay);
|
||||
|
||||
*x += stepX;
|
||||
|
||||
bx += dy;
|
||||
|
||||
if (bx >= dx)
|
||||
{
|
||||
bx -= dx;
|
||||
*y += stepY;
|
||||
}
|
||||
|
||||
steps--;
|
||||
}
|
||||
}
|
||||
```
|
Loading…
Add table
Add a link
Reference in a new issue