[ODE] ode networking code snippet

Anselm Hook anselm at hook.org
Mon Nov 18 12:06:02 2002


Here's a small hack to network a couple of ODE cars together.  I had a
couple of hours and I was thinking about a comment somebody asked earlier
- so I threw this together...  I'll probably improve it later since it is
really crude right now.

It really just hammers on objects positions and it doesn't network all of
the state of each object so it doesn't look very good - although this
could be improved.  The networking layer itself might be useful as a
test-bench for people to explore other more effacious networking
strategies.

The theory is that even if object physics state is hammered on, one will
still get the appearance of reasonably believable physical interaction
simply because of the way each machine will simulate forward the whole
system.  But in practice since not all state is networked properly, the
first pass at least looks pretty poor - however I do think the theory is
still sound.

If this theory doesn't pan out then (if I have time) I may modify the code
to try out a variety of other approaches.  For example the simplest
approach is simply to integrate everything on the server.

To build it replace the test_buggy.cpp code with the below, and compile
ODE as usual under cygwin.  It doesn't build under msvc because I haven't
bothered fully implementing the berkeley sockets for msvc.

To run a server type:
   test_buggy

To run a client type:
   test_buggy be a client

It networks on 127.0.0.1:8000 so you can run multiple instances on one
machine to fiddle with it...

BTW I *really* like the geomgroups stuff now...  way better!

Have fun!

 - a

/*

"bangup" - a small testbed for ode networking.

The code is broken up into a number of pieces,

	1) NetSocket - a network abstraction layer (runs under cygwin only
- not ported to windows)
	2) SimObject - a generic type of simulation object
	3) Dynamics  - the basic ODE dynamics (world, space, ground )
	4) Car		 - a car
	5) Main		 - handles network traffic and overall event stuff

In general the networking scheme here is:

	1) There is a server and a number of clients.
	2) A server is similar to a client in that it also has a window
and a pov
	3) Each machine is locally authoritative for some objects
	4) Other objects on a machine are considered to be driven by some
remote authority
	5) In the tests so far the clients are authoritative for the local
participants pov.
	6) In the tests so far the server is authoritative for its pov
	7) The clients and server echo to each other any state that they
are authoritative for.
	8) The server additionally re-echoes out all state so that other
clients can be advised of changes.
	9) Rather than publishing a database on new client connections;
clients dynamically create missing objects.

This is extremely crude... and one will see that it does not behave well.
Not much state is networked so things
tend to jump around and misbehave terribly.  It may improve over time.

*/


/**************************************************************/
// network stuff
/**************************************************************/

//#if defined( WIN32 ) || defined( __CYGWIN32__ )
//#include <winsock.h>
//#else
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define _USE_BSD
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/fcntl.h>
#include <errno.h>
#include <sys/signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <stdarg.h>
typedef int SOCKET;
//#endif

#include <errno.h>

class NetSocket {
public:
	struct sockaddr_in  sa;
	struct hostent *hp;
	SOCKET sock;
	FILE* sockin;
	FILE* sockout;
	NetSocket* next;
	#define MAXHOSTNAME 64
	char	myname[MAXHOSTNAME+1];
	int isserver;
	int key;

	void error_fail(char* str,...) { puts(str); }

	void init() {
		isserver = 0;
		sock = 0;
		next = 0;
		sockin = 0;
		sockout = 0;
		myname[0] = 0;
		key = 0;
	}

	NetSocket() {
		init();
	}

	NetSocket(SOCKET _s) {
		init();
		attach(_s);
	}

	~NetSocket() {
		disconnect();
		// xxx remove from list
	}

	void attach(SOCKET _s) {
		sock = _s;
		int opts = fcntl(sock,F_GETFL);
		opts = opts | O_NONBLOCK;
		fcntl(sock,F_SETFL,opts);
		sockin = fdopen(sock,"r");
		sockout = fdopen(sock,"w");
	}

	void disconnect() {
		if( sockout ) {
			fflush(sockout);
			fclose(sockout);
			sockout = 0;
		}
		if( sockin ) {
			fclose(sockin);
			sockin = 0;
		}
		if( sock ) {
			#ifdef USEWIN32
			closesocket(sock);
			#else
			close(sock);
			#endif
			sock = 0;
		}
	}

