gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Beginners > basic physics, direction and motion issue

#20426 - tethered - Tue May 11, 2004 4:59 am

hello,

before i post i just want to say thanks for all your assistance thus far. this forum is an invaluable resource to anyone who is curious about developing on the gba console, and, quite frankly, i couldn't have gotten as far as i have in such a short time period without your help. thanks!

with that said... i've created a multiple layer, parallax scrolling background in tile mode 0, text bg 0. originally, you could scroll up, down, left, right, and all of the diagonals in between by pressing the corresponding d-pad button. i've decided to take it a step further now by adding some basic physics.

the intention is to allow the user to "steer" by pressing left and right (i.e., they press left, and it decreases the angle of direction, they press right, and it increases the angle of direction). and then when the user presses up, it should provide forward motion in the direction that you are facing. for now, to keep things simple, as i dont have much experience with sprites, i'm moving just the background around. eventually, i would like to have a ship sprite in the center of the screen that rotates to reflect the angle, but thats something i will do in a later session.

anyway, i've gotten the basic formula down...

the velocity is a constant 3 units per loop iteration, the direction vector is represented as an angle ranging from 0 - 359, and to calculate the x and y components of the velocity based on the direction vector, i'm using sine and cosine, respectively.

here's the full source:

Code:
#include "math.h"
#include "gba.h"
#include "master.pal.c"
#include "lvl1bg0.raw.c"
#include "lvl1bg0.map.c"
#include "lvl1bg1.raw.c"
#include "lvl1bg1.map.c"


void SetVideoMode(void);
void LoadPalette(void);
void LoadBackground(void);


int main()
{
    float f_x = 0, f_y = 0;
    float f_velocity = 3;
    float f_angle = 0;
    float f_x_displacement = 0, f_y_displacement = 0;

    SetVideoMode();
    LoadPalette();
    // vdraw . . .
    while (REG_VCOUNT < 160);
    LoadBackground();

    while(1) {
       
        f_x_displacement = 0;
        f_y_displacement = 0;
       
        // When LEFT is pressed, the ship should rotate to the left, thus
        // decrease angle by the specified rate... When it reaches 0,
        // roll over to 359...
        if(keyDown(KEY_LEFT))
        {
            f_angle = f_angle - .05;
            if (f_angle < 0)
                f_angle = 359;
        }

        // When RIGHT is pressed, the ship should rotate to the right, thus
        // increase the angle by the specified rate... When it reaches 359,
        // roll over to 0...
        if(keyDown(KEY_RIGHT))
        {
            f_angle = f_angle + .05;
            if (f_angle > 359)
                f_angle = 0;
        }

        // When UP is pressed, the ship should move at the specified velocity
        // in the direction it is facing... sin(angle) * velocity calculates
        // the displacement of the x component, and cos(angle) * velocity
        // calculates the displacement of the y component
        if(keyDown(KEY_UP))
        {
            f_x_displacement = f_x_displacement + sin(f_angle) * f_velocity;
            f_y_displacement = f_y_displacement + cos(f_angle) * f_velocity;
        }

        // vdraw . . .
        while (REG_VCOUNT < 160);

        // update x and y coordinates for background
        f_x = f_x + f_x_displacement;
        f_y = f_y - f_y_displacement; // inverted :)
        REG_BG0HOFS = f_x;
        REG_BG0VOFS = f_y;
        REG_BG1HOFS = (f_x / 2) + 100; // 100 == offset
        REG_BG1VOFS = (f_y / 2) + 100;
        REG_BG2HOFS = f_x / 7.5;
        REG_BG2VOFS = f_y / 7.5;

        // wait for vblank . . .
        while (REG_VCOUNT >= 160);
    }

    return 0;

}


void SetVideoMode(void)
{
    // set video mode
    SetMode(MODE_0 | BG0_ENABLE | BG1_ENABLE | BG2_ENABLE);
}


void LoadPalette(void)
{
    int i;

    // load palette data
    for (i = 0; i < 256; i++)
        BG_PaletteMem[i] = master_Palette[i];
}


