Go to notes and algorithms index. ~ Go to home page.

draw_small_vectors()


Draw an array of short vectors (i.e. lines) to a bit map and if necessary rotate them.

This routine would be useful for slow basic bit mapped hardware where you don't have any vector drawing commands or APIs available. Typical uses would be for doing vector style text, or vector style graphics (ala Asteroids etc). It will draw and rotate one whole object (e.g. a space ship) in one call, to do this you have to set-up the coordintes of your object in an array (typically when you initialize your prorgam but there is nothing to stop you making or modifying these coordinate arrays on the fly).

... or perhaps you want to draw lines, not of pixels but of sprites for example, and your graphic API may only do lines of pixels. It should be easy to adapt this source code to do things like that -- the main key to such an adaptaion would be rewriting the #define macros: HOR, VER, DIA and DOT.

You pass this routine an array of vectors for it to draw. I chose to write a routine to draw an array of vectors rather than just one at a time, not only for the convenience of being able to draw a whole spaceship, for example, with one call but so that the rotation code would only have to be done once... also even calling routine carries a small overhead (loading-up parameters to the stack etc).

The vectors can have a maximum dx and dy of 8 pixels (meaning they can be upto 11 pixels long if diagonal) this is because my routine uses pre-canned sequences to draw each vector to be as fast as possible .. a different pre-canned sequence exists for every possible dx and dy vector dimension combination... so to allow for longer vectors the permutations that have to be precanned would start to get unweildy and make the program much larger. In theory you could adapt this routine to draw longer vectors or if the compiled code of this routine is too large for your system you could reduce the maximum vector dimension (5x5 is a handy one because if you sketch something on traditional graph paper there are thicker divisions every 5 divisions making it easy to read-off the values). To do this you'll need to down load and modify the Quick BASIC program I wrote that generated much of the routine's source code automatically. Click here to download the source code generator program.

As metioned earlier this routine also rotates the vectors to the specified angle, but that's not all... if you want to know where a point on your object will be after rotation, it can return that to you. For example if your object is a gun and you want to know where to initially draw the bullet comming out of the barrell (considering that it could be tited up or down) you can specify a point at the end of the barrell in your array of vectors as a point (it need not be a visible drawn point) and the routine will rotate it in exactly the same way as it does the vectors it draws, and then output the point in an small array for your later use.

I wrote it to draw to an 8bit bit-mapped image with a 256 colour palette, but it could easily be adapted for other formats.

To compile this routine you will also need my sine_12() routine or substitute your own or the standard C ones... if you use floating point sin and cos functions you'll have divide the angle you pass to them by an amount depending on whether they require degrees or radians (11.37778 for degrees), and multiply their results by 4096 before copying them to the integer variables sine and cosine.

