Spriteclipping

Volvox

Hi,

after reading Snukey's article about virtual screens in Hugi #12, I decided to write about spriteclipping.

At first let's talk about the meaning of clipping. The purpose of clipping is to suppress the display of invisible graphic data. If the to-be-drawn sprite passes the screen edge, this produces the rather ugly effect that all graphic data that should actually be invisible is displayed on the other side of the screen. We can solve this problem with a clipping algorithm. If you draw a sprite, four cases of clipping are possible (of course there can be combinations, too):

The sprite passes the

                       top    screen edge (mode 13h: y<0)
                       bottom screen edge (mode 13h: y+height>200)
                       left   screen edge (mode 13h: x<0)
                       right  screen edge (mode 13h: x+width>320)

Before drawing the sprite we have to know by how many pixel it passes the screen edge in all four possible directions. Let's assume that there is a sprite with a width of 30 and a height of 60 pixel.

                   xy    width=30
                     *************
                     *     *     * h
                     *    ***    * e
                     *   *****   * i
                     *  *******  * g
                     *     *     * h
                     *     *     * t =60
                     *************

The four variables in which we store the numbers of invisible pixels are:

                    CLIP_RIGHT
                    CLIP_LEFT
                    CLIP_TOP
                    CLIP_BOTTOM

So what happens if a part of a sprite is invisible?

The sprite passes the top screen edge (e.g. y = -30)

             ---------------------------------
                     *   *****   *
                     *  *******  *
                     *     *     *
                     *     *     *
                     *************

We know that we have to leave out the first 30 pixel lines of the sprite. We store this information in the variable CLIP_TOP. In this way not lines 0-59 are drawn, but only lines 30-59.

=> CLIP_TOP = ABS(y)

The sprite passes the bottom screen edge (e.g. y = 170)

                     *************
                     *     *     *
                     *    ***    *
                     *   *****   *
                     *  *******  *
             ---------------------------------

If the sum of the y-position and the height of the sprite is greater than the number of screen lines (i.e. in mode 13h: 200), the sprite passes the bottom screen edge. We can only display the first 200-y lines of the sprite. In our case we can only draw the lines 0-29 instead of 0-59 of the sprite.

If CLIP_BOTTOM is zero or a negative value, the sprite is invisible, and we must not draw it.

=> CLIP_BOTTOM = screenheight - y

The sprite passes the left screen edge (e.g. x = -15)

                     |
                     |
                     |*********
                     |  *     *
                     | ***    *
                     |*****   *
                     |******  *
                     |  *     *
                     |  *     *
                     |*********
                     |
                     |

Like with CLIP_TOP, we store the x-position in CLIP_LEFT and remove the sign. Now the lines of the sprite are not completely drawn, but only from pixel 15.

=> CLIP_LEFT = ABS(x)

The sprite passes the right screen edge (e.g. x = 305)

                              |
                     *********|
                     *     *  |
                     *    *** |
                     *   *****|
                     *  ******|
                     *     *  |
                     *     *  |
                     *********|
                              |
                              |

Again you check if the sum of the x-position and the width is greater than the screenwidth (in mode 13h: 320). In order to calculate CLIP_RIGHT, it's enough to substract the x-position from the screenwidth.

Also here the rule is: If CLIP_RIGHT is zero or a negative value, the sprite is invisible, and we must not display it.

=> CLIP_RIGHT = screenwidth - x

Okay. That's how you calculate the values of the clipping variables. Next comes a small, unoptimized code fragment in C for drawing a clipped sprite:

 unsigned char _far *VGA=(unsigned char _far*)0xA0000000;
 char sprite[60*30];

 int draw_sprite(signed int xpos,signed int ypos,signed int width,
                 signed int height,char sprite[])
  {
   signed int
   clip_left  =0,
   clip_right =0,
   clip_top   =0,
   clip_bottom=0,
   x,y;

   // sprite is invisible and must not be drawn
   if (xpos < -width || xpos > 320 || ypos < -height || ypos > 200)
   return (0);

   if          (xpos < 0) clip_left  =   xpos * -1;
   if ((clip_right =   320-xpos)>width) clip_right=width;
   if          (ypos < 0) clip_top   =   ypos * -1;
   if ((clip_bottom  =   200-ypos)>height)  clip_bottom=height;

   for (y=clip_top ; y < clip_bottom ; y++)
   for (x=clip_left; x < clip_right ; x++)
   *(VGA+(ypos+y)*320+x+xpos)=sprite[(y*width)+x];
   return (1);

  }

 void main (void)
  {
    int x,y;
    //switch to graphic mode
    asm mov ax,13h
    asm int 10h
    //the sprite is just a blue rectangle
    for (x=0;x< 60;x++)
    for (y=0;y< 30;y++)
    sprite[(y*30)+x]=1;
    //test four cases
    draw_sprite(160,-15,30,60,sprite);
    draw_sprite(160,185,30,60,sprite);
    draw_sprite(-15,100,30,60,sprite);
    draw_sprite(305,100,30,60,sprite);
    getch();
  }

I hope I managed to explain you how spriteclipping works and you can use it. An ASM-optimized sprite set routine and the usual crap like clearing a virtual screen etc. is attached in bonus.zip.

At last I want to say that I'd be pleased if you could optimize the sprite routine more and send them to me via e-mail. It isn't the best one, but certainly faster than a high level language routine with two for-loops. :)

Enjoy reading Hugi #14 and have a nice coding session.

- Volvox