	bool serve(int portnum = 8700 ) {

		isserver = 1;

		#ifdef USEWIN32
		WSAData ws;
		if( WSAStartup(0x0101,&ws) < 0 ) {
			error_fail("TcpServer: tcp does not seem to be
supported");
			return 0;
		}
		#endif

		struct protoent *tcp_prot = getprotobyname("tcp");
		if(!tcp_prot) {
			error_fail("TcpServer: tcp does not seem to be
supported");
			return 0;
		}

		sock = socket(AF_INET,SOCK_STREAM,tcp_prot->p_proto);
		if( sock < 0 ) {
			error_fail(" can't make server socket: %s",
strerror(errno));
			return 0;
		}

		int one = 1;
		if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)
&one, sizeof(int)) < 0) {
			error_fail("failed on setsockopt(): %s",
strerror(errno));
			return 0;
		}

		gethostname(myname, MAXHOSTNAME);
		if ((hp= gethostbyname(myname)) == NULL) {
			disconnect();
			return 0;
		}

		memset(&sa, 0, sizeof(struct sockaddr_in));
		sa.sin_family= hp->h_addrtype;
		sa.sin_port= htons(portnum);
		sa.sin_family = AF_INET;
		sa.sin_addr.s_addr = htonl(INADDR_ANY);
		if( bind(sock,(struct sockaddr *)&sa,sizeof(sa)) == -1 ) {
			error_fail("server count not bind");
			disconnect();
			return 0;
		}

		#ifdef USEWIN32

WSAAsyncSelect(sock,GetForegroundWindow(),WM_USER+1,FD_ACCEPT | FD_CLOSE |
FD_READ | FD_WRITE );
		#endif

		if( listen(sock,100) < 0 ) {
			disconnect();
			return 0;
		}

		return 1;
	}

	void update() {

		if( !isserver ) {
			return;
		}

		if( !sock ) {
			return;
		}

		timeval tv; tv.tv_sec = 0; tv.tv_usec = 10;
		fd_set readmask,writemask,exceptmask;
		FD_ZERO( &readmask );
		FD_ZERO( &writemask );
		FD_ZERO( &exceptmask );
		FD_SET( sock, &readmask );
		int results = select( FD_SETSIZE, &readmask, &writemask,
&exceptmask, &tv );
		if( !results ) {
			return;
		}

		int conn;
		if( (conn = accept(sock,NULL,NULL)) < 0 ) {
			printf("failed conn\n");
			return;
		}

		printf("new conn\n");

		NetSocket* cl = new NetSocket( conn );
		cl->next = next;
		next = cl;
	}

	void client(const char* hostname = "localhost", unsigned short
portnum = 8700 ) {
		isserver = 0;
		if ((hp= gethostbyname(hostname)) == NULL) {
			return;
		}
		printf("got localhost\n");
		memset(&sa, 0, sizeof(struct sockaddr_in));
		memcpy((char *)&sa.sin_addr,hp->h_addr,hp->h_length);
		sa.sin_family= hp->h_addrtype;
		sa.sin_port= htons((u_short)portnum);
		if ((sock= socket(hp->h_addrtype,SOCK_STREAM,0)) < 0) {
			return;
		}
		if (connect(sock,(struct sockaddr *)&sa,sizeof sa) < 0) {
			disconnect();
			return;
		}
		attach( sock );
		printf("conned %x,%x,%x\n",sock,(int)sockin,(int)sockout);
	}

	void publish(char* str) {
		if( sockout ) {
			fputs(str,sockout);
			fflush(sockout);
		}
	}
};

#if 0
/*
	// some inactivated code - idea here was to run 2 sims, one at
present moment, and one in past that is synced.
	// but i am concerned that ode is not deterministic between
machines; there might be strange rounding errors.

		// non deterministic input event structure
		class ndi_event {
		public:
			struct ndi_event* next;
			struct ndi_event* prev;
			time_t timenow;
			int key;
			int cmd;
			ndi_event( int key, int cmd) {
				next = prev = 0;
				time(&timenow);
				this->key = key;
				this->cmd = cmd;
			}
		};

		// data structures
		static ndi_event* ndi_head = 0;
		static ndi_event* ndi_tail = 0;

		// insert event into temporally sorted queue
		void ndi_hold_event(ndi_event* event) {
			event->next = event->prev = 0;
			for( ndi_event* cursor = ndi_tail ; cursor ;
cursor = cursor->prev ) {
				if( cursor->time < event->time ) {
					event->next = cursor->next;
					event->prev = cursor;
					if( cursor->next )
cursor->next->prev = event;
					cursor->next = event;
					return;
				}
			}
			event->next = ndi_head;
			ndi_head = event;
		}

		void system_command( int key, int cmd );

		// do all events up to the current time
		void ndi_advance(int current_time) {
			for( ndi_event* cursor = ndi_head ; cursor ; ) {
				if( cursor->time > current_time ) {
					return;
				}
				ndi_head = cursor->next;
				system_command(cursor->key, cursor->cmd );
				cursor = cursor->next
				free cursor->prev;
			}
		}

		void network_publish_event(int key, int cmd) {
		//	ndi_hold_event( new ndi_event(key,cmd) );

			// - send over net now also - except maybe updates

			// for updates we want to do this general
bookkeeping
				// accept new pending connections and fire
off a copy of my objects once only as well as pending state
				// get current time
				// ndi_advance( to current time - 1 );
				// and do a local command update too... or
heart beated
		}
*/

