gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

DS development > draw a triangle

#162772 - hacker013 - Fri Sep 12, 2008 5:59 pm

hey everybody,

has anybody a formula for drawing a filled triangle??
_________________
Website / Blog

Let the nds be with you.

#162773 - Justice - Fri Sep 12, 2008 6:33 pm

2D or 3D? You can use openGl! Example:
3D
Code:

glBegin(GL_TRIANGLES);                        
glVertex3f( 0.0f, 1.0f, 0.0f);               
glVertex3f(-1.0f,-1.0f, 0.0f);               
glVertex3f( 1.0f,-1.0f, 0.0f);               
glEnd();   

#162774 - DekuTree64 - Fri Sep 12, 2008 6:33 pm

In software you mean? It's a lot like drawing lines. Just interpolate down the left and right edges of the triangle, and draw horizontal spans between them.

If you mean drawing with the 3D hardware, you at least need to set the projection matrix, set the backdrop color, set the vertex color, send some vertices (with some positive or negative Z, I can never remember which), and send a swap buffers command. There should be functions in libnds for all that, and probably some examples around somewhere.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#162775 - hacker013 - Fri Sep 12, 2008 6:48 pm

no, i mean in 2D.

@DekuTree64, it is not so easy as you say.
_________________
Website / Blog

Let the nds be with you.

#162777 - DekuTree64 - Fri Sep 12, 2008 7:33 pm

Yes, of course not :)
Well, the theory is that simple, but implementing it can get confusing with so many interpolations going on at once. Plus there are tricky accuracy problems that cause cracks between polygons in a model.

Here is a good article on scan converting, which also covers the accuracy problems.

Get it working flat shaded first. After that it's not too tough to add goraud shading and/or texture mapping.

Perspective correct texture mapping is hard though, just because it's unreasonably slow if you do it the straightforward way. Well, it's probably unreasonably slow nomatter what you do on a DS. But less than on GBA, since there's independent division hardware. Chris Hecker wrote some good articles on texture mapping using floating point hardware for the perspective division, which should be applicable to DS.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#162783 - Miked0801 - Fri Sep 12, 2008 9:27 pm

Software triangle. Use a fast line drawing algorithm to 'draw' the edges of the triangle in a 2D array of xPositions. Fill the array with some flag so that you will know if something has already drawn in the first field. When you draw all three edges, your array will hold the X start and X Stop positions.

Now run thru the list and quickly draw horizontal lines between the X start and x stop positions. If the 2nd entry is blank, it's a 1 pixel line.

You've just drawn a scanline converted traingle to screen somewhat quickly. To add shading, add another field for start and stop colors to each X position and interpolate across the line.

#162791 - keldon - Fri Sep 12, 2008 11:24 pm

The scanline method is about the easiest to code; you can even store uv and uz deltas for texture mapping without too much effort.

Just to show you in code:
Code:
fill_poly (point1, point2, point3)
{
   draw_line (point1, point2, min_max);
   draw_line (point2, point3, min_max);
   draw_line (point3, point1, min_max);

   interpolate(min_max);
}

draw_line (a, b, min_max)
{
   foreach (pixel p in a->b)
   {
      put_pixel (p);
      min_max[p.y].min = MIN (p.x, min_max.min[p.y]);
      min_max[p.y].max = MAX (p.x, min_max.max[p.y]);
   }
}

interpolate (min_max)
{
   for (y = 0; y < min_max.max_y(); ++y)
   {
      for (x = min_max[y].min; x < min_max[y].max; ++x) put_pixel (x,y);
   }
}


And just as mike said, you can store what you want at the beginning and end of the line. In the interpolate method you might read that RED is the colour on the left and BLUE is the colour on the right, and write a gradient using the interpolated values.

As a matter of fact, I've used it a few times myself.

p.s. I didn't implement it using those methods exactly, I'm just trying to show you the general logic.

#162802 - hacker013 - Sat Sep 13, 2008 10:01 am

keldon wrote:
The scanline method is about the easiest to code; you can even store uv and uz deltas for texture mapping without too much effort.