void LoadBackground(void)
{
    u16* ScreenBB0 = (u16*)ScreenBaseBlock(0);
    u16* ScreenBB1 = (u16*)ScreenBaseBlock(1);
    u16* CharBB1 = (u16*) CharBaseBlock(1);
    u16* CharBB2 = (u16*) CharBaseBlock(2);
    int i;
   
    // set bg control register
    REG_BG0CNT = BG_COLOR_256 | TEXTBG_SIZE_256x256 | (0 << SCREEN_SHIFT) | (1 << CHAR_SHIFT);
    REG_BG1CNT = BG_COLOR_256 | TEXTBG_SIZE_256x256 | (0 << SCREEN_SHIFT) | (1 << CHAR_SHIFT);
    REG_BG2CNT = BG_COLOR_256 | TEXTBG_SIZE_256x256 | (1 << SCREEN_SHIFT) | (2 << CHAR_SHIFT);
       
    // load map data
    // -------------
    // layer 0
    for (i = 0; i < 1024; i++)
        ScreenBB0[i] = lvl1bg0_Map[i];
    // layer 1
    for (i = 0; i < 1024; i++)
        ScreenBB1[i] = lvl1bg1_Map[i];

    // load tile data
    // --------------
    // layer 0
    for (i = 0; i < (23 * 64) / 2; i++)
        CharBB1[i] = ((u16*) lvl1bg0_Tiles)[i];
    // layer 1
    for (i = 0; i < (232 * 64) / 2; i++)
        CharBB2[i] = ((u16*) lvl1bg1_Tiles)[i];
}


for the most part this seems to work. there is some slow down but that's not an issue at this point (i was kind of expecting it and i'm aware that you can pre-calculate the sine and cosine values). also, i'm aware that a lot of the other source isn't exactly the most efficient. as i said in an earlier thread, clarity is more important than optimization right now. the problem i'm running into, however, is that when you first begin to turn to the left it immediately jerks over to the right, and at various points around the rotation it jerks in different directions.

here is the compiled GBA rom:
http://cemmel.com/gbadev/demos/direction_and_motion.gba

i suspect it has something to do w/ my if statements that i'm using to roll over from 359 to 0, and 0 to 359, but i can't seem to make any real sense of it.

if anyone has any experience, please comment and let me know what i'm doing wrong.

thanks,


chuck

#20427 - poslundc - Tue May 11, 2004 5:31 am

The cause of the jerkiness is that the math library trig functions take radians as their input parameters, not degrees. So when your variable jumps from 0 to 359, you're actually jumping from 0 to 49 degrees. To fix this, multiply your angles by (PI / 180) before passing them to the trig functions.