#endif



/**********************************************************************/
// game stuff
/**********************************************************************/

#ifdef USE_WIN32
#define __USE_W32_SOCKETS 1
#include <windows.h>
#include <timer.h>
#ifdef MSVC
#pragma warning(disable:4244 4305)  // for VC++, no precision loss
complaints
#endif
#endif

#include <ode/ode.h>
#include <drawstuff/drawstuff.h>

// select correct drawing functions

#ifdef dDOUBLE
#define dsDrawBox dsDrawBoxD
#define dsDrawSphere dsDrawSphereD
#define dsDrawCylinder dsDrawCylinderD
#define dsDrawCappedCylinder dsDrawCappedCylinderD
#endif


/**********************************************************************/
// simobject - game object management
/**********************************************************************/

class SimObject {
public:

	SimObject* next;
	int key;
	static class SimObject* objects;

	SimObject() {
		next = objects; objects = this;
	}
	virtual ~SimObject() {
		for(SimObject** obj = &objects; *obj; obj =
&((*obj)->next)) {
			if( *obj == this ) {
				*obj = (*obj)->next;
				break;
			}
		}
	}
	virtual void draw() {}
	virtual void update() {}
	virtual void command(char *cmd) {}
};

SimObject* SimObject::objects = 0;

/**********************************************************************/
// a dynamics object
/**********************************************************************/

class Dynamics {
public:
	dWorldID world;
	dSpaceID space;
	dGeomID ground;
	dJointGroupID contactgroup;
	Dynamics* next;

	// this is called by dSpaceCollide when two objects in space are
potentially colliding.

	void nearCallback2 (void *data, dGeomID o1, dGeomID o2) {
		int i,n;
		const int N = 10;
		dContact contact[N];
		n = dCollide (o1,o2,N,&contact[0].geom,sizeof(dContact));
		if (n > 0) {
			for (i=0; i<n; i++) {
			contact[i].surface.mode = dContactSlip1 |
dContactSlip2 |

dContactSoftERP | dContactSoftCFM | dContactApprox1;
			contact[i].surface.mu = dInfinity;
			contact[i].surface.slip1 = 0.1;
			contact[i].surface.slip2 = 0.1;
			contact[i].surface.soft_erp = 0.5;
			contact[i].surface.soft_cfm = 0.3;
			dJointID c = dJointCreateContact
(world,contactgroup,&contact[i]);
			dJointAttach
(c,dGeomGetBody(contact[i].geom.g1),dGeomGetBody(contact[i].geom.g2));
			}
		}
	}

	static void nearCallback(void* data, dGeomID o1, dGeomID o2) {
		((Dynamics*)data)->nearCallback2(0,o1,o2);
	}

	Dynamics() {
		world = dWorldCreate();
		space = dHashSpaceCreate();
		contactgroup = dJointGroupCreate (0);
		dWorldSetGravity (world,0,0,-0.5);
		ground = dCreatePlane (space,0,0,1,0);
	}

	void draw() {}

	void update() {
		dSpaceCollide (space,this,&nearCallback);
		dWorldStep (world,0.05);
		dJointGroupEmpty (contactgroup);
	}

	void command(char* cmd) {}

	~Dynamics() {
		dJointGroupDestroy (contactgroup);
		dSpaceDestroy (space);
		dWorldDestroy (world);
	}
};

/**********************************************************************/
// a car
/**********************************************************************/

class Car: public SimObject {
public:
	// some constants

	#define LENGTH 0.7	// chassis length
	#define WIDTH 0.5		// chassis width
	#define HEIGHT 0.2	// chassis height
	#define RADIUS 0.18	// wheel radius
	#define STARTZ 0.5	// starting height of chassis
	#define CMASS 1		// chassis mass
	#define WMASS 0.2		// wheel mass

