Jump to content


- - - - -

Issues with Vectors in Zero-G Spaceflight


7 replies to this topic

#1 Flamesilver

    New Member

  • Members
  • PipPip
  • 28 posts

Posted 11 February 2011 - 02:15 PM

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;

}



#2 tobeythorn

    Valued Member

  • Members
  • PipPipPip
  • 189 posts

Posted 11 February 2011 - 02:56 PM

in getAngle, what happens when x = 0?

#3 Flamesilver

    New Member

  • Members
  • PipPip
  • 28 posts

Posted 11 February 2011 - 06:08 PM

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.

#4 Flamesilver

    New Member

  • Members
  • PipPip
  • 28 posts

Posted 14 February 2011 - 05:08 AM

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;
}



#5 tobeythorn

    Valued Member

  • Members
  • PipPipPip
  • 189 posts

Posted 14 February 2011 - 05:40 AM

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).

#6 }:+()___ (Smile)

    Member

  • Members
  • PipPipPip
  • 169 posts

Posted 16 February 2011 - 01:58 PM

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.
Sorry my broken english!

#7 tobeythorn

    Valued Member

  • Members
  • PipPipPip
  • 189 posts

Posted 16 February 2011 - 04:18 PM

Smile,
Some interesting suggestions you have. Thanks man.

#8 Flamesilver

    New Member

  • Members
  • PipPip
  • 28 posts

Posted 22 February 2011 - 12:34 AM

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





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users