You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
826 lines
29 KiB
826 lines
29 KiB
<html><head>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<link rel="stylesheet" type="text/css" href="Chapter%201.5%20Fixed%20point%20Math_files/style.css">
|
|
<title>Chapter 1.5: Fixed point Math</title>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
|
|
</head>
|
|
<body>
|
|
|
|
<h1>Chapter 1.5: Fixed Point Math</h1>
|
|
|
|
<p>If you've been trying to write some games on a modern system (ie. your
|
|
PC), you probably already know about using floats to implement momentum and
|
|
jumping physics in a 2D platformer, or have something move towards a direction
|
|
specified in degrees rather than X,Y velocity values to have said object
|
|
move at an angle, like a ship in an Asteroids style game or a car in a
|
|
top-down racer... On the PlayStation, these principles still apply. But
|
|
there's a problem; the PlayStation does not have a hardware floating point
|
|
unit.</p>
|
|
|
|
<p>Whilst you can still use floats on the PlayStation as the compiler will
|
|
resort to software emulation to perform such operations, which you might
|
|
be able to get away with, it's not exactly the most ideal method as software
|
|
emulation of floats is quite slow, and will become an issue if used for
|
|
collision detection or processing movement of a hundred projectile
|
|
entities... The alternative that's better suited for a system without a
|
|
floating point unit is to use a integer based fractional number system
|
|
commonly known as fixed point math.</p>
|
|
|
|
<p>As the name suggests, fixed point math is a trick for storing fractional
|
|
numbers with fixed points, in this case an integer scale of 4096 will have
|
|
a range between zero to 4095 representing a fractional value, with 4096
|
|
representing a integer value of 1. Fixed point math is used heavily in games
|
|
made for systems that lacked a floating point unit (5th generation and older
|
|
consoles), or where integer math is considerably faster than floating point
|
|
math and not all systems feature a floating point unit
|
|
(ie. IBM compatible PCs).</p>
|
|
|
|
<p>This chapter covers the general idea and basics of performing fixed point
|
|
integer arithmetic. This is a highly essential chapter when getting into
|
|
PlayStation homebrew development, as learning it will increase the range of
|
|
games you can write for the console immensely. If you've already learned some
|
|
binary encoding of numeric values this chapter might be a bit easier to
|
|
understand.</p>
|
|
|
|
<p><b>Compatible with PSn00bSDK:</b> Yes</p>
|
|
|
|
<h2>Tutorial Index</h2>
|
|
<ul>
|
|
<li><a href="#principles">Principle of Fixed Point Math</a></li>
|
|
<li><a href="#basics">Fixed Point Arithmetic Basics</a></li>
|
|
<li><a href="#tankcontrols">Implementing Tank Controls with Fixed Point</a></li>
|
|
<li><a href="#asteroids">Asteroids Style Physics with Fixed Point</a></li>
|
|
<li><a href="#spriterot">Bonus: Fixed Point Sprite Rotation</a></li>
|
|
<li><a href="#limitations">Limitations of Fixed Point Math</a></li>
|
|
<li><a href="#conclusion">Conclusion and Further Reading</a></li>
|
|
</ul>
|
|
|
|
<h2 id="principles">Principle of Fixed Point Math</h2>
|
|
|
|
<p>The basic principle of fixed point integer math is instead of representing
|
|
a real number of 1.25 as a floating point number, you instead represent
|
|
the number as an integer value from a scale value, in this case 4096 which is
|
|
defined as <b>ONE</b> in the GTE library headers. So a real value of 1.25
|
|
will look like 5120 when converted to fixed point math with a scale of 4096.
|
|
Converting a floating point number to a fixed point number is achieved by
|
|
simply multiplying the real number against the scale value and rounding off
|
|
the fractions, turning the result into a plain integer value. The following
|
|
table shows some sample floating point values represented in fixed point.</p>
|
|
|
|
<center>
|
|
<table class="bordered-table">
|
|
<tbody><tr>
|
|
<th>Floating point representation</th><th>Fixed point representation</th>
|
|
</tr>
|
|
<tr><td>1.25</td><td>5120</td></tr>
|
|
<tr><td>0.75</td><td>3072</td></tr>
|
|
<tr><td>0.2</td><td>819</td></tr>
|
|
<tr><td>21.284</td><td>87179</td></tr>
|
|
<tr><td colspan="2"><center>1.0 = 4096</center></td></tr>
|
|
</tbody></table>
|
|
</center>
|
|
|
|
<p>Using a scale value of 4096 is most ideal for PlayStation projects not
|
|
only because it provides a good balance between precision and the maximum
|
|
decimal value it can store, alongside allowing fixed point values to be
|
|
rounded and divided quickly with simple bit shift operations and AND masks,
|
|
but also because the Geometry Transformation Engine (GTE) also uses the
|
|
same scale value for some of its registers. Any scale value can be used to
|
|
gain more precision or to increase the decimal range limit. Generally
|
|
a power of two scale value is recommended for performance reasons.</p>
|
|
|
|
<center>
|
|
<table class="bordered-table">
|
|
<tbody><tr>
|
|
<td><b>Bits</b></td>
|
|
<td>31</td><td>30</td><td>29</td><td>28</td><td>27</td><td>26</td><td>25</td>
|
|
<td>24</td><td>23</td><td>22</td><td>21</td><td>20</td><td>19</td><td>18</td>
|
|
<td>17</td><td>16</td><td>15</td><td>14</td><td>13</td><td>12</td><td>11</td>
|
|
<td>10</td><td>9</td><td>8</td><td>7</td><td>6</td><td>5</td><td>4</td>
|
|
<td>3</td><td>2</td><td>1</td><td>0</td>
|
|
</tr>
|
|
<tr>
|
|
<td><b>Description</b></td>
|
|
<td>Sign</td>
|
|
<td colspan="19">Decimal (19 bits)</td>
|
|
<td colspan="12">Fractional (12 bits)</td>
|
|
</tr>
|
|
</tbody></table>
|
|
</center>
|
|
|
|
<p>The table above illustrates a fixed point value with a scale value
|
|
of 4096 in a signed 32-bit integer. As there's only 19 bits available for
|
|
the decimal portion the maximum range for the decimal value is -524288 to
|
|
524287.</p>
|
|
|
|
|
|
<h2 id="basics">Fixed Point Arithmetic Basics</h2>
|
|
|
|
<p>Simple mathematical operations in fixed point such as addition and
|
|
subtraction is not that much different to performing the same operations
|
|
with integers, only values have to be based around the scale value.</p>
|
|
<pre>fixed_value += 4096; // += 1.0
|
|
fixed_value -= 4096; // -= 1.0
|
|
fixed_value += 2048; // += 0.5
|
|
fixed_value -= 6144; // -= 1.5
|
|
</pre>
|
|
|
|
<p>For decimal multiplication and divide operations, the value for the
|
|
dividend or multiplicand stays as-is.</p>
|
|
<pre>fixed_value *= 5; // *= 5;
|
|
fixed_value /= 10; // /= 10;
|
|
</pre>
|
|
|
|
<p>The equation for performing fractional multiplications is a little
|
|
tricky, but still not too complicated.</p>
|
|
<pre>multiplicand = 2048; // multiplicand = 0.5;
|
|
fixed_value = (fixed_value*multiplicand)>>12; // fixed_value *= multiplicand;
|
|
|
|
multiplicand = 6144; // multiplicand = 1.5;
|
|
fixed_value = (fixed_value*multiplicand)>>12; // fixed_value *= multiplicand;
|
|
</pre>
|
|
|
|
<p>For fractional divisions, the value will need to be multiplied by the
|
|
scale value and then divided by the dividend. Keep in mind that division by
|
|
zero would still occur if the dividend is zero.
|
|
</p><pre>dividend = 2048; // dividend = 0.5;
|
|
fixed_value = (fixed_value*4096)/dividend; // fixed_value /= dividend;
|
|
|
|
dividend = 6144; // dividend = 1.5;
|
|
fixed_value = (fixed_value*4096)/dividend; // fixed_value /= dividend;
|
|
</pre>
|
|
|
|
|
|
<h2 id="tankcontrols">Implementing Tank Controls with Fixed Point</h2>
|
|
|
|
<p>As a little exercise, this part of the chapter will go through using fixed
|
|
point math to implement tank controls. The basic jist of this is that instead
|
|
of moving the player position directly by which direction of the D-pad is
|
|
pressed, instead, left and right will adjust the angle the player is facing
|
|
while up and down moves the player forward and backward perpendicular to
|
|
where the player is facing. To do this requires using sine and cosine values
|
|
and conveniently, the GTE libraries of both SDKs feature integer based sine
|
|
and cosine functions which is exactly what's needed for this exercise.</p>
|
|
|
|
<p>In the official PsyQ or Programmer's Tool SDKs there are two integer based
|
|
sine and cosine functions which are <b>rsin()</b>/<b>rcos()</b> and
|
|
<b>csin()</b>/<b>ccos()</b>. The differences between the two is that
|
|
<b>rsin()</b>/<b>rcos()</b> takes up less code but is slower, whereas
|
|
<b>csin()</b>/<b>ccos()</b> takes up more code but is faster. PSn00bSDK on
|
|
the other hand only has one set of functions called <b>isin()/icos()</b>,
|
|
which are based on a Taylor series implementation which is both small and fast.
|
|
PSn00bSDK provides definitions for <b>rsin()</b>/<b>rcos()</b> and
|
|
<b>csin()</b>/<b>ccos()</b> that simply points to <b>isin()/icos()</b>
|
|
for compatibility with PsyQ / Programmer's Tool projects and sample code.</p><p>
|
|
|
|
</p><p>These functions also take degrees in a fixed point range of 0 to 4096
|
|
where 4096 equals to 360 degrees, so a value of 45 degrees will be
|
|
specified as 512. The value returned is also in a fixed point notation
|
|
with a scale of 4096, where 4096 equals to 1.0.</p>
|
|
|
|
<p>Working from code from the controllers chapter, rewrite the code that
|
|
defines the initial values of the player position to begin at the center
|
|
of the screen. Because the player coordinates will be in fixed point, the
|
|
screen center coordinate must be multiplied by <b>ONE</b>.</p>
|
|
|
|
<pre>pos_x = ONE*(disp[0].disp.w>>1);
|
|
pos_y = ONE*(disp[0].disp.h>>1);
|
|
</pre>
|
|
|
|
<p>Then, define a new variable for the player's angle and set the initial
|
|
value to zero.</p>
|
|
|
|
<pre>int angle;
|
|
|
|
angle = 0;
|
|
</pre>
|
|
|
|
<p>The player will be represented as a rotating triangle in this chaper, so
|
|
a simple array of vector coordinates will define the shape of the triangle.
|
|
The triangle will be rotated with, you guessed it, fixed point math as well.
|
|
</p>
|
|
|
|
<pre>SVECTOR player_tri[] =
|
|
{
|
|
{ 0, -20, 0 },
|
|
{ 10, 20, 0 },
|
|
{ -10, 20, 0 }
|
|
};
|
|
</pre>
|
|
|
|
<p>Then ditch the code for sorting <b>TILE</b> and <b>SPRT</b> primitives
|
|
and replace it with the following routine that sorts a rotating triangle.</p>
|
|
<pre>POLY_F3 *tri;
|
|
|
|
...
|
|
|
|
// Rotate the triangle coordinates based on the player's angle
|
|
// as well as apply the position
|
|
for( i=0; i<3; i++ )
|
|
{
|
|
v[i].vx = (((player_tri[i].vx*ccos( angle ))
|
|
-(player_tri[i].vy*csin( angle )))>>12)+(pos_x>>12);
|
|
v[i].vy = (((player_tri[i].vy*ccos( angle ))
|
|
+(player_tri[i].vx*csin( angle )))>>12)+(pos_y>>12);
|
|
}
|
|
|
|
// Sort the player triangle
|
|
tri = (POLY_F3*)nextpri;
|
|
setPolyF3( tri );
|
|
setRGB0( tri, 255, 255, 0 );
|
|
setXY3( tri,
|
|
v[0].vx, v[0].vy,
|
|
v[1].vx, v[1].vy,
|
|
v[2].vx, v[2].vy );
|
|
|
|
addPrim( ot[db], tri );
|
|
nextpri += sizeof(POLY_F3);
|
|
</pre>
|
|
|
|
<p>And then replace the input code with the following. If you've written
|
|
something that played like Asteroids in the past, this may look pretty
|
|
familiar.</p>
|
|
<pre>if( !(pad->btn&PAD_UP) ) // test UP
|
|
{
|
|
pos_x += csin( angle );
|
|
pos_y -= ccos( angle );
|
|
}
|
|
else if( !(pad->btn&PAD_DOWN) ) // test DOWN
|
|
{
|
|
pos_x -= csin( angle );
|
|
pos_y += ccos( angle );
|
|
}
|
|
if( !(pad->btn&PAD_LEFT) ) // test LEFT
|
|
{
|
|
// Turns counter-clockwise
|
|
angle -= 16;
|
|
}
|
|
else if( !(pad->btn&PAD_RIGHT) ) // test RIGHT
|
|
{
|
|
// Turns clockwise
|
|
angle += 16;
|
|
}
|
|
</pre>
|
|
|
|
<p>Next is to finally add some text drawing so the player coordinates can be
|
|
displayed on screen, so you can see how fixed point numbers work while
|
|
interacting with the sample program more easily. This can be done with the
|
|
debug font functions provided by <h>libetc</h> or <h>psxetc</h> in both
|
|
PsyQ / Programmer's Tool and PSn00bSDK respectively. These font functions are
|
|
intended for debugging purposes, so they're not fit for drawing things like
|
|
player status with custom fonts and lower case letters.</p>
|
|
|
|
<p>Start by loading the debug font texture to VRAM using <b>FntLoad()</b>,
|
|
then, initialize a font window using <b>FntOpen()</b>. Ideally, you should
|
|
put these calls inside the <b>init()</b> function.</p>
|
|
|
|
<pre>// Load the font texture on the upper-right corner of the VRAM
|
|
FntLoad( 960, 0 );
|
|
|
|
// Define a font window of 100 characters covering the whole screen
|
|
FntOpen( 0, 8, 320, 224, 0, 100 );
|
|
</pre>
|
|
|
|
<p>Text can be printed using <b>FntPrint()</b> which works more or less like
|
|
<b>printf()</b>, only text output is directed to the specified font window.
|
|
The first argument specifies which font window the text should go to, the
|
|
value of which is obtained through the return value of <b>FntOpen()</b>.
|
|
Specifying -1 directs text to the last opened font window. The debug font
|
|
routines can only draw uppercase text, and text color and font cannot be
|
|
customized.</p>
|
|
|
|
<p>In PsyQ / Programmers tool the first argument can be omitted, but modern
|
|
GNU GCC does not support this convention anymore.</p>
|
|
|
|
<pre>// Print player coordinates
|
|
FntPrint( -1, "POS_X=%d (%d.%d)\n", pos_x, (pos_x>>12), (pos_x&0xfff) );
|
|
FntPrint( -1, "POS_Y=%d (%d.%d)\n", pos_y, (pos_y>>12), (pos_y&0xfff) );
|
|
FntPrint( -1, "ANGLE=%d\n", angle );
|
|
</pre>
|
|
|
|
<p>Then to make the text actually appear, call <b>FntFlush()</b> to draw the
|
|
characters 'printed' by <b>FntPrint()</b> and flush the character buffer.
|
|
This should be called immediately before the <b>display()</b> call in the
|
|
sample code.</p>
|
|
|
|
<pre>// Draw and flush the character buffer
|
|
FntFlush( -1 );
|
|
</pre>
|
|
|
|
<p>The finished code should look like the following (the texture stuff
|
|
remains for future samples):</p>
|
|
<pre>#include <sys/types.h> // This provides typedefs needed by libgte.h and libgpu.h
|
|
#include <stdio.h> // Not necessary but include it anyway
|
|
#include <psxetc.h> // Includes some functions that controls the display
|
|
#include <psxgte.h> // GTE header, not really used but libgpu.h depends on it
|
|
#include <psxgpu.h> // GPU library header
|
|
#include <psxapi.h>
|
|
|
|
#define OTLEN 8 // Ordering table length (recommended to set as a define
|
|
// so it can be changed easily)
|
|
|
|
DISPENV disp[2]; // Display/drawing buffer parameters
|
|
DRAWENV draw[2];
|
|
int db = 0;
|
|
|
|
// PSn00bSDK requires having all u_long types replaced with
|
|
// u_int, as u_long in modern GCC that PSn00bSDK uses defines it as a 64-bit integer.
|
|
|
|
u_int ot[2][OTLEN]; // Ordering table length
|
|
char pribuff[2][32768]; // Primitive buffer
|
|
char *nextpri; // Next primitive pointer
|
|
|
|
int tim_mode; // TIM image parameters
|
|
RECT tim_prect,tim_crect;
|
|
int tim_uoffs,tim_voffs;
|
|
|
|
// Pad stuff
|
|
#define PAD_SELECT 1
|
|
#define PAD_L3 2
|
|
#define PAD_R3 4
|
|
#define PAD_START 8
|
|
#define PAD_UP 16
|
|
#define PAD_RIGHT 32
|
|
#define PAD_DOWN 64
|
|
#define PAD_LEFT 128
|
|
#define PAD_L2 256
|
|
#define PAD_R2 512
|
|
#define PAD_L1 1024
|
|
#define PAD_R1 2048
|
|
#define PAD_TRIANGLE 4096
|
|
#define PAD_CIRCLE 8192
|
|
#define PAD_CROSS 16384
|
|
#define PAD_SQUARE 32768
|
|
|
|
typedef struct _PADTYPE
|
|
{
|
|
unsigned char stat;
|
|
unsigned char len:4;
|
|
unsigned char type:4;
|
|
unsigned short btn;
|
|
unsigned char rs_x,rs_y;
|
|
unsigned char ls_x,ls_y;
|
|
} PADTYPE;
|
|
|
|
u_char padbuff[2][34];
|
|
|
|
// For the player triangle
|
|
SVECTOR player_tri[] = {
|
|
{ 0, -20, 0 },
|
|
{ 10, 20, 0 },
|
|
{ -10, 20, 0 }
|
|
};
|
|
|
|
void display() {
|
|
|
|
DrawSync(0); // Wait for any graphics processing to finish
|
|
|
|
VSync(0); // Wait for vertical retrace
|
|
|
|
PutDispEnv(&disp[db]); // Apply the DISPENV/DRAWENVs
|
|
PutDrawEnv(&draw[db]);
|
|
|
|
SetDispMask(1); // Enable the display
|
|
|
|
DrawOTag(ot[db]+OTLEN-1); // Draw the ordering table
|
|
|
|
db = !db; // Swap buffers on every pass (alternates between 1 and 0)
|
|
nextpri = pribuff[db]; // Reset next primitive pointer
|
|
|
|
}
|
|
|
|
// Texture upload function
|
|
void LoadTexture(u_int *tim, TIM_IMAGE *tparam) {
|
|
|
|
// Read TIM parameters (PsyQ)
|
|
//OpenTIM(tim);
|
|
//ReadTIM(tparam);
|
|
|
|
// Read TIM parameters (PSn00bSDK)
|
|
GetTimInfo(tim, tparam);
|
|
|
|
// Upload pixel data to framebuffer
|
|
LoadImage(tparam->prect, tparam->paddr);
|
|
DrawSync(0);
|
|
|
|
// Upload CLUT to framebuffer if present
|
|
if( tparam->mode & 0x8 ) {
|
|
|
|
LoadImage(tparam->crect, tparam->caddr);
|
|
DrawSync(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void loadstuff(void) {
|
|
|
|
TIM_IMAGE my_image; // TIM image parameters
|
|
|
|
extern u_int tim_my_image[];
|
|
|
|
// Load the texture
|
|
LoadTexture(tim_my_image, &my_image);
|
|
|
|
// Copy the TIM coordinates
|
|
tim_prect = *my_image.prect;
|
|
tim_crect = *my_image.crect;
|
|
tim_mode = my_image.mode;
|
|
|
|
// Calculate U,V offset for TIMs that are not page aligned
|
|
tim_uoffs = (tim_prect.x%64)<<(2-(tim_mode&0x3));
|
|
tim_voffs = (tim_prect.y&0xff);
|
|
|
|
}
|
|
|
|
// To make main look tidy, init stuff has to be moved here
|
|
void init(void) {
|
|
|
|
// Reset graphics
|
|
ResetGraph(0);
|
|
|
|
// First buffer
|
|
SetDefDispEnv(&disp[0], 0, 0, 320, 240);
|
|
SetDefDrawEnv(&draw[0], 0, 240, 320, 240);
|
|
// Second buffer
|
|
SetDefDispEnv(&disp[1], 0, 240, 320, 240);
|
|
SetDefDrawEnv(&draw[1], 0, 0, 320, 240);
|
|
|
|
draw[0].isbg = 1; // Enable clear
|
|
setRGB0(&draw[0], 63, 0, 127); // Set clear color (dark purple)
|
|
draw[1].isbg = 1;
|
|
setRGB0(&draw[1], 63, 0, 127);
|
|
|
|
nextpri = pribuff[0]; // Set initial primitive pointer address
|
|
|
|
// load textures and possibly other stuff
|
|
loadstuff();
|
|
|
|
// set tpage of lone texture as initial tpage
|
|
draw[0].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
|
|
draw[1].tpage = getTPage( tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
|
|
|
|
// apply initial drawing environment
|
|
PutDrawEnv(&draw[!db]);
|
|
|
|
// Initialize the pads
|
|
InitPAD( padbuff[0], 34, padbuff[1], 34 );
|
|
|
|
// Begin polling
|
|
StartPAD();
|
|
|
|
// To avoid VSync Timeout error, may not be defined in PsyQ
|
|
ChangeClearPAD( 1 );
|
|
|
|
// Load the font texture on the upper-right corner of the VRAM
|
|
FntLoad( 960, 0 );
|
|
|
|
// Define a font window of 100 characters covering the whole screen
|
|
FntOpen( 0, 8, 320, 224, 0, 100 );
|
|
|
|
}
|
|
|
|
int main() {
|
|
|
|
int i;
|
|
int pos_x,pos_y,angle;
|
|
PADTYPE *pad;
|
|
POLY_F3 *tri;
|
|
SVECTOR v[3];
|
|
|
|
TILE *tile; // Pointer for TILE
|
|
SPRT *sprt; // Pointer for SPRT
|
|
|
|
// Init stuff
|
|
init();
|
|
|
|
pos_x = ONE*(disp[0].disp.w>>1);
|
|
pos_y = ONE*(disp[0].disp.h>>1);
|
|
angle = 0;
|
|
|
|
while(1) {
|
|
|
|
// Parse controller input
|
|
pad = (PADTYPE*)padbuff[0];
|
|
|
|
// Only parse inputs when a controller is connected
|
|
if( pad->stat == 0 )
|
|
{
|
|
// Only parse when a digital pad,
|
|
// dual-analog and dual-shock is connected
|
|
if( ( pad->type == 0x4 ) ||
|
|
( pad->type == 0x5 ) ||
|
|
( pad->type == 0x7 ) )
|
|
{
|
|
if( !(pad->btn&PAD_UP) ) // test UP
|
|
{
|
|
pos_x += csin( angle );
|
|
pos_y -= ccos( angle );
|
|
}
|
|
else if( !(pad->btn&PAD_DOWN) ) // test DOWN
|
|
{
|
|
pos_x -= csin( angle );
|
|
pos_y += ccos( angle );
|
|
}
|
|
if( !(pad->btn&PAD_LEFT) ) // test LEFT
|
|
{
|
|
angle -= 16;
|
|
}
|
|
else if( !(pad->btn&PAD_RIGHT) ) // test RIGHT
|
|
{
|
|
angle += 16;
|
|
}
|
|
}
|
|
}
|
|
|
|
ClearOTagR(ot[db], OTLEN); // Clear ordering table
|
|
|
|
// Rotate the triangle coordinates based on the player's angle
|
|
// as well as apply the position
|
|
for( i=0; i<3; i++ )
|
|
{
|
|
v[i].vx = (((player_tri[i].vx*icos( angle ))
|
|
-(player_tri[i].vy*csin( angle )))>>12)+(pos_x>>12);
|
|
v[i].vy = (((player_tri[i].vy*icos( angle ))
|
|
+(player_tri[i].vx*csin( angle )))>>12)+(pos_y>>12);
|
|
}
|
|
|
|
// Sort the player triangle
|
|
tri = (POLY_F3*)nextpri;
|
|
setPolyF3( tri );
|
|
setRGB0( tri, 255, 255, 0 );
|
|
setXY3( tri,
|
|
v[0].vx, v[0].vy,
|
|
v[1].vx, v[1].vy,
|
|
v[2].vx, v[2].vy );
|
|
addPrim( ot[db], tri );
|
|
nextpri += sizeof(POLY_F3);
|
|
|
|
// Print player coordinates
|
|
FntPrint( -1, "POS_X=%d (%d.%d)\n", pos_x, (pos_x>>12), (pos_x&0xfff) );
|
|
FntPrint( -1, "POS_Y=%d (%d.%d)\n", pos_y, (pos_y>>12), (pos_y&0xfff) );
|
|
FntPrint( -1, "ANGLE=%d\n", angle );
|
|
|
|
// Draw and flush the character buffer
|
|
FntFlush( -1 );
|
|
|
|
// Update the display
|
|
display();
|
|
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
</pre>
|
|
|
|
<p>Compile and execute the code and you should get a triangle that controls
|
|
more or less like a tank. Press the left and right directional buttons to
|
|
turn the triangle while pressing up and down moves the triangle forwards and
|
|
backward perpendicular to where the triangle is pointing.</p>
|
|
|
|
<center>
|
|
<img src="Chapter%201.5%20Fixed%20point%20Math_files/tankmove.png">
|
|
</center>
|
|
|
|
|
|
<h2 id="asteroids">Asteroids Style Physics with Fixed Point</h2>
|
|
|
|
<p>Now to make the tank controls sample a little more interesting by making
|
|
it control like an Asteroids or Spacewar game. Basically, instead of
|
|
accumulating the player coordinates directly with values based on the
|
|
player's angle you accumulate them to a velocity vector, which in turn
|
|
accumulates on the player's coordinates. This way, simple momentum based
|
|
physics are archieved.</p>
|
|
|
|
<p>This can be done by implementing some changes to the last sample. Start
|
|
by adding variables in <b>main()</b> that'll be used to store the player's
|
|
velocity coordinates.</p>
|
|
|
|
<pre>int vel_x,vel_y;
|
|
|
|
...
|
|
|
|
// just to make sure they don't contain garbage
|
|
vel_x = 0;
|
|
vel_y = 0;
|
|
</pre>
|
|
|
|
<p>Next, modify some of the pad code to accumulate player movement
|
|
towards the velocity variables instead of the player's coordinates.
|
|
Put some bit shifts that divides the sin/cos values by 8 otherwise
|
|
the 'ship' will accelerate too quickly.</p>
|
|
|
|
<pre>if( !(pad->btn&PAD_UP) ) // test UP
|
|
{
|
|
vel_x += csin( angle )>>3;
|
|
vel_y -= ccos( angle )>>3;
|
|
}
|
|
else if( !(pad->btn&PAD_DOWN) ) // test DOWN
|
|
{
|
|
vel_x -= csin( angle )>>3;
|
|
vel_y += ccos( angle )>>3;
|
|
}
|
|
</pre>
|
|
|
|
<p>Immediately after the pad code add a few lines that accumulates the
|
|
velocity coordinates to the player's coordinates.</p>
|
|
|
|
<pre>// accumulate player coordinates by its velocity
|
|
pos_x += vel_x;
|
|
pos_y += vel_y;
|
|
</pre>
|
|
|
|
<p>And to keep the player from going off-screen, place the following lines
|
|
immediately after the above code to wrap the player's coordinates when they
|
|
go off-screen.</p>
|
|
|
|
<pre>// wrap player coordinates from going off-screen
|
|
if( (pos_x>>12) < 0 )
|
|
{
|
|
pos_x += (320<<12);
|
|
}
|
|
if( (pos_x>>12) > 320 )
|
|
{
|
|
pos_x -= (320<<12);
|
|
}
|
|
if( (pos_y>>12) < 0 )
|
|
{
|
|
pos_y += (240<<12);
|
|
}
|
|
if( (pos_y>>12) > 240 )
|
|
{
|
|
pos_y -= (240<<12);
|
|
}
|
|
</pre>
|
|
|
|
<p>Then place some additional <b>FntPrint()</b> calls that displays the
|
|
values from the velocity variables. You'll also need to increase the
|
|
character buffer size of the <b>FntOpen()</b> call (the last argument)
|
|
from 100 to 150.</p>
|
|
|
|
<pre>FntPrint( -1, "VEL_X=%d (%d.%d)\n", vel_x, (vel_x>>12), (vel_x&0xfff) );
|
|
FntPrint( -1, "VEL_Y=%d (%d.%d)\n", vel_y, (vel_y>>12), (vel_y&0xfff) );
|
|
</pre>
|
|
|
|
<p>Compile the sample demo and the triangle should behave a lot like an
|
|
Asteroids ship.</p>
|
|
|
|
<center>
|
|
<img src="Chapter%201.5%20Fixed%20point%20Math_files/velocitymove.png">
|
|
</center>
|
|
|
|
<p>If you want velocity to slowly diminish overtime, add the following
|
|
lines just before the velocity vectors are accumulated to the player
|
|
coordinates. It also acts as a velocity constraint as well.</p>
|
|
|
|
<pre>// equivalent to multiplying each axis by 0.9765625
|
|
vel_x = (vel_x*4000)>>12;
|
|
vel_y = (vel_y*4000)>>12;
|
|
</pre>
|
|
|
|
|
|
<h2 id="spriterot">Bonus: Fixed Point Sprite Rotation</h2>
|
|
|
|
<p>Since it is something new programmers would find incredibly handy and to
|
|
show off some of what the 32-bit console can do, especially for PSn00bSDK folk
|
|
as it does not have an equivalent to libgs, here's a fixed point based sprite
|
|
rotation and scaling routine that draws a rotated and scaled sprite with a
|
|
single quad. The math for rotation is more or less the same as how the player
|
|
triangle is rotated.</p>
|
|
|
|
<pre>void sortRotSprite( int x, int y, int pw, int ph, int angle, int scale )
|
|
{
|
|
POLY_FT4 *quad;
|
|
SVECTOR s[4];
|
|
SVECTOR v[4];
|
|
|
|
int i,cx,cy;
|
|
|
|
// calculate the pivot point (center) of the sprite
|
|
cx = pw>>1;
|
|
cy = ph>>1;
|
|
|
|
// increment by 0.5 on the bottom and right coords so scaling
|
|
// would increment a bit smoother
|
|
s[0].vx = -(((pw*scale)>>12)-cx);
|
|
s[0].vy = -(((ph*scale)>>12)-cy);
|
|
|
|
s[1].vx = (((pw*scale)+2048)>>12)-cx;
|
|
s[1].vy = s[0].vy;
|
|
|
|
s[2].vx = -(((pw*scale)>>12)-cx);
|
|
s[2].vy = (((ph*scale)+2048)>>12)-cy;
|
|
|
|
s[3].vx = (((pw*scale)+2048)>>12)-cx;
|
|
s[3].vy = s[2].vy;
|
|
|
|
// a simple but pretty effective optimization trick
|
|
cx = ccos( angle );
|
|
cy = csin( angle );
|
|
|
|
// calculate rotated sprite coordinates
|
|
for( i=0; i<4; i++ )
|
|
{
|
|
v[i].vx = (((s[i].vx*cx)
|
|
-(s[i].vy*cy))>>12)+x;
|
|
v[i].vy = (((s[i].vy*cx)
|
|
+(s[i].vx*cy))>>12)+y;
|
|
}
|
|
|
|
// initialize the quad primitive for the sprite
|
|
quad = (POLY_FT4*)nextpri;
|
|
setPolyFT4( quad );
|
|
|
|
// set CLUT and tpage to the primitive
|
|
setTPage( quad, tim_mode&0x3, 0, tim_prect.x, tim_prect.y );
|
|
setClut( quad, tim_crect.x, tim_crect.y );
|
|
|
|
// set color, screen coordinates and texture coordinates of primitive
|
|
setRGB0( quad, 128, 128, 128 );
|
|
setXY4( quad,
|
|
v[0].vx, v[0].vy,
|
|
v[1].vx, v[1].vy,
|
|
v[2].vx, v[2].vy,
|
|
v[3].vx, v[3].vy );
|
|
setUVWH( quad, tim_uoffs, tim_voffs, pw, ph );
|
|
|
|
// add it to the ordering table
|
|
addPrim( ot[db], quad );
|
|
nextpri += sizeof(POLY_FT4);
|
|
|
|
}
|
|
</pre>
|
|
|
|
<p>By this point, it should be easy to figure out how to use this function
|
|
in the sample above. The <i>angle</i> and <i>scale</i> arguments are of a
|
|
fixed point notation, where 4096 is 360 degrees on the former and 4096 is
|
|
1.0 on the latter.</p>
|
|
|
|
<center>
|
|
<img src="Chapter%201.5%20Fixed%20point%20Math_files/rotscalesprite.png">
|
|
</center>
|
|
|
|
<h2 id="limitations">Limitations of Fixed Point Math</h2>
|
|
|
|
<p>One limitation that becomes apparent when performing 32-bit fixed point
|
|
math arithmetic is when performing fractional multiplication operations with
|
|
large fixed point values due to the C compiler usually always performing
|
|
multiplication and bit shifting operations with 32-bit registers, as
|
|
multiplying a large fixed point value by a large fixed point multiplicand
|
|
will likely exceed the capacity of a 32-bit word, yielding a faulty
|
|
result.</p>
|
|
|
|
<p>The MIPS R3000 CPU of the PlayStation can actually return a 64-bit
|
|
multiplication result, but the C compiler doesn't really make use of this
|
|
trait when performing multiplication operations with 32-bit integers. So the
|
|
only way to effectively take advantage of this is to use some in-line
|
|
assembly code. Should be pretty self explanatory on how and where it should
|
|
be used.</p>
|
|
|
|
<pre>/* in-line assembly macro for performing multiplication */
|
|
/* operations with 12-bit fractions. Uses what is effectively */
|
|
/* 64-bit maths with both hi and lo result registers to avoid */
|
|
/* overflow bugs when using 32-bit maths. */
|
|
/* */
|
|
/* Performs r2 = ( r0 * r1 )>>12 with 64-bit arithmetic */
|
|
/* */
|
|
#define mult12( r0, r1, r2 ) __asm__ volatile ( \
|
|
"mult %1, %0;" /* multiply values (small * large is faster) */\
|
|
"mflo $t0;" /* retrieve the 64-bit result in two regs */\
|
|
"mfhi $t1;" \
|
|
"srl $t0, 12;" /* equivalent to dividing LO by 4096 */\
|
|
"and $t1, 0x0fff;" /* mask HI result to fit in upper 12 bits of */\
|
|
"sll $t1, 20;" /* LO result, then shift bits to position */\
|
|
"or $t0, $t1;" /* combine the bits */\
|
|
"sw $t0, ( %2 );" /* store the result to r2 */\
|
|
: \
|
|
: "r"( r0 ), "r"( r1 ), "r"( r2 ) \
|
|
: "lo", "hi", "t0", "t1", "memory" )
|
|
</pre>
|
|
|
|
<p>Though naturally, fixed point math can't really be as precise as floating
|
|
point math in certain operations. You can try increasing precision by
|
|
increasing the base value, alloting more bits for the fractional part of a
|
|
fixed point value, but there will still be something rough about it.</p>
|
|
|
|
|
|
<h2 id="conclusion">Conclusion and Further Reading</h2>
|
|
|
|
<p>This chapter only really covers some pretty basic but highly essential math
|
|
operations with fixed point math, but it should be enough to get you started
|
|
in learning the practice. Interestingly fixed point math is still somewhat
|
|
talked about around the modern Internet, so you may want to look around for
|
|
those for further reading if need-be.</p>
|
|
|
|
<hr>
|
|
<table width="100%">
|
|
<tbody><tr>
|
|
<td width="40%" align="left"><a href="http://lameguy64.net/tutorials/pstutorials/chapter1/4-controllers.html">Previous</a></td>
|
|
<td width="20%" align="center"><a href="http://lameguy64.net/tutorials/pstutorials/index.html">Back to Index</a></td>
|
|
<td width="40%" align="right"><a href="http://lameguy64.net/tutorials/pstutorials/chapter1/6-cdreading.html">Next</a></td>
|
|
</tr>
|
|
</tbody></table>
|
|
|
|
|
|
|
|
</body></html>
|