Issues with Vectors in Zero-G Spaceflight

C6cb6c90c411f01492653729e7548a50
0
Flamesilver 101 Feb 11, 2011 at 14:15

I’m trying to prototype a Star Control / Space War type zero-G flying game using Visual C++ 2008 and Dark GDK. My physics code isn’t quite working.

Everything seems fine when I’m facing Right (1st and 4th Quadrant, I think), but then when I face left weird stuff happens…

Go easy on my guys - I haven’t written code in 7 years and it’s my first 3D project ever.

#include "DarkGDK.h"
#include <math.h>

// pi and conversion function
#define PI 3.14159265358979323846
#define DEG2RAD(DEG) ((DEG)*((PI)/(180.0)))
#define RAD2DEG(RAD) (RAD*180/PI)


class vect2 {

    // Generic Vector Class

public:

    float x, y; // seperate component coordinates

    // ctor
    vect2( float in_x = 0, float in_y = 0 ) {
        x = in_x;
        y = in_y;
    }

    float getAngle() {  return (float) RAD2DEG(atan( y / x )); }    // returns angle of vector based on x, y
    float getLength() { return (float) sqrt( x*x + y*y ); }     // returns magnitude

    // set x, y based on angle , magnitude
    void setXYFromAM( float a, float m ) { 
        x = m * cos( DEG2RAD(a) );
        y = m * sin( DEG2RAD(a) );
    }

    vect2 operator+ ( vect2 v ) { return vect2( x + v.x, y + v.y ); }   // add 2 vectors

};



class Ship {

private:

    int id;         // id of the ship and form object

    float x, y, z;  // position in space
    float dir;      // direction facing
    float aim;      // direction aiming
    float speed;        // current speed

public:

    Ship (int i);               // constructor
    void reset();

    float getX() { return x; }  // accessors
    float getY() { return y; }
    float getZ() { return z; }
    
    float getDir() { return dir; }
    int getid() { return id; }

    float setX( float i ) { x = i; }    // set functions
    float setY( float i ) { y = i; }
    float setZ( float i ) { z = i; }
    float setDir( float i ) { dir = i; }    // set dir

    void thrust( float i );                     // thrust forward or backward
    void turn ( float i ) { aim = aim + i; }    // change facing direction of ship
    
    void update();                              // updates position of the ship based on dir, speed

};


Ship::Ship( int i ) {

    id = i;             // get ship id
    reset();            // call the reset function to init
}

void Ship::reset() {

    x = y = z = 0;      // position to be 0,0,0
    aim = 90;           // faces 90
    dir = 90;           // goes 90
    speed = 0;          // not moving

}


void Ship::thrust(float i) {

    // add a thrust vector based on aim, thrust to the current movement vector
    // declare vT, vM

    vect2 vT, vM, result;                       // declare Thrust, Cur Movement, and Result (new move) vector
    
    vT.setXYFromAM( aim, i );       // initialize Thrust vector from aim, i
    vM.setXYFromAM( dir, speed );   // initialize Move vector from dir, speed

    result = vT + vM;                           // result vector = adding thrust to current movement

    dir = result.getAngle();        // calculate new direction based on added vector (rounded)
    speed = result.getLength();     // calculate new speed
    
}


void Ship::update() {

    vect2 pos( x, y );      // make pos vector based on current x, y
    vect2 v;                // the movement vector

    v.setXYFromAM( dir, speed );

    pos = pos + v;

    x = pos.x;
    y = pos.y;
    

    dbRotateObject(id, 0, 0, aim);


    // update the position
    dbPositionObject(id, x, y, z);

}


// the main entry point for the application is this function
void DarkGDK ( void )
{

    dbSyncOn   ( );         // sync
    dbSyncRate ( 60 );

    dbRandomize ( dbTimer ( ) ); // set our random seed to a value from the timer

    Ship PlayerShip(1);     // instance Player

    // Load the ship and place it at 0,0,0
    dbLoadObject("Ship.x", PlayerShip.getid());
    dbPositionObject(PlayerShip.getid(), PlayerShip.getX(), PlayerShip.getY(), PlayerShip.getZ());

    // Position the Camera
    dbPositionCamera ( 0, 0, -2500 );

    // GDK Loop

    while ( LoopGDK() ) {

        // display some text on screen
        dbText ( 0, 0, "Some Text" );

        // add thrust to the ship
        if ( dbUpKey ( ) )
            PlayerShip.thrust( 3 );

        // subtract thrust to the ship
        if ( dbDownKey ( ) )
            PlayerShip.thrust( -3 );

        // turn ship left
        if ( dbLeftKey ( ) )
            PlayerShip.turn( 11.25 );

        // turn ship right
        if ( dbRightKey ( ) )
            PlayerShip.turn( -11.25 );

        // reset ship
        if ( dbSpaceKey ( ) )
            PlayerShip.reset();

        // Update Ship Position
        PlayerShip.update();

        // Perform Sync
        dbSync();

    }

    dbDeleteObject ( PlayerShip.getid() );  // cleanup PlayerShip

    return;
}

