DISPLAYING SPRITES

Sprites, sprites, and more sprites. They are simply everywhere. Imagine you
want to draw a nice fancy graphic onto your screen. How are you going to do
it? 1. Use mathematical formulae (or otherwise), and plot the pixels directly
onto screen. Yuck. This is way too low-level. 2. Draw a graphic in a software
package beforehand, and simply use that information and blit to screen.
Obviously the second method is far more attractive.
In this document, my aim to how to display sprites on screen.

Any suggestions or criticisms on this document welcome.

James Chow aka jc             9 June 1998
james@chowfam.demon.co.uk
---------------------------------------------------------------


Creating A Sprite
-----------------
Before we a sprite is created, we must understand how the video
memory works in (slightly) more detail.

   (0,0)  -----------------
          |   |   |   |   |
          |   |   |   |   |
 (x,256)  -----------------
          |   |   |   |   |
          |   |   |   |   |
          ----------------- (1023,511)
      (0,y) (64,y)..(960,y)

The video memory is divided into a number of "texture pages".
Their boundaries lie in multiples of 64 in the x direction and
multiples of 256, in the y direction.
Each pixel in the frame buffer also has a width of 16 bits.

If we were to draw all our sprites with a colour depth of 16 bits,
you'll soon find that it runs out quite fast. To compensate for this,
the facility is provided so that we can use 4 bit or 8 bit Colour
Look-Up Tables (CLUT). Each point in the sprite does not hold the
actual colour but an index to the CLUT.
What this means is that in one 16 bit value in the memory we can
store 4 pixels (if using a 4 bit CLUT), or 2 pixels (if using a
8 bit CLUT), or a single pixel if using direct colour. Although, in
a single sprite we cannot mix these. This gives a slight restriction
when we are drawing our sprites. If we decide to use a 4 bit CLUT,
then the sprite width must be a multiple of 4, and if 8 bit CLUT,
then multiple of 2.
If a pixel is partially occupied, it is considered to be completely
used by the libraries, and so when the sprite on screen, it may not
match exactly how it was origrinally drawn, or there may be black
borders along the edges.

Where to Place the Sprite
-------------------------
Using TIMUtil, we can allocate a position in video memory where
the sprite should be placed. Although, we can ignore this
information during coding, it's more flexible to do it this way.
In addition to the position of the sprite, if we're using a
4 bit CLUT or 8 bit CLUT, we need to allocate a position for
that as well.
We need to make sure that the positions of the sprite and CLUT does
not conflict with the display buffers, or with any other sprites
and CLUTs. If this happens, you'll probably get nothing on screen,
even though your code runs perfectly.

As well as fixing the position in video memory, where in the
sprite should be placed, we must also fix the position in
normal memory where the TIM file, now containing the sprite,
should be downloaded to - somewhere in between 0x80090000
and 0x801fffff. Outside these boundaries, and nothing will work.
The reason for this, is that we cannot directly download our
TIM file into video memory and so must be transferred from main
memory into video memory during runtime.

To do this, we do,

  #define TIMLOC   (0x800f8000)   //for example

  //somewhere in our code
  GsIMAGE img;
  RECT rect;
  GsGetTimInfo((u_long*)(TIMLOC+4),&img);
  rect.x = img.px; rect.y = img.py; rect.w = img.pw; rect.h = img.ph;
  LoadImage(&rect,img.pixel);
  if ((img.pmode>>3)&0x01) {
    rect.x = img.cx; rect.y = img.cy; rect.w = img.cw; rect.h = img.ch;
    LoadImage(&rect,img.clut);
  }

GsIMAGE is structure, which holds the details of our TIM
file - the location of the pixel data, CLUT data, and whether
there is a CLUT. If there is a CLUT then, the pmode field will
hold 0x08 (4 bit) or 0x09 (8 bit).
RECT is a convenience structure (albeit an important one), so that
the system knows where to load the info to.

Setting GsSPRITE and Displaying
-------------------------------
After the sprite and CLUT has been loaded into video memory
we need to set the fields of a variable of GsSPRITE struct
accordingly.
attribute fields indicate which properties are valid for the
           display of this sprite, and whether semi-transparencies
           apply - similar to those for lines and filled boxes.
x,y fields indicate where to display the sprite on the display
           buffer.
w,h fields indicate the width and height of the *rendered* sprite.
           If we're using a 4 bit CLUT, since 4 pixels occupy 1
           pixel space in the video memory, this will 4 times
           the *image* width. And for an 8 bit CLUT, this will
           be twice the *image* width.
tpage indicates which texture page in memory the beginning of the
           sprite is located. We use GetTPage(), to find this out.
u,v fields indicate the offsets in a texture page where the sprite
           is located, since a texture page can hold many sprites.
           Using these fields and w,h fields we can select a
           small portion of a larger sprite to display on screen.
cx,cy for the position of the CLUT, if any.
r,g,b indicate the brightness of the image. For a red tint, we
           have a high r value and low g,b values. For white
           light we simply have equal values of each.
