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++; } }
#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;
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 ); }