Just to show you in code:
Code:
fill_poly (point1, point2, point3)
{
   draw_line (point1, point2, min_max);
   draw_line (point2, point3, min_max);
   draw_line (point3, point1, min_max);

   interpolate(min_max);
}

draw_line (a, b, min_max)
{
   foreach (pixel p in a->b)
   {
      put_pixel (p);
      min_max[p.y].min = MIN (p.x, min_max.min[p.y]);
      min_max[p.y].max = MAX (p.x, min_max.max[p.y]);
   }
}

interpolate (min_max)
{
   for (y = 0; y < min_max.max_y(); ++y)
   {
      for (x = min_max[y].min; x < min_max[y].max; ++x) put_pixel (x,y);
   }
}


And just as mike said, you can store what you want at the beginning and end of the line. In the interpolate method you might read that RED is the colour on the left and BLUE is the colour on the right, and write a gradient using the interpolated values.

As a matter of fact, I've used it a few times myself.

p.s. I didn't implement it using those methods exactly, I'm just trying to show you the general logic.


Can you add some explanation because i don't get it?
_________________
Website / Blog

Let the nds be with you.

#162809 - keldon - Sat Sep 13, 2008 11:30 am

Sorry, that wasn't intended; I was just trying to add to what Mike said by showing you some psuedo code to demonstrate it.

In short; the polygon is drawn (mostly) by interpolate. All interpolate does is run along the y-plane and reading what the minimum and maximum x values are. For example, if you had a line from (0,0) to (10,0) then all min_max would store is:
- y[0] {min = 0, max = 10}

You can implement min_max by creating a 2d array, such as:
- int min_max [2][height]

... where min_max[0][y] gives the minimum, and min_max[1][y] gives the max (alternatively have two arrays, one for min and one for max). Initialize min_max so that min=WIDTH and max=0 for all y.

So to recap, the interpolate method just takes a min_max array. To prepare min_max simply draw lines between the three points, updating min_max as you go along. As you can see in the draw_line method, nothing more than storing the minimum and maximum pixels for the line is required - the put_pixel call in draw_line (now I think of it) is not even needed.

This simple algorithm can allow you to draw [simple] polygons of any length (so long as all polygons have inner angles < 180 degrees).

#162810 - hacker013 - Sat Sep 13, 2008 12:29 pm

can you give an working example because it is for me to complicated.
_________________
Website / Blog

Let the nds be with you.

#162811 - hacker013 - Sat Sep 13, 2008 12:37 pm

Oeps, dubble post
_________________
Website / Blog

Let the nds be with you.

#162812 - chuckstudios - Sat Sep 13, 2008 1:08 pm

hacker013 wrote:
can you give an working example because it is for me to complicated.


To be honest, part of the joy of programming is tackling challenges by yourself. Just experiment, and remember to have fun with it.

#162814 - keldon - Sat Sep 13, 2008 2:20 pm

hacker013 wrote:
can you give an working example because it is for me to complicated.


Note: I just spent the last 30 minutes trying to explain my code (in this post), but it was a texture mapping algorithm so I decided that I might as well just strip it of that code and turn it into a fill-poly routine.

It still has code geared for texture mapping, and it was written a long time ago for my image processing coursework so you will have to excuse the code a little. The section where KEdge is used to draw the line can easily be replaced with a different line drawing algorithm.