When it comes time to optimize, you should consider abandoning both degrees and radians in favour of using a unit of measurement that divides evenly into a circle by a power of two. For example, you can make a cosine look-up table that has 256 entries (or 512 for more accuracy), and is periodic such that cos 256 = cos 0 = 1. This way you can wrap your variable around from 0 to 255 just by bitwise-ANDing it with 255 (rather than having to use the if statements). For that to work, you need to abandon floats for integers (which is a good idea anyway; you can check out the Beginner's FAQ for more info).

Dan.

#20428 - DiscoStew - Tue May 11, 2004 5:40 am

Poslundc is right. At first when I looks at your code, I was thinking in degrees, and would have only said something about going from 0 to 359, even though you are only using increments/decrements of .05, but when poslundc said radians, it all made sense. No wonder when you make a full counter-clockwise rotation is doesn't bring up the jerking problem until a few more full rotations, because when working in radians, 359 is considered a big number, and it slowly counts down to 0, while making its revolutions.
_________________
DS - It's all about DiscoStew

#20430 - tethered - Tue May 11, 2004 5:45 am

thanks a lot guys.


- chuck

#20432 - tethered - Tue May 11, 2004 6:33 am

Works beautifully. To continue the discussion, another thread (http://forum.gbadev.org/viewtopic.php?t=2771&highlight=physics) mentions the following algorithm for applying basic ateroids-like physics:

Code:
dx += cos_table[heading] * thrust;
dy += sin_table[heading] * thrust + gravity;
dx = dx * drag;
dy = dy * drag;
x += dx;
y += dy;


I was able to implement this successfully, my only issue now being that as thrust is applied it speeds up into infinity... I'm fiddling with methods of capping the maximum speed, but I'm getting nothing but awkward results. Any input would be helpful...


- Chuck

#20437 - yaustar - Tue May 11, 2004 1:36 pm

wouldnt something like

Code:
if(thrust > MAX_THRUST)
{
   thrust = MAX_THRUST;
}


Then do all the calcuations
_________________
[Blog] [Portfolio]

#20447 - tepples - Tue May 11, 2004 3:45 pm

tethered wrote:
tepples wrote:
Code:
dx += cos_table[heading] * thrust;
dy += sin_table[heading] * thrust + gravity;
dx = dx * drag;
dy = dy * drag;
x += dx;
y += dy;

I was able to implement this successfully, my only issue now being that as thrust is applied it speeds up into infinity

In this pseudocode, thrust is a force, not a speed. Think of a rocket-propelled craft floating in free 2-space, as in Asteroids. If there's no fire coming out of the rocket, then thrust = 0. To cap speed, set your craft's thrust capacity such that (max_speed + thrust) * drag = max_speed.

What kind of handling do you eventually expect to have in your craft? I can help you implement kinematics for it.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#20456 - tethered - Tue May 11, 2004 5:55 pm

tepples, i appreciate your help. at this point, the kinematics should be quite simple. you can change your direction vector by pressing left or right, and by pressing up it provides thrust.

right now, the result of all this is that the background layers shift around according to displacement. eventually, i'll have a ship sprite in the center of the screen that rotates around and can shoot and so forth.

here's my source at this point:

Code:
#define PI 3.14159265

int main()
{
    int i;
    float f_x = 0, f_y = 0;
    float f_thrust = .05;
    float f_angle = 0;
    float f_radian = f_angle * (PI / 180);
    float f_x_displacement = 0, f_y_displacement = 0;
    float f_drag = .95;
    float f_sin_array[128], f_cos_array[128];

    // generate SIN array
    f_angle = 0;
    for (i = 0; i < 128; i++)
    {
        f_radian = f_angle * (PI / 180);
        f_sin_array[i] = sin(f_radian);
        f_angle+=2.8125;
    }

    // generate COS array
    f_angle = 0;
    for (i = 0; i < 128; i++)
    {
        f_radian = f_angle * (PI / 180);
        f_cos_array[i] = cos(f_radian);
        f_angle+=2.8125;
    }
    f_angle = 0;

    SetVideoMode();
    LoadPalette();
    // vdraw . . .
    while (REG_VCOUNT < 160);
    LoadBackground();

    while(1) {

        // When LEFT is pressed, the ship should rotate to the left, thus
        // decrease angle by the specified rate... When it reaches 0,
        // roll over to 127...
        if(keyDown(KEY_LEFT))
        {
            f_angle = f_angle - 1;
            if (f_angle < 0)
                f_angle = 127;
        }

        // When RIGHT is pressed, the ship should rotate to the right, thus
        // increase the angle by the specified rate... When it reaches 128,
        // roll over to 0...
        if(keyDown(KEY_RIGHT))
        {
            f_angle = f_angle + 1;
            if (f_angle > 127)
                f_angle = 0;
        }

        // When UP is pressed, the ship should move at the specified velocity
        // in the direction it is facing... sin(angle) * thrust calculates
        // the displacement of the x component, and cos(angle) * thrust
        // calculates the displacement of the y component
        if(keyDown(KEY_UP))
        {
            f_x_displacement = f_x_displacement + f_sin_array[((int)f_angle)] * f_thrust;
            f_y_displacement = f_y_displacement + f_cos_array[((int)f_angle)] * f_thrust;
        }
       
        // DOWN acts as a brake (for now)
        if(keyDown(KEY_DOWN))
        {
            f_x_displacement = f_x_displacement * f_drag;
            f_y_displacement = f_y_displacement * f_drag;
        }

        // vdraw . . .
        while (REG_VCOUNT < 160);

        // update x and y coordinates for background
        f_x = f_x + f_x_displacement;
        f_y = f_y - f_y_displacement; // inverted :)
        REG_BG0HOFS = f_x;
        REG_BG0VOFS = f_y;
        REG_BG1HOFS = (f_x / 2) + 100; // 100 == offset
        REG_BG1VOFS = (f_y / 2) + 100;
        REG_BG2HOFS = f_x / 7.5;
        REG_BG2VOFS = f_y / 7.5;

        // wait for vblank . . .
        while (REG_VCOUNT >= 160);
    }

    return 0;

}


here's the compiled gba rom:
http://cemmel.com/gbadev/demos/direction_and_motion2.gba

i'm not sure i understood your formula for limiting thrust capacity, tepples. i've fooled around w/ some of my own formulas (if sqrt of x_displacement^2 + y_displacement^2 > max_velocity then thrust = 0), but again, everything seems to act flakey.

thanks again...


- chuck

#20459 - tepples - Tue May 11, 2004 6:53 pm

tethered wrote:
here's my source at this point:

Code:
    // generate SIN array
    f_angle = 0;
    for (i = 0; i < 128; i++)
    {
        f_radian = f_angle * (PI / 180);
        f_sin_array[i] = sin(f_radian);
        f_angle+=2.8125;
    }

I'd suggest precomputing this table on a PC and then storing it in the ROM so that you don't get the huge startup delay. I'd also suggest fixed-point arithmetic across the board.

Quote:
Code:
    // generate COS array
    f_angle = 0;
    for (i = 0; i < 128; i++)
    {
        f_radian = f_angle * (PI / 180);
        f_cos_array[i] = cos(f_radian);

        f_angle+=2.8125;
    }
    f_angle = 0;

I'd suggest implementing sin() in terms of cos() or vice versa to save memory.

Quote:
Code:
        // DOWN acts as a brake (for now)
        if(keyDown(KEY_DOWN))
        {
            f_x_displacement = f_x_displacement * f_drag;
            f_y_displacement = f_y_displacement * f_drag;
        }

Kinematics question: What is your frame of reference for the displacements? What significance in the game world does the origin have?

Quote:
here's the compiled gba rom:
http://cemmel.com/gbadev/demos/direction_and_motion2.gba

Tested.

Quote:
i'm not sure i understood your formula for limiting thrust capacity, tepples.

I was assuming the craft was working against air resistance. In a gas, drag is roughly proportional to velocity relative to the gas. When a craft is moving at terminal velocity, its thrust is equal to the drag. One would apply thrust every frame and drag every frame, and at terminal velocity, they would cancel out.

But in space, there is no air, so one would never apply drag. Because there is no air, there is also no still frame of reference for velocity and thus no maximum speed except as one approaches c. The down key would apply thrust in the opposite direction. And in space, a parallax starfield would never scroll nearly that fast; think of how far away stars are and how fast your craft moves. Or are those points of light supposed to represent space junk instead?

Quote:
i've fooled around w/ some of my own formulas (if sqrt of x_displacement^2 + y_displacement^2 > max_velocity then thrust = 0)

I thought of that one too, but I didn't suggest it because I had no idea what kind of handling you wanted, whether in air or in space. Anyway, a multiplication is faster than a sqrt(), so square both sides of the inequality. Instead of
if(sqrt(dx*dx + dy*dy) > max_velocity)
Do
if(dx*dx + dy*dy > max_velocity*max_velocity)

If you're working relative to air or to a surface, you might have to create a piecewise thrust curve based on velocity squared.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#20462 - tethered - Tue May 11, 2004 7:38 pm

optimization suggestions noted.

about the frame of reference, i'm not sure exactly what the correct answer to that is... what happens when you move about is the x and y coordinates of the background layers are changing, which gives the effect of a "camera" moving about a 2D plane. the ship that the player controls will be dead center on the screen at all times.

about the stars moving so fast, i suppose i'm going for more of an arcade kind of feel than a simulation, so i'm overlooking certain aspects for the sake of gameplay. that may change in the future.

regarding the formula for limiting the maximum velocity:

Code:
if (dx*dx + dy*dy >= max_velocity*max_velocity)
     thrust = 0;


what this does is it determines whether the displacement is greater than the maximum velocity, and if it is, it cuts the thrust. but, since there's no drag (we're in space), what happens is once you reach maximum velocity you lose your thrust and then lose your ability to maneuver. for example, say you reach maximum velocity, then you turn 180 degrees, and try to apply thrust in the opposite direction, you'll have no thrust to apply.

so i was thinking about possibly doing something along the lines of if (ship is at max velocity AND still accelerating) cut thrust, else turn thrust back on... but that causes its own problems. for example, if in the last iteration you cut thrust b/c you reached maximum velocity, in the next frame you wouldn't have accelerated, so it would turn thrust back on.

perhaps i've been looking at this too long and just need a break. hmph.


- chuck

#20465 - tepples - Tue May 11, 2004 8:03 pm

I'll start with some definitions:
  • The "dot product" of two vectors is the sum of the products of their corresponding components. For example, v1 dot v2 = x1*x2 + y1*y2 + z1*z2. To learn where it is useful, go to Google.
  • A "unit vector" is a vector whose length is 1.
  • A "heading vector" is a unit vector describing a direction.
Try this formula to determine when you should autodrag after applying thrust:
Code:
if(heading dot velocity > max_speed)
{
  apply drag;
}

But I still wonder about the validity of the concept of a not-even-close-to-relativistic "maximum velocity" in space, other than to limit temporal aliasing in order to keep from missing collisions from objects that just went right past each other.

Quote:
for example, if in the last iteration you cut thrust b/c you reached maximum velocity, in the next frame you wouldn't have accelerated, so it would turn thrust back on.

This is perfectly fine in practice.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#20467 - tethered - Tue May 11, 2004 8:17 pm

thanks for your advice. i'm sure its sound but i dont have the facilities to verify it at the moment. it's clear that i'm gonna have to spend some alone time with vectors.

thanks!


- chuck

#20573 - LOst? - Thu May 13, 2004 8:20 am

Quote:
Anyway, a multiplication is faster than a sqrt(), so square both sides of the inequality. Instead of
if(sqrt(dx*dx + dy*dy) > max_velocity)
Do
if(dx*dx + dy*dy > max_velocity*max_velocity)


tepples, this is one good way for me to get rid of calling sqrt(). Thanks for the info. No the question is, is this method better when programming on a PC? Is it faster to do multiplication instead of sqrt() on a PIII or P4?

#20577 - Cearn - Thu May 13, 2004 11:48 am

LOst? wrote:
No the question is, is this method better when programming on a PC? Is it faster to do multiplication instead of sqrt() on a PIII or P4?

Since multiplication is one of the most basic arithmatic operatorsand sqrt a relatively complicated procedure, it should always be faster. On any system. That goes for all the routines in math.h, like sin, cos, tan, atan, ln, exp, etc, etc.

#20585 - poslundc - Thu May 13, 2004 2:13 pm

Cearn wrote:
LOst? wrote:
No the question is, is this method better when programming on a PC? Is it faster to do multiplication instead of sqrt() on a PIII or P4?

Since multiplication is one of the most basic arithmatic operatorsand sqrt a relatively complicated procedure, it should always be faster. On any system. That goes for all the routines in math.h, like sin, cos, tan, atan, ln, exp, etc, etc.


I would add, however, that such a mathematical optimization - while extremely important on the GBA - may be premature for a PC game, since there are very few 16 MHz PCs still floating around and most have sophisticated math coprocessors that make square-root processing much quicker.

I would be inclined to leave the sqrt() in the game for sake of clarity of the code and the mathematical basis until such time as it became necessary to optimize it out. YMMV.

Dan.

#20816 - Cearn - Tue May 18, 2004 8:52 am

poslundc wrote:
... sophisticated math coprocessors that make square-root processing much quicker

True, but quicker than a single multiplication? I mean, there has to be some sort of interpolation to find the correct value, right?

#20827 - poslundc - Tue May 18, 2004 2:27 pm

I'm out of touch with what newer systems are capable of, but keep in mind that even if the instructions are slower they can often be executed in parallel with the main CPU, so you can increase your throughput considerably.

But this is not intended as an argument in favour of sqrt() over multiplication, only to show that what was a necessary design-level optimization on less powerful computers has been made far less critical on modern systems.

Put another way: I am opposed to premature optimization. Ideally, I would leave my distance-comparison algorithm as a comparison of two actual distances, rather than the squared distances, just for the sake of clarity and engine portability. In the practical world of GBA programming, however, I wouldn't allow the unnecessary sqrt() anywhere near my code; I would optimize the heck out of it. If I was programming for a P6 or whatever, though, I would probably leave it in until it became necessary to remove it for the sake of optimization.

Dan.

#20850 - Miked0801 - Tue May 18, 2004 6:38 pm

Agreed - sqrt() is evil as are most inverse trig functions which is why vector math is so cool compared against geometric math :)