7 Replies

Please log in or register to post a reply.

A77e71b962cd6c7c3b885f0488452f1f
0
tobeythorn 101 Feb 11, 2011 at 14:56

in getAngle, what happens when x = 0?

C6cb6c90c411f01492653729e7548a50
0
Flamesilver 101 Feb 11, 2011 at 18:08

Wow… I’m surprised it’s not crashing with divide by zero…

I’ll start with that little fix, but I’d imagine the problem is a bit deeper than that.

C6cb6c90c411f01492653729e7548a50
0
Flamesilver 101 Feb 14, 2011 at 05:08

Hey Everyone! Problem SOLVED! Thanks you for your efforts.

I didn’t really find out exactly what the problem was, but I cleaned up my code and rethought some of my design decisions. I ended up storing a movement vector for the inertia of the ship as movementVector(x, y), did the thrust adding via another vector, and skipped out on the nasty conversion back to angle,magnitude representations.

Here’s my code for those who care (note I even added in maximum speed accounting for outside factors accelerating the ship beyond max thrustable speed, but never being able to add more thrust to a ship moving beyond max speed)

#include "DarkGDK.h"
#include <math.h>
#include <stdio.h>


// pi and conversion function
#define PI 3.14159265358979323846
#define DEG2RAD(DEG) ((DEG)*((PI)/(180.0)))
#define RAD2DEG(RAD) (RAD*180.0/PI)


class vect2 {

    // Generic Vector Class

public:

    float x, y; // seperate component coordinates

    // ctor
    vect2( float in_x = 0, float in_y = 0 ) {
        x = in_x;
        y = in_y;
    }

    void setXY( float i, float j ) { x = i; y = j; }    // set (x, y)
    
    
    void normalize() {
        
        // normalize to unit vector (direction only)
        // if length > 0  you divide each side by length

        float l = getLength();  // get the current length
        
        if ( l != 0 ) {
            x /= l;
            y /= l;
        }
    }


    float getAngle() {  return (float) RAD2DEG(atan( y / x )); }    // returns angle of vector based on x, y
    float getLength() { return (float) sqrt( ((x*x) + (y*y)) ); }       // returns magnitude

    // set x, y based on angle , magnitude
    void setXYFromAM( float a, float m ) { 
        x = m * cos( DEG2RAD(a) );
        y = m * sin( DEG2RAD(a) );
    }

    void scaleTo ( float s ) {

        normalize();
        x *= s;
        y *= s;
    
    }

    vect2 operator+ ( vect2 v ) { return vect2( x + v.x, y + v.y ); }   // add 2 vectors
    vect2 operator+= ( vect2 v ) { return vect2( x += v.x, y += v.y ); }    // add 2 vectors

};



class Ship {

private:

    int id;         // id of the ship and form object

    float x, y, z;  // position in space
    vect2 movementVector;   // current movement vector due to inertia (used to update ship position)

    float aim;              // direction aiming

    float thrustspeed;      // thrust speed
    float turnspeed;        // turning speed
    float maxspeed;         // maximum speed

public:

    Ship (int i);               // constructor
    void reset();

    float getX() { return x; }  // accessors
    float getY() { return y; }
    float getZ() { return z; }
    
    int getid() { return id; }

    void setX( float i ) { x = i; } // set functions
    void setY( float i ) { y = i; }
    void setZ( float i ) { z = i; }
    void setXYZ( float i, float j, float k ) { x = i; y = j; z = k; }
    
    void setTurnspeed( float i ) { turnspeed = i; } // set turnspeed, thrustspeed, and maximum speed
    void setThrustspeed( float i ) { thrustspeed = i; }
    void setMaxspeed( float i ) { maxspeed = i; }

    void thrust( float i );                     // thrust forward or backward
    void turn ( float i ) { aim = aim + i*turnspeed; }  // change facing direction of ship
    
    void update();                              // updates position of the ship based on dir, speed

};