Code:
/*!
   Draws a texture mapped polygon to img.
   
   \param   img         Destination image
   \param   points      KPoint*[npoints]
   \param   npoints      Number of points in 'points' parameter.
*/
void drawPoly (wxImage *img, const KPoint **points, int npoints)
{
   int minx [img->getHeight()];
   int maxx [img->getHeight()];
   
   int miny = img->getHeight();
   int maxy = 0;
   
   //   >
   //      Initialize min/max in such a way that nothing will be drawn for each line
   //   >
   for ( int i = 0; i < img->getHeight(); i ++ )
   {
      minx[i] = img->getWidth();
      maxx[i] = 0;
   }
   
   //   >
   //      Calculate minx and maxx for each line, aswell as miny and maxy
   //   >
   for ( int i = 0; i < npoints -1; i ++ )
   {
      KPoint *p1 = points[i];
      KPoint *p2 = points[i+1];
      KEdge e( p1, p2);
      
      //   >
      //      KEdge stores some calculations on an edge designed to draw
      //      an edge line by line. In short, each point in the line will be
      //      reached.
      //   >
      for ( int dx = e.a->x <<16, dy = e.a->y <<16, cy = 0; cy < e.diffy ; cy++ )
      {
         int x = dx >> 16;
         int y = dy >> 16;

         if ( x < minx[y])
         {
            minx[y] = x;
         }

         if ( x > maxx[y]) maxx[y] = x;
         
         if ( y < miny ) miny = y;
         if ( y > maxy ) maxy = y;
         
         dx += e.dx;
         dy += e.dy;
      }
   }


   //   >
   //      Render shape using min/max values created when drawing the lines.
   //   >
   for ( int y = miny; y <= maxy; y ++ )
   {
      for ( int x = minx[y]; x <= maxx[y]; x++ )
      {
         img->SetRGB(x,y, WHITE);
         
      }
   }
}


Edit: oops, missed a bit of code


Last edited by keldon on Sat Sep 13, 2008 9:28 pm; edited 1 time in total

#162815 - hacker013 - Sat Sep 13, 2008 2:29 pm

thank you very much , but i'm needing for drawing a triangle in 2D on the screen with cordinates like x1, y1, x2, y2 , etc. For this i muss use a image, it is complicated, i don't get it anymore, it is confusing (for me).
_________________
Website / Blog

Let the nds be with you.

#162837 - keldon - Sat Sep 13, 2008 11:09 pm

My apologies, I overestimated your programming experience and thought you would know where to go from here. Simply replace the "calculate minx and maxx" section with a standard line drawing algorithm, and replace img->setPixel with your own set_pixel. My code is interfacing with wxWidgets, but that structure can easily be swapped with just about anything - for example width and height can be function parameters and you could just be passing an array to pixel data ^_^

Judging by your post, I guess you just want the code; if nobody has done it by 7pm GMT tomorrow then I'll put this line drawing algorithm in its place for you. It's not that hard, but I guess you're at quite an early stage.

So what are you working on that requires polygon filling?

#162850 - hacker013 - Sun Sep 14, 2008 9:07 am

@keldon, i'm working on a gmwrapper (for the functions) for my libhax library. And i have already a line drawing function with the DDA algorithm.
_________________
Website / Blog

Let the nds be with you.

#162861 - keldon - Sun Sep 14, 2008 8:09 pm

100% untested - but compiles and in theory should do the job you require. And I've "written" (or should I say edited) the draw_line method as a template method to allow any class with the required methods to use the algorithm without run time speed penalties.

You might also want to throw in some asserts to make sure that the image is not greater than MAX_IMAGE_HEIGHT, and make sure that it is not possible to write to invalid addresses in your image.

You will have to fill in your POINT and IMAGE structures; if you are only writing to the screen then create an IMAGE class that relays the set_pixel message to your own set_pixel method.

p.s. I normally would dedicate more time to helping others, but I got back quite late from church service and I have some work to do.

Code:
#define MIN( a, b )                        ( (a)<(b)?(a):(b) )
#define MAX( a, b )                        ( (a)>(b)?(a):(b) )
#define ABS( a )                        ( (a)<0?-(a):(a) )
#define FABS( a )                        ( (a)<0.0?-(a):(a) )
#define LIMIT( v, min, max )               ( (v)>(max)?(max):((v)<(min)?(min):v) )
#define IN_RANGE( val, MIN, MAX )             ( ( MIN <= val && MAX >= val ) ? 1 : 0 )
#define OUT_OF_RANGE( val, MIN, MAX )          ( ( MIN <= val && MAX >= val ) ? 0 : ( ( MIN > val ) ? -1 : 1 ) )