	// dynamics and collision objects (chassis, 3 wheels )

	dBodyID body[4];
	dJointID joint[3];	// joint[0] is the front wheel
	dGeomID geom_group;
	dGeomID box[1];
	dGeomID sphere[3];
	double speed,steer;
	Dynamics* parent;

	Car(Dynamics* parent, dReal x, dReal y, dReal z);
	virtual ~Car();
	virtual void update();
	virtual void draw();
	virtual void command(char *);
};

Car::Car(Dynamics* parent, dReal x, dReal y, dReal z): SimObject() {

	this->parent = parent;

  int i;
  dMass m;

  // chassis body
  body[0] = dBodyCreate (parent->world);
  dBodySetPosition (body[0],x+0,y+0,z+STARTZ);
  dMassSetBox (&m,1,LENGTH,WIDTH,HEIGHT);
  dMassAdjust (&m,CMASS);
  dBodySetMass (body[0],&m);

  box[0] = dCreateBox (0,LENGTH,WIDTH,HEIGHT);
  dGeomSetBody (box[0],body[0]);
  // wheel bodies
  for (i=1; i<=3; i++) {
    body[i] = dBodyCreate ( parent->world);
    dQuaternion q;
    dQFromAxisAndAngle (q,1,0,0,M_PI*0.5);
    dBodySetQuaternion (body[i],q);
    dMassSetSphere (&m,1,RADIUS);
    dMassAdjust (&m,WMASS);
    dBodySetMass (body[i],&m);
    sphere[i-1] = dCreateSphere (0,RADIUS);
    dGeomSetBody (sphere[i-1],body[i]);
  }
  dBodySetPosition (body[1],x+ 0.5*LENGTH,y+ 0,z + STARTZ-HEIGHT*0.5);
  dBodySetPosition (body[2],x+ -0.5*LENGTH,y+ WIDTH*0.5,z +
STARTZ-HEIGHT*0.5);
  dBodySetPosition (body[3],x+ -0.5*LENGTH,y+ -WIDTH*0.5,z +
STARTZ-HEIGHT*0.5);

  // front and back wheel hinges
  for (i=0; i<3; i++) {
    joint[i] = dJointCreateHinge2 (parent->world,0);
    dJointAttach (joint[i],body[0],body[i+1]);
    const dReal *a = dBodyGetPosition (body[i+1]);
    dJointSetHinge2Anchor (joint[i],a[0],a[1],a[2]);
    dJointSetHinge2Axis1 (joint[i],0,0,1);
    dJointSetHinge2Axis2 (joint[i],0,1,0);
  }

  // set joint suspension
  for (i=0; i<3; i++) {
    dJointSetHinge2Param (joint[i],dParamSuspensionERP,0.4);
    dJointSetHinge2Param (joint[i],dParamSuspensionCFM,0.8);
  }

  // lock back wheels along the steering axis
  for (i=1; i<3; i++) {
    // set stops to make sure wheels always stay in alignment
    dJointSetHinge2Param (joint[i],dParamLoStop,0);
    dJointSetHinge2Param (joint[i],dParamHiStop,0);
    // the following alternative method is no good as the wheels may get
out
    // of alignment:
    //   dJointSetHinge2Param (joint[i],dParamVel,0);
    //   dJointSetHinge2Param (joint[i],dParamFMax,dInfinity);
  }

  // create geometry group and add it to the space
  geom_group = dCreateGeomGroup ( parent->space );
  dGeomGroupAdd (geom_group,box[0]);
  dGeomGroupAdd (geom_group,sphere[0]);
  dGeomGroupAdd (geom_group,sphere[1]);
  dGeomGroupAdd (geom_group,sphere[2]);

  speed = 0;
  steer = 0;
}

Car::~Car() {
	// destroy body xxx
	dGeomDestroy (box[0]);
	dGeomDestroy (sphere[0]);
	dGeomDestroy (sphere[1]);
	dGeomDestroy (sphere[2]);
}

void Car::update() {

    // motor
    dJointSetHinge2Param (joint[0],dParamVel2,-speed);
    dJointSetHinge2Param (joint[0],dParamFMax2,0.1);

    // steering
    dReal v = steer - dJointGetHinge2Angle1 (joint[0]);
    if (v > 0.1) v = 0.1;
    if (v < -0.1) v = -0.1;
    v *= 10.0;
    dJointSetHinge2Param (joint[0],dParamVel,v);
    dJointSetHinge2Param (joint[0],dParamFMax,0.2);
    dJointSetHinge2Param (joint[0],dParamLoStop,-0.75);
    dJointSetHinge2Param (joint[0],dParamHiStop,0.75);
    dJointSetHinge2Param (joint[0],dParamFudgeFactor,0.1);
}