Ship::Ship( int i ) {

    id = i;             // get ship id
    reset();            // call the reset function to init
}

void Ship::reset() {

    x = y = z = 0;                  // position to be 0,0,0
    aim = 0;                        // faces 90
    movementVector.setXY(0, 0);     // no movement

}


void Ship::thrust(float i) {

    // add a thrust vector based on aim, thrust to the current movement vector
    // declare vT, vM

    vect2 vT;               // declare Thrust, Cur Movement, and Result (new move) vector
    float l1, l2;           // length temp variables
    
    vT.setXYFromAM( aim, i*thrustspeed );       // initialize Thrust vector from aim, i
    
    l1 = movementVector.getLength();            // get original speed it's moving at
    
    movementVector += vT;                       // result vector = adding thrust to current movement
    
    l2 = movementVector.getLength();

    // speed limiting code
    if ( ( l2 > l1 ) && ( l1 > maxspeed ) ) {

        // if it was already going faster than maxspeed, make it go at original speed with current vector

        movementVector.scaleTo( l1 - thrustspeed );

    }

}


void Ship::update() {


    x += movementVector.x;
    y += movementVector.y;

    dbRotateObject(id, 0, 0, aim);


    // update the position
    dbPositionObject(id, x, y, z);

}


// the main entry point for the application is this function
void DarkGDK ( void )
{

    dbSyncOn   ( );         // sync
    dbSyncRate ( 60 );

    dbRandomize ( dbTimer ( ) ); // set our random seed to a value from the timer

    // Create Playership and Set Variables
    Ship PlayerShip(1);     // instance Player
    PlayerShip.setThrustspeed(.5);      // set Thrust applied per cycle
    PlayerShip.setTurnspeed(3);         // set turn applied per cycle
    PlayerShip.setMaxspeed(20);         // set turn applied per cycle
    



    // Load the ship and place it at 0,0,0
    dbLoadObject("Ship.x", PlayerShip.getid());
    dbPositionObject(PlayerShip.getid(), PlayerShip.getX(), PlayerShip.getY(), PlayerShip.getZ());

    // Position the Camera
    dbPositionCamera ( 0, 0, -2500 );


    // GDK Loop

    while ( LoopGDK() ) {


        // display some text on screen
        dbText ( 0, 0, "Sigh.... " );

        // add thrust to the ship
        if ( dbUpKey ( ) )
            PlayerShip.thrust( 1 );

        // subtract thrust to the ship
        if ( dbDownKey ( ) )
            PlayerShip.thrust( -1 );

        // turn ship left
        if ( dbLeftKey ( ) )
            PlayerShip.turn( 1 );

        // turn ship right
        if ( dbRightKey ( ) )
            PlayerShip.turn( -1 );

        // reset ship
        if ( dbSpaceKey ( ) )
            PlayerShip.reset();

        // Update Ship Position
        PlayerShip.update();

        // Perform Sync
        dbSync();

    }

    dbDeleteObject ( PlayerShip.getid() );  // cleanup PlayerShip

    return;
}
A77e71b962cd6c7c3b885f0488452f1f
0
tobeythorn 101 Feb 14, 2011 at 05:40

glad you solved your problem and cleaned up your code. I do not have fond memories of working with inverse trig functions in game programming and try to avoid them at all costs. If you do use them (and sometime you really do need them), remember you have to check for divide by zeros of your input, and also check to make sure that your input is valid (imprecision can turn an +/-1.0 into a +/-1.0001 which will fail when you take the arccos or arcsin).

6eaf0e08fe36b2c23ca096562dd7a8b7
0
__________Smile_ 101 Feb 16, 2011 at 13:58

There is two-argument inverse trigonometric function atan2(y,x) which does the same as atan(y/x) with automatic quadrant detection and without division by zero problem. I suggest to use it instead all other arc-functions (in fact, these functions defined in terms of atan2).

Also, why use degrees for angles? I suggest using small integer types with warping arithmetics. For example, use 1-byte integer and measure angles in 360/256 degree units, or 2-byte with 360/65536 units, etc… This measure eliminates overflow/precision problem with non-warping angles.

A77e71b962cd6c7c3b885f0488452f1f
0
tobeythorn 101 Feb 16, 2011 at 16:18

Smile,
Some interesting suggestions you have. Thanks man.

C6cb6c90c411f01492653729e7548a50
0
Flamesilver 101 Feb 22, 2011 at 00:34

Yeah, thanks Smile! I’m going to use that to improve my current vector classes.