mx,my indicate the position in the sprite to scale and rotate
           around. This becomes the origin of the sprite and
           which will transformed so that the origin is located
           at the x,y positions.
scalex,scaley are scaling factors. 4096 produces a scaling factor
           of 1. In other words - original size.
rotate indicates amount to rotate. 4096 for every 1 degree.

We can sort our GsSPRITE into the ordering tables. Using either,
  GsSortSprite(&sprite,&ot,0)  or
  GsSortFastSprite(&sprite,&ot,0)

Using GsSortFastSprite ignores translation (mx,my), scaling (scalex,
scaley) and rotation (rotate) fields.

One final thing - if the sprite in video memory is larger than the
size of a texture page, then only GsSortFastSprite can be used.
If GsSortSprite is used, it will fail to be displayed correctly.
Even if the sprite spans between texture pages it should work.
When you want to translate/scale/rotate a large sprite, then you
will need to work in texture page sizes.

Code
----
I've decided not to include a demo TIM file. But the creation
is simple. Load it in at address 0x80094000.
To save space, I'm reusing the pad reading routines from
"Reading the Controllers" and the main function from "Understanding
Drawing Primitives".

/**********/
/* main.c */
/**********/

#include 
#include "pad.h"

#define OTLEN   (1)
#define SCNW    (320)
#define SCNH    (240)

#define TIMLOC  (0x80094000)

GsSPRITE sprite;

void initGame(void) {
  GsIMAGE img;
  RECT rect;
  FntLoad(960,256);
  FntOpen(0,0,SCNW,SCNH,0,512);
  GsGetTimInfo((u_long*)(TIMLOC+4),&img);
  rect.x = img.px; rect.y = img.py; rect.w = img.pw; rect.h = img.ph;
  LoadImage(&rect,img.pixel);
  if ((img.pmode>>3)&0x01) {
    rect.x = img.cx; rect.y = img.cy; rect.w = img.cw; rect.h = img.ch;
    LoadImage(&rect,img.clut);
  }
  switch (img.pmode) {
    case 0x08:     //4 bit with CLUT
      sprite.attribute = 0x00000000;
      sprite.w = img.pw*4;
      sprite.tpage = GetTPage(0,0,img.px,img.py);
      break;
    case 0x09:     //8 bit with CLUT
      sprite.attribute = 0x01000000;
      sprite.w = img.pw*2;
      sprite.tpage = GetTPage(1,0,img.px,img.py);
      break;
    default:       //16 bit
      sprite.attribute = 0x02000000;
      sprite.w = img.pw;
      sprite.tpage = GetTPage(2,0,img.px,img.py);
      break;
  }
  sprite.x = 50; sprite.y = 50;
  sprite.h = img.ph;
  sprite.u = 0; sprite.v = 0;
  sprite.cx = img.cx; sprite.cy = img.cy;
  sprite.r = 128; sprite.g = 128; sprite.b = 128;
  sprite.mx = sprite.w/2; sprite.my = sprite.h/2;
  sprite.scalex = 4096; sprite.scaley = 4096;
  sprite.rotate = 0;
}

int updateGame(void) {
  static int padstate;
  if (padConnected(0) == PADin)
    if ((padType(0) == PADstandard)||
        (padType(0) == PADanalogue)||
        (padType(0) == PADdualshck))
      if ((padState(0) & PADstart)&&
          (padState(0) & PADselect))
        return 1;
  if (padState(0) & PADLup) sprite.y--;
  if (padState(0) & PADLdown) sprite.y++;
  if (padState(0) & PADLleft) sprite.x--;
  if (padState(0) & PADLright) sprite.x++;
  if (padState(0) & PADRleft) sprite.r = (sprite.r+1)%256;
  if (padState(0) & PADRdown) sprite.g = (sprite.g+1)%256;
  if (padState(0) & PADRright) sprite.b = (sprite.b+1)%256;
  if (padState(0) & PADL1)
    sprite.rotate = (sprite.rotate+359*4096)%(360*4096);
  if (padState(0) & PADR1)
    sprite.rotate = (sprite.rotate+4096)%(360*4096);
  if (padState(0) & PADL2)
    if (padState(0) & PADselect) sprite.scalex -= 256;
    else                         sprite.scalex += 256;
  if (padState(0) & PADR2)
    if (padState(0) & PADselect) sprite.scaley -= 256;
    else                         sprite.scaley += 256;
  padstate = padState(0);
  return 0;
}

void drawGame(GsOT *ot) {
  FntPrint("X:%d  Y:%d\n",sprite.x,sprite.y);
  FntPrint("SCALEX:%d  SCALEY:%d\n",sprite.scalex,sprite.scaley);
  FntPrint("ROTATE:%d (%d DEGS)\n",sprite.rotate,sprite.rotate/4096);
  FntPrint("R:%d G:%d B:%d\n",sprite.r,sprite.g,sprite.b);
  FntFlush(-1);
  GsSortSprite(&sprite,ot,0);
}

void main(void);
  //same as from Understanding Drawing Primitives