The Parameters:-
*p_points --- the array of points. This must be terminated by adding one extra element with the x coordinte set to END_OF_SHORT_POINTS.
p_rot --- the angle you want the object rotated, from 0 to 4095, where 4095 is nearly 360 degrees. (you can specify angles bigger or negative angles and this routine will convert it internally to be within range provided you use my sine_12 routine, e.g. it will convert 4096 to 0).
p_bit_map_width --- the width of your bit map in bytes. (Note that with Windows this maybe more than the pixel width since it will be to the nearest 4 bytes).
**p_pixel_ptr --- the address of the pixel that is the origin of your object to draw.
This is updated so that after this routine has executed it will be at the address of the next pixel that would be drawn. Hence the ** so that the pointer parameter can be modified.
*p_min_ptr --- This is for bounds checking. You specify this address as the lowest address in the region of the bit map you're drawing to. This routine will not plot points to memory below this address -- hence preventing crashes due to trying to write to illegal memory locations.
*p_max_ptr --- Similar to p_min_ptr but it is the upper address limit.
p_pixel_colour --- The colour number from 0 to 256 (relating to the bitmap's colour palette).
p_avoid_colour --- A colour number that this routine will not draw over.
*p_output_points --- The address of an array to output any requested points to. If you don't need any points output then you can specify this as 0.

To make this routine faster (and use less memory) you can rewrite the macros HOR, DIA, VER and DOT to reduce the number of checks they do -- but obviously if, for example, you remove the check that prevents you writing on memory outside your bitmap you'll have to make sure in your program that you don't draw off the bottom of the screen. Here are the macros in their bare minimum form:-

   // Bare minimum macros (these make the parameters *p_min_ptr, *p_max_ptr and p_avoid_colour redundant.
   #define HOR **p_pixel_ptr = p_pixel_colour; *p_pixel_ptr += hor_inc;
   //   ... plot one pixel then move one horizontally left or right.
   #define VER **p_pixel_ptr = p_pixel_colour; *p_pixel_ptr += ver_inc;
   //   ... plot one pixel then move vertically up or down.
   #define DIA **p_pixel_ptr = p_pixel_colour; *p_pixel_ptr += ver_inc + hor_inc;
   //   ... plot one pixel then move diagonally.
   #define DOT **p_pixel_ptr = p_pixel_colour;
   //   ... just plot a pixel without moving afterwards

You can do much more by rewriting the macros -- e.g. to draw a double thickness line... or you could modify the DIA macro to smooth the jagged diagonals a bit by also drawing an intemediate colour.

This routine uses err_dis() -- just an error message display routine of mine... you might want to replace it with a printf to show an error message insead. Do "#define DIAGS 1" at the top of your source if you want to detect those errors -- especially useful when you first try using the routine, then when your program works OK then you can change DIAGS to 0 so that your program runs a little faster.

EXAMPLE! At the bottom of this page is an example of this routine's use.



void draw_small_vectors( short_point *p_points, // The array of vectors and points.
                         short p_rot, // Rotation 0 to 4095 (clockwise)
                         short p_bit_map_width, // Bitmap width in bytes.
                         unsigned char **p_pixel_ptr, // Current location in bitmap
                         unsigned char *p_min_ptr, // Lowest address in your bitmap
                         unsigned char *p_max_ptr, // Highest address in your bitmap
                         unsigned char p_pixel_colour,  // Colour num to draw the lines in.
                         unsigned char p_avoid_colour,  // The colour num not to draw over.
                         short_point *p_output_points  // Array for output points (specify as zero if not required).
                       )
   {
   // Jon P, 2006.

   // Define some macros that draw a pixel at a time to save a fair old bit of typing...
   #define HOR if ( *p_pixel_ptr < p_min_ptr || *p_pixel_ptr > p_max_ptr ) break; if ( **p_pixel_ptr != p_avoid_colour ) **p_pixel_ptr = p_pixel_colour; *p_pixel_ptr += hor_inc;
   //   ... plot one pixel then move one horizontally left or right.
   #define VER if ( *p_pixel_ptr < p_min_ptr || *p_pixel_ptr > p_max_ptr ) break; if ( **p_pixel_ptr != p_avoid_colour ) **p_pixel_ptr = p_pixel_colour; *p_pixel_ptr += ver_inc;
   //   ... plot one pixel then move vertically up or down.
   #define DIA if ( *p_pixel_ptr < p_min_ptr || *p_pixel_ptr > p_max_ptr ) break; if ( **p_pixel_ptr != p_avoid_colour ) **p_pixel_ptr = p_pixel_colour; *p_pixel_ptr += ver_inc + hor_inc;
   //   ... plot one pixel then move diagonally.
   #define DOT if ( *p_pixel_ptr < p_min_ptr || *p_pixel_ptr > p_max_ptr ) break; if ( **p_pixel_ptr != p_avoid_colour ) **p_pixel_ptr = p_pixel_colour;
   //   ... just plot a pixel without moving afterwards,


   long x, y, sine, cosine,
        prev_x = 0, prev_y = 0,
        dx, dy, hor_inc, ver_inc, temp = 0;
   short_point *point_ptr;

   if ( p_rot )
      {
      p_rot = 0 - p_rot;
      sine_12( p_rot, &sine, &cosine );
      }

   point_ptr = p_points;
   if ( point_ptr->x == END_OF_SHORT_POINTS ) return;

   #if DIAGS
      if ( point_ptr->bits & sL )
         {
         err( 7201 );  // ERROR the first coordinate must be a point ( sP )
         return;
         }
   #endif

   while ( point_ptr->x != END_OF_SHORT_POINTS )
      {      

      if ( p_rot )
         {
         // Rotate if required...
         x = ( 0 - (long) point_ptr->y ) * sine;
         x += ( (long) point_ptr->x ) * cosine;
         if ( x > 0 )
            x += 2048;
         else
            x -= 2048;
         x /= 4096;   // (you can try replacing this line with x >>= 12; for better perfomance)
   
         y = ( (long) point_ptr->y ) * cosine;
         y += ( (long) point_ptr->x ) * sine;
         if ( y > 0 )
            y += 2048;
         else
            y -= 2048;
         y /= 4096;   // (you can try replacing this line with y >>= 12; for better perfomance)
         }
      else
         {
         x = (long) point_ptr->x;
         y = (long) point_ptr->y;
         }
      if ( point_ptr->bits & sANY_OUTS && p_output_points )
         { // an ouput point was requested...
         if ( p_output_points[(point_ptr->bits & sANY_OUTS) - 1].bits == sOUTPUT_POINTS )
            {
            p_output_points[(point_ptr->bits & sANY_OUTS) - 1].x = (short) x;
            p_output_points[(point_ptr->bits & sANY_OUTS) - 1].y = (short) y;
            }
         else
            {
            #if DIAGS
               err( 7202 );  // an ouput point was requested but the element of
                             // the output array was not set-up correctly or the
                             // array size insufficient.
            #endif
            }
         }
      if ( x >= prev_x )
         {
         dx = x - prev_x;
         hor_inc = 1;
         }
      else
         {
         dx = prev_x - x;
         hor_inc = -1;
         }
      if ( y >= prev_y )
         {
         dy = y - prev_y;
         ver_inc = p_bit_map_width;
         }
      else
         {
         dy = prev_y - y;
         ver_inc = 0 - p_bit_map_width;
         }

      if ( point_ptr->bits & sL )  // this is a line to be drawn from the last point
         {

         switch( dy )
            {
            case 0:    //  0 pixel high vector.
               switch( dx )
                  {  // Now select on width of vector...
                  case 0:  DOT  break;
                  case 1:  HOR DOT  break;
                  case 2:  HOR HOR DOT  break;
                  case 3:  HOR HOR HOR DOT  break;
                  case 4:  HOR HOR HOR HOR DOT  break;
                  case 5:  HOR HOR HOR HOR HOR DOT  break;
                  case 6:  HOR HOR HOR HOR HOR HOR DOT  break;
                  case 7:  HOR HOR HOR HOR HOR HOR HOR DOT  break;
                  case 8:  HOR HOR HOR HOR HOR HOR HOR HOR DOT  break;
                  #if DIAGS
                   default:  err( 7022 );
                  #endif
                  }
               break;
               
            case 1:    //  1 pixel high vector.
               switch( dx )
                  {  // Now select on width of vector...
                  case 0:  VER DOT  break;
                  case 1:  DIA DOT  break;
                  case 2:  HOR DIA DOT  break;
                  case 3:  HOR DIA HOR DOT  break;
                  case 4:  HOR HOR DIA HOR DOT  break;
                  case 5:  HOR HOR DIA HOR HOR DOT  break;
                  case 6:  HOR HOR HOR DIA HOR HOR DOT  break;
                  case 7:  HOR HOR HOR DIA HOR HOR HOR DOT  break;
                  case 8:  HOR HOR HOR HOR DIA HOR HOR HOR DOT  break;
                  #if DIAGS
                   default:  err( 7023 );
                  #endif
                  }
               break;
               
            case 2:    //  2 pixel high vector.
               switch( dx )
                  {  // Now select on width of vector...
                  case 0:  VER VER DOT  break;
                  case 1:  VER DIA DOT  break;
                  case 2:  DIA DIA DOT  break;
                  case 3:  DIA HOR DIA DOT  break;
                  case 4:  HOR DIA DIA HOR DOT  break;
                  case 5:  HOR DIA HOR DIA HOR DOT  break;
                  case 6:  HOR DIA HOR HOR DIA HOR DOT  break;
                  case 7:  HOR DIA HOR HOR HOR DIA HOR DOT  break;
                  case 8:  HOR HOR DIA HOR HOR DIA HOR HOR DOT  break;
                  #if DIAGS
                   default:  err( 7024 );
                  #endif
                  }
               break;
               
            case 3:    //  3 pixel high vector.
               switch( dx )
                  {  // Now select on width of vector...
                  case 0:  VER VER VER DOT  break;
                  case 1:  VER DIA VER DOT  break;
                  case 2:  DIA VER DIA DOT  break;
                  case 3:  DIA DIA DIA DOT  break;
                  case 4:  DIA DIA HOR DIA DOT  break;
                  case 5:  DIA HOR DIA HOR DIA DOT  break;
                  case 6:  HOR DIA DIA HOR HOR DIA DOT  break;
                  case 7:  HOR DIA HOR DIA HOR DIA HOR DOT  break;
                  case 8:  HOR DIA HOR DIA HOR HOR DIA HOR DOT  break;
                  #if DIAGS
                   default:  err( 7025 );
                  #endif
                  }
               break;
               
            case 4:    //  4 pixel high vector.
               switch( dx )
                  {  // Now select on width of vector...
                  case 0:  VER VER VER VER DOT  break;
                  case 1:  VER VER DIA VER DOT  break;
                  case 2:  VER DIA DIA VER DOT  break;
                  case 3:  DIA DIA VER DIA DOT  break;
                  case 4:  DIA DIA DIA DIA DOT  break;
                  case 5:  DIA DIA HOR DIA DIA DOT  break;
                  case 6:  DIA HOR DIA DIA HOR DIA DOT  break;
                  case 7:  DIA HOR DIA HOR DIA HOR DIA DOT  break;
                  case 8:  HOR DIA DIA HOR HOR DIA DIA HOR DOT  break;
                  #if DIAGS
                   default:  err( 7026 );
                  #endif
                  }
               break;
               
            case 5:    //  5 pixel high vector.
               switch( dx )
                  {  // Now select on width of vector...
                  case 0:  VER VER VER VER VER DOT  break;
                  case 1:  VER VER DIA VER VER DOT  break;
                  case 2:  VER DIA VER DIA VER DOT  break;
                  case 3:  DIA VER DIA VER DIA DOT  break;
                  case 4:  DIA DIA VER DIA DIA DOT  break;
                  case 5:  DIA DIA DIA DIA DIA DOT  break;
                  case 6:  DIA DIA HOR DIA DIA DIA DOT  break;
                  case 7:  DIA HOR DIA DIA DIA HOR DIA DOT  break;
                  case 8:  DIA HOR DIA HOR DIA DIA HOR DIA DOT  break;
                  #if DIAGS
                   default:  err( 7027 );
                  #endif
                  }
               break;
               
            case 6:    //  6 pixel high vector.
               switch( dx )
                  {  // Now select on width of vector...
                  case 0:  VER VER VER VER VER VER DOT  break;
                  case 1:  VER VER VER DIA VER VER DOT  break;
                  case 2:  VER DIA VER VER DIA VER DOT  break;
                  case 3:  VER DIA DIA VER VER DIA DOT  break;
                  case 4:  DIA VER DIA DIA VER DIA DOT  break;
                  case 5:  DIA DIA VER DIA DIA DIA DOT  break;
                  case 6:  DIA DIA DIA DIA DIA DIA DOT  break;
                  case 7:  DIA DIA DIA HOR DIA DIA DIA DOT  break;
                  case 8:  DIA DIA HOR DIA DIA HOR DIA DIA DOT  break;
                  #if DIAGS
                   default:  err( 7028 );
                  #endif
                  }
               break;
               
            case 7:    //  7 pixel high vector.
               switch( dx )
                  {  // Now select on width of vector...
                  case 0:  VER VER VER VER VER VER VER DOT  break;
                  case 1:  VER VER VER DIA VER VER VER DOT  break;
                  case 2:  VER DIA VER VER VER DIA VER DOT  break;
                  case 3:  VER DIA VER DIA VER DIA VER DOT  break;
                  case 4:  DIA VER DIA VER DIA VER DIA DOT  break;
                  case 5:  DIA VER DIA DIA DIA VER DIA DOT  break;
                  case 6:  DIA DIA DIA VER DIA DIA DIA DOT  break;
                  case 7:  DIA DIA DIA DIA DIA DIA DIA DOT  break;
                  case 8:  DIA DIA DIA DIA HOR DIA DIA DIA DOT  break;
                  #if DIAGS
                   default:  err( 7029 );
                  #endif
                  }
               break;
               
            case 8:    //  8 pixel high vector.
               switch( dx )
                  {  // Now select on width of vector...
                  case 0:  VER VER VER VER VER VER VER VER DOT  break;
                  case 1:  VER VER VER VER DIA VER VER VER DOT  break;
                  case 2:  VER VER DIA VER VER DIA VER VER DOT  break;
                  case 3:  VER DIA VER DIA VER VER DIA VER DOT  break;
                  case 4:  VER DIA DIA VER VER DIA DIA VER DOT  break;
                  case 5:  DIA VER DIA VER DIA DIA VER DIA DOT  break;
                  case 6:  DIA DIA VER DIA DIA VER DIA DIA DOT  break;
                  case 7:  DIA DIA DIA DIA VER DIA DIA DIA DOT  break;
                  case 8:  DIA DIA DIA DIA DIA DIA DIA DIA DOT  break;
                  }
               break;
            #if DIAGS
             default:  err( 7030 );
            #endif
            }

         }
      else // no line required just a point
         {
         *p_pixel_ptr += ver_inc * dy + hor_inc * dx;
         }
      prev_x = x;
      prev_y = y;
      point_ptr++;
      }   

   }




Below are the definitions etc needed for the draw_small_vectors routine.


#define sP 0
// ... a point.
#define sOUT0 1
// ... request that this point's transformation be output to output point array 0.
#define sOUT1 2
// ... request that this point's transformation be output to output point array 1.
#define sOUT2 3
// ... request that this point's transformation be output to output point array 2.
#define sOUT3 4
// ... request that this point's transformation be output to output point array 3.
#define sOUT4 5
// ... request that this point's transformation be output to output point array 4.
#define sOUT5 6
// ... request that this point's transformation be output to output point array 5.
#define sOUT6 7
// ... request that this point's transformation be output to output point array 6.
#define sANY_OUTS 7
#define sL 16
// ... a short line with its origin as the previous point.
#define sOUTPUT_POINTS 32
// ... set this bit on each element of the ouput array.
#define END_OF_SHORT_POINTS 4096

typedef struct
   {
   short x;
   short y;
   unsigned short bits;
   } short_point;




Below is some example data that draws a cannon on a trolly that I used in an early version of Antiverse folowed by the routine that called the routine.

p_win_ptr is just a pointer to a structure where I hold some details about the window I'm drawing to, it's not part of the operating system. You'll note that I have used the output points feature -- I first draw the trolly then I draw the wheels and cannon using points output from when drawing the trolly to locate them.


   short_point cannon[] =
      {
      -10,0,sP,  -8,3,sL,  -5,4,sL,  0,5,sL,  5,4,sL,  10,3,sL,  15,3,sL,  20,2,sL,  // top
      20,-2,sL,  //vertical -- front of cannon barrel
      15,-3,sL,  10,-3,sL,  5,-5,sL,  0,-5,sL,  -5,-4,sL,  -8,-3,sL,  -10,0,sL,   // bottom
      END_OF_SHORT_POINTS,END_OF_SHORT_POINTS
      };

   short_point trolly[] =
      {
      -15,8,sP, //left hand vetical
      -15,12,sL,  -10,12,sL,  -5,12,sL,  0,12,sL,
         5,12,sL,  10,12,sL,  15,12,sL,//top
      15,8,sL,  // right-hand vertical
      -4,5,sP, 4,5,sL, // bottom horizontal
      -5,12,sP,  -4,17,sL,  -2,22,sL,
      2,22,sL,  4,17,sL,  5,12,sL,
      0,20,sP|sOUT0,  -10,6,sP|sOUT1,  10,6,sP|sOUT2,
      END_OF_SHORT_POINTS,END_OF_SHORT_POINTS
      };

   short_point wheel[] =
      {
      0,6,sP,   4,4,sL,
      6,0,sL,   4,-4,sL,
      0,-6,sL,  -4,-4,sL,
      -6,0,sL,  -4,4,sL,
      0,6,sL,
      0,0,sL,
      END_OF_SHORT_POINTS,END_OF_SHORT_POINTS
      };


void gun_class::draw_gun( window_details_struct *p_win_ptr,
                          long p_command_bits
                        )
   {
   unsigned char *origin_pixel_ptr, *pixel_ptr, colour_num;
   long i;

   #define CANNON_PIVOT_IDX 0
   #define WHEEL1_PIVOT_IDX 1
   #define WHEEL2_PIVOT_IDX 2
   #define NUM_OUTPUT_POINTS 3
   short_point output_points[NUM_OUTPUT_POINTS];

   if ( p_command_bits & DRAW_IT )
      colour_num = test_ol_colour;
   else
      colour_num = bg_colour;

   for( i = 0; i < NUM_OUTPUT_POINTS; i++ )
      {
      output_points[i].x = output_points[i].y = 0;
      output_points[i].bits = sOUTPUT_POINTS;
      }


   // draw the cannon

   origin_pixel_ptr = p_win_ptr->first_pixel_ptr;
   origin_pixel_ptr += ( y_8 >> 8 ) * ((short) p_win_ptr->bitmap_width);
   origin_pixel_ptr += ( x_8 >> 8 );

   pixel_ptr = origin_pixel_ptr;
   draw_small_vectors( trolly,
                       rot,
                       (short) p_win_ptr->bitmap_width,
                       &pixel_ptr,
                       p_win_ptr->first_pixel_ptr,
                       p_win_ptr->last_pixel_ptr,
                       colour_num,
                       ol_colour,
                       output_points
                       );

   pixel_ptr = origin_pixel_ptr + ( output_points[CANNON_PIVOT_IDX].y * ((short) p_win_ptr->bitmap_width) );
   pixel_ptr += output_points[CANNON_PIVOT_IDX].x;
   draw_small_vectors( cannon,
                       cannon_rot,
                       (short) p_win_ptr->bitmap_width,
                       &pixel_ptr,
                       p_win_ptr->first_pixel_ptr,
                       p_win_ptr->last_pixel_ptr,
                       colour_num,
                       ol_colour,
                       0
                       );

   pixel_ptr = origin_pixel_ptr + ( output_points[WHEEL1_PIVOT_IDX].y * ((short) p_win_ptr->bitmap_width) );
   pixel_ptr += output_points[WHEEL1_PIVOT_IDX].x;
   draw_small_vectors( wheel,
                       wheel1_rot,
                       (short) p_win_ptr->bitmap_width,
                       &pixel_ptr,
                       p_win_ptr->first_pixel_ptr,
                       p_win_ptr->last_pixel_ptr,
                       colour_num,
                       ol_colour,
                       0
                       );

   pixel_ptr = origin_pixel_ptr + ( output_points[WHEEL2_PIVOT_IDX].y * ((short) p_win_ptr->bitmap_width) );
   pixel_ptr += output_points[WHEEL2_PIVOT_IDX].x;
   draw_small_vectors( wheel,
                       wheel2_rot,
                       (short) p_win_ptr->bitmap_width,
                       &pixel_ptr,
                       p_win_ptr->first_pixel_ptr,
                       p_win_ptr->last_pixel_ptr,
                       colour_num,
                       ol_colour,
                       0
                       );
   }