PS2 Linux Programming
Sprite Movement with Angles
Introduction
This tutorial will show
how to draw and manipulate 2D sprites using the triangle strip primitive. Full
freedom of movement of the sprite in 2 dimensions will be introduced.
The GS supports a Triangle
Strip primitive, which is a series of continuous triangles sharing sides. The
first triangle is drawn with 3 pieces of vertex information (1, 2, and 3 in the
diagram below), and the succeeding ones are drawn whenever one further piece of
vertex information is added (vertex 4 draws the second triangle in the diagram below).
Full information on the format of a GS triangle strip is described on page 36
of the GS Users Manual.
A quad drawn with a
triangle strip, (which will be textured with a bitmap image), is illustrated in
the diagram below.
Four vertices are required
in order to define the quad using a triangle strip primitive. Each vertex is
independently defined and has position, depth, texture coordinates etc.
Suitable transformations can be used to translate, scale, and rotate the
sprite.
The structure used to hold
the sprite information is shown below and is similar to the one used in the
previous tutorial. The main additions are that the position on screen is now
held as floats rather than ints, there are variables to store half the width
and half the height of the sprite as floats, and there is a float to hold the
angle of rotation of the sprite in radians. The function of these variable will
become clear.
typedef struct
{
float x, y; // position on screen (center)
int z; // z depth (big = near)
float fHalfW, fHalfH; //
width/2 and height/2
int w, h; // width and height
int u, v; // offset from texture top left
unsigned char r, g, b, a; //
colour and alpha
int FCountMax; //
Counter max value
int FCount; // Frame Counter
int NFrames; // number of animation frames
float Rot; // Rotation angle in radians
} PS2Sprite_t;
Sprite coordinates are
best defined in “local space” with the origin being defined as the point at
which the sprite is to be rotated about. This makes rotating, mirroring etc.
much easier. As the name suggests, local space coordinates define the positions
of the vertices of the quad relative to an origin which is within the same
coordinate system as the quad itself. In this example, the local origin will be
defined at the centre of the quad, i.e. the point where the two diagonals of
the quad meet.
The simplest 2D transform is
translation. This is achieved by adding a translation amount for each axis on to
the coordinates of all of the vertices of the sprite. For example, to translate
a sprite 4 units right and 10 units down, 4 is added to the x component and 10
to the y component of all 4 vertices that make up the sprite.
Rotation is a little
harder. When rotation is applied, the vertices are rotated about the origin.
The method to be used will depend on how the vertex information is stored for
the sprite. If the absolute (or world) position of the vertices are stored, a
translation can be used to position the point of rotation of the sprite at the
origin, perform rotation, then translate the sprite back to where it was
originally positioned. If there is access to the untransformed sprite
coordinated, as in the example being created here, then rotation can simply be
applied to these local coordinates. The equation for rotating a point about the
origin is as follows (note that this is rotation about the Z axis in 3D space):
X’ = X * cos(A) – Y * sin(A)
Y’ = X * sin(A) + Y * cos(A)
A is the angle of
rotation, X’ and Y’ are the rotated coordinates and X, Y are the original
coordinates. Thus, in order to rotate the quad, the above equation is applied
to all four coordinated to obtain the new, or rotated coordinates.
Moving the sprite in the
direction it is pointing is now relatively straightforward. The angle of
rotation, A, is known so to move the sprite, the following amounts are added to
the positions of all the vertices of the quad:
dx = D * sin(A)
dy = D * cos(A)
The application of the
above movement equation can be seen in operation in the MoveSprite() function
within main.cpp.
Investigating the source
code it can be seen that the standard maths functions of sinf and cosf which
are defined in <math.h> are used. These functions on the PS2 are __SLOW__,
even although they are using floating point as opposed to doubles. They are not
just a little slow, they are slow enough to be useless when creating real
games. For the purposes of this tutorial these standard maths functions are
acceptable since they are not being used very often, but in future tutorials
hand crafted assembly language routines will be used which are considerably
faster.
Much of the initialisation
that is performed in main.cpp has been described in previous tutorials and will
not be repeated here. Before the render loop is entered, the sprite structures
are initialised with appropriate information. Within the render loop the sprite
animation sequence can be changed with the triangle and square buttons, the
sprite can be rotated with the left and right buttons and the circle and cross
buttons are used to move the sprite backwards and forwards. Notice that the
movement and rotation buttons are all pressure sensitive, so the harder the
buttons are pressed the greater the associated action.
The GS packets are
constructed using the BuildSprite2D() function which is contained in the file
packet.cpp. BuildSprite2D() takes two parameters, the first is a pointer to the
structure describing the sprite to be drawn and the second is a pointer to the
start of the area of memory where the primitive data is to be built. One the
primitive data is built, it is sent to the GS using the DMAC operating in
normal mode. This could be extended to accommodate stitching if necessary
The prototype for BuildSprite2D()
is:
int
BuildSprite2D(PS2Sprite_t * pSprite, char *& pMem)
Notice that the actual
memory pointer, pMem, (rather than a copy of the pointer) is passed to this
function so that it can be incremented to reflect the amount of data written
into the packet memory ( this is the function of the *& operator).
From the position of the
centre of the sprite the coordinates of the untransformed vertices are first
determined. These coordinates are then rotated by the angle that is contained
within the sprite structure to determine the rotated coordinates of the
vertices.
The prim field of the
GIFtag is set up to draw a triangle strip primitive and texture mapping is
enabled. Notice that the REGS field of the GIFTag is configured with the line
of code:
pGIFTag->s.REGS =
(uint64(0x5) << 32) | (uint64(0x35353531));
Each hex digit refers to a
GIF register address, 1 being RGBAQ, 3 being UV and 5 being XYZ2. After the
GIFTag, 9 registers are configured, one colour, four vertex positions and four
texture coordinates.
Notice that the XYZ data
for the registers are constructed from the rotated coordinates of the quad. Also,
in order to preserve accuracy, the floating point coordinate values are
multiplied by 16.0f (also in floating point) before being passed into the
function AddVertexFP() which adds the vertex information to the XYZ2 packed
register data.
At the end of the
function, the memory pointer, pMem in set to point to the next free location
after the written data.
On running the program,
two sprites are drawn on screen, one is static, the other is an animated
character which walks around the screen under control of the user. Full
information on controlling the character is contained within the accompanying
readme file.
This tutorial has
introduced the concepts associated with 2d movement and rotation under control
of user input. In the next tutorial the techniques will be extended to
investigate the use of matrices to transform the vertices.
Dr Henry S Fortuna
University of Abertay
Dundee