class IMAGE;

typedef u16 COLOUR;

/*!   \class   IMAGE
   \brief   This is the interface used in drawPoly.
*/
class IMAGE
{
   public:
      void set_pixel (int x, int y, COLOUR colour);
      u32 get_width (void) const;
      u32 get_height (void) const;
};

class SCANLINE_BOUNDARY
{
   public:
   
      SCANLINE_BOUNDARY (u32 *minx, u32 *maxx, u32 height)
         : minx_ (minx), maxx_ (maxx), height_ (height) {}
      
      void set_pixel (int x, int y, COLOUR)
      {
         if (minx_ && maxx_ && (y >= 0) && (y < height_))
         {
            minx_[y] = MIN (minx_[y], x);
            maxx_[y] = MAX (maxx_[y], x);
         }
      }
      
   private:
      u32 height_;
      u32 *minx_;
      u32 *maxx_;
};

class POINT
{
   public:
      int get_x()   {return 1;}
      int get_y()   { return 1;}
};

void drawPoly (IMAGE &img, const POINT &p1, const POINT &p2, const POINT &p3, COLOUR colour);

template <class T>
void draw_line(T &image, POINT p1, POINT p2, COLOUR color);

/*!
   Draws a texture mapped polygon to img.
   
   \param   img         Destination image
   \param   points      KPoint*[npoints]
   \param   npoints      Number of points in 'points' parameter.
*/
void drawPoly (IMAGE &img, const POINT &p1, const POINT &p2, const POINT &p3, COLOUR colour)
{
   // minx and maxx could be changed to dynamically allocated arrays, but
   // I opted for stack allocated arrays.
   
   const int MAX_IMAGE_HEIGHT = 512;
   u32 minx [MAX_IMAGE_HEIGHT];
   u32 maxx [MAX_IMAGE_HEIGHT];
   
   u32 miny = img.get_height();
   u32 maxy = 0;
   
   //   >
   //      Initialize min/max in such a way that nothing will be drawn for each line
   //   >
   for ( int i = 0; i < img.get_height(); i ++ )
   {
      minx[i] = img.get_width();
      maxx[i] = 0;
   }
   
   SCANLINE_BOUNDARY boundary (minx, maxx, img.get_height());
   draw_line(boundary, p1, p2, colour);
   draw_line (boundary, p2, p3, colour);
   draw_line (boundary, p3, p1, colour);

   //   >
   //      Render shape using min/max values created when drawing the lines.
   //   >
   for ( u32 y = miny; y <= maxy; y ++ )
   {
      for ( u32 x = minx[y]; x <= maxx[y]; x++ )
      {
         img.set_pixel((int)x,(int)y, colour);
         
      }
   }
}


template <class T>
void draw_line(T &image, POINT p1, POINT p2, COLOUR color) {
   int x = p1.get_x();
   int y = p1.get_y();
   
   int x2 = p2.get_x();
   int y2 = p2.get_y();

    int w = x2 - x ;
    int h = y2 - y ;
    int dx1 = 0, dy1 = 0, dx2 = 0, dy2 = 0 ;
    if (w<0) dx1 = -1 ; else if (w>0) dx1 = 1 ;
    if (h<0) dy1 = -1 ; else if (h>0) dy1 = 1 ;
    if (w<0) dx2 = -1 ; else if (w>0) dx2 = 1 ;
    int longest = ABS(w) ;
    int shortest = ABS(h) ;
    if (!(longest>shortest)) {
        longest = ABS(h) ;
        shortest = ABS(w) ;
        if (h<0) dy2 = -1 ; else if (h>0) dy2 = 1 ;
        dx2 = 0 ;           
    }
    int numerator = longest >> 1 ;
    for (int i=0;i<=longest;i++) {
        image.set_pixel(x,y,color) ;
        numerator += shortest ;
        if (!(numerator<longest)) {
            numerator -= longest ;
            x += dx1 ;
            y += dy1 ;
        } else {
            x += dx2 ;
            y += dy2 ;
        }
    }
}


Hope that helps!