void Car::draw() {
  dsSetColor (0,1,1);
  dsSetTexture (DS_WOOD);
  dReal sides[3] = {LENGTH,WIDTH,HEIGHT};
  dsDrawBox (dBodyGetPosition(body[0]),dBodyGetRotation(body[0]),sides);
  dsSetColor (1,1,1);
  int i;
  for (i=1; i<=3; i++) dsDrawCylinder
(dBodyGetPosition(body[i]),dBodyGetRotation(body[i]),0.02f,RADIUS);
}

void Car::command(char* command) {
	int cmd;
	float x,y,z;
	sscanf(command,"%d %f %f %f",&cmd,&x,&y,&z);
	switch( cmd ) {
	  case 'a': case 'A':
	    speed += 0.3;
	    break;
	  case 'z': case 'Z':
	    speed -= 0.3;
	    break;
	  case ',':
	    steer -= 0.5;
	    break;
	  case '.':
	    steer += 0.5;
	    break;
	  case ' ':
	    speed = 0.0;
	    steer = 0.0;
	default:
		break;
	}
}

/**************************************************************************************/
// main
/**************************************************************************************/

static NetSocket network;
static Dynamics* dynamics;
static int local_sim_key_code = 0x00010000;
static int remote_machine_code = 0x00010000;

static void network_command(NetSocket* s,char* buffer) {
	if(!strncmp(buffer,"login",5)) {
		// remote has logged into me; accept their login
		printf("got login\n");
		remote_machine_code += 0x00010000;
		if( s ) {
			fprintf(s->sockout,"id %x\n",remote_machine_code);
			// tell client to make an obj
			fprintf(s->sockout,"new %x %d %f %f
%f\n",remote_machine_code + 1, 0, 0.0f,0.0f,0.0f);
			fflush(s->sockout);
			s->key = remote_machine_code;
		}
	}
	else if(!strncmp(buffer,"id",2)) {
		// remote sent us our own unique id
		sscanf(buffer+2,"%x",&local_sim_key_code);
		printf("local is is now %x\n",local_sim_key_code);
		remote_machine_code = local_sim_key_code;
	}
	else if(!strncmp(buffer,"new",3)) {
		// remote has asked us to make something at location xyz
		int id,type;
		float x,y,z;
		sscanf(buffer+3,"%x %d %f %f %f",&id,&type,&x,&y,&z);
		printf("got new request:
0x%x,%d,%f,%f,%f\n",id,type,x,y,z);
		Car* car = new Car(dynamics,x,y,z);
		car->key = id;
	}
	else if(!strncmp(buffer,"update",3)) {
		int key;
		float x,y,z;
		float rotf[12];
		double rot[12];
		sscanf(buffer+6,"%x %f %f %f\n",&key,&x,&y,&z);
		sscanf(buffer+6,"%x %f %f %f %f %f %f %f %f %f %f %f %f %f
%f %f\n",
			&key,&x,&y,&z,
			&rotf[0],&rotf[1],&rotf[2],&rotf[3],
			&rotf[4],&rotf[5],&rotf[6],&rotf[7],
			&rotf[8],&rotf[9],&rotf[10],&rotf[11]
			);
		rot[0] = rotf[0];		rot[1] = rotf[1];
rot[2] = rotf[2];		rot[3] = rotf[3];
		rot[4] = rotf[4];		rot[5] = rotf[5];
rot[6] = rotf[6];		rot[7] = rotf[7];
		rot[8] = rotf[8];		rot[9] = rotf[9];
rot[10] = rotf[10];		rot[11] = rotf[11];
		for(SimObject* obj = SimObject::objects; obj; obj =
obj->next ) {
			if( obj->key == key ) {
				Car* car = (Car*)obj;
				dBodySetPosition( car->body[0],x,y,z);
				dBodySetRotation( car->body[0], rot );
				return;
			}
		}
		// if not found then add
		Car* car = new Car(dynamics,x,y,z);
		car->key = key;
	}
}

static void network_input_events() {

	// handle incoming traffic from any port
	for(NetSocket* s = &network; s ; s = s->next ) {
		if(! s->sockin ) continue;
		char buffer[2048];
		while( fgets(buffer,2040,s->sockin) ) {
			network_command(s,buffer);
		}
	}
}

static void network_output_events()  {
	// for all machines that have observers here, tell them about all
objects we are responsible for
	static int counter = 0;
	if( counter++ < 16 ) {
		return;
	}
	counter = 0;
	for(NetSocket* s = &network; s ; s = s->next ) {
		if( !s->sockout ) continue;
		for(SimObject* obj = SimObject::objects; obj; obj =
obj->next ) {
			if(network.sockout) {
				// if we are a client then emit
information about our objects only - else emit all
				if( (obj->key & 0xffff0000) !=
(local_sim_key_code & 0xffff0000) ) {
					continue;
				}
			} else {
				// if we are a server - emit all objects
to all machines except real owners
				if( (obj->key & 0xffff0000) == ( s->key &
0xffff0000) ) {
					continue;
				}
			}
			Car* car = (Car*)obj;
			const dReal* pos = dBodyGetPosition( car->body[0]
);
			const dReal* rot = dBodyGetRotation( car->body[0]
);
			float x = pos[0]; float y = pos[1]; float z =
pos[2];
			fprintf(s->sockout,"update %x %f %f %f %f %f %f %f
%f %f %f %f %f %f %f %f\n",
				obj->key,x,y,z,

(float)rot[0],(float)rot[1],(float)rot[2],(float)rot[3],

(float)rot[4],(float)rot[5],(float)rot[6],(float)rot[7],

(float)rot[8],(float)rot[9],(float)rot[10],(float)rot[11]
				);
			fflush(s->sockout);
		}
	}
}

static void mainLoop (int pause) {

	// for server only - look for new socket connections
	network.update();

	// for servers and clients - deal with any remote commands
	network_input_events();

	// for servers and clients - emit anything anybody else might be
interested in
	network_output_events();

	// update...  could be done before outputting state
	for(SimObject* obj = SimObject::objects; obj; obj = obj->next ) {
		obj->update();
	}

	if( dynamics ) {
		dynamics->update();
	}

	// redraw... best done after updating state
	for(SimObject* obj = SimObject::objects; obj; obj = obj->next ) {
		obj->draw();
	}
}

void key_events(int cmd ) {
	switch(cmd) {
	default:
		int key = ( local_sim_key_code & 0xffff0000 ) + 1;
		for(SimObject* obj = SimObject::objects; obj; obj =
obj->next ) {
			printf("%x %x\n",obj->key,key);
			if( obj->key == key ) {
				char buffer[512];
				sprintf(buffer,"%d 0 0 0\n",cmd);
				obj->command(buffer);
				return;
			}
		}
	}
}

void start() {
  printf ("Press:\t'a' to increase speed.\n"
	  "\t'z' to decrease speed.\n"
	  "\t',' to steer left.\n"
	  "\t'.' to steer right.\n"
	  "\t' ' to reset speed and steering.\n");
	// move camera somewhere useful
	{
		//const dReal* pos = dBodyGetPosition(body[0]);
		float pos[3] = { 3,-3,2 };
		// xxx move viewpoint behind car - xxx wrong - do
dynamically
		static float xyz[3] = {0.8317f,-0.9817f,0.8000f};
		static float hpr[3] = {121.0000f,-27.5000f,0.0000f};
		xyz[0] = pos[0] + 0.8317f;
		xyz[1] = pos[1] - 0.9817f;
		xyz[2] = pos[2] + 0.8000f;
		dsSetViewpoint (xyz,hpr);
	}

}

int main (int argc, char **argv) {

	dynamics = new Dynamics();

	if( argc < 2 ) {
		// be a server - server will always start up one dynamics
engine and always add one car to kickstart itself.
		printf("server\n");
		network.serve();
		network_command(0,"id 0x00010000\n");
// server id
		network_command(0,"new 0x00010001 0 2.0 0 0\n");	//
server car location
	}
	else {
		// be a client - client explicitly logs in and waits for
reply with an id before doing anything
		network.client();
		if( network.sockout ) {
			network.publish("login joe\n");
			printf("client: network up - logging in\n");
		} else {
			printf("client: no network :-(\n");
		}
	}

	// setup pointers to drawstuff callback functions and run forever
	dsFunctions fn;
	fn.version = DS_VERSION;
	fn.start = &start;
	fn.step = &mainLoop;
	fn.command = &key_events;
	fn.stop = 0;
	fn.path_to_textures = "../../drawstuff/textures";
	dsSimulationLoop (0,0,352,288,&fn);

	// exit...
	network.disconnect();

}