diff --git a/NOMADVRLib/ConfigFile.cpp b/NOMADVRLib/ConfigFile.cpp
index ef19a3b6d6122d49c393f71e6a39d770850d6274..365a442dfd7fb7e3466eb6f1f6acca4029180ca8 100644
--- a/NOMADVRLib/ConfigFile.cpp
+++ b/NOMADVRLib/ConfigFile.cpp
@@ -213,6 +213,9 @@ void cleanConfig()
 		numAtoms=nullptr;
 		atoms=nullptr;
 	}
+
+	delete[] server;
+	server=nullptr;
 }
 
 void initState()
diff --git a/OpenVR/TimestepData/hellovr_opengl_main.cpp b/OpenVR/TimestepData/hellovr_opengl_main.cpp
index 8f8eeda93df915c060b4b01d98336b20633b02f3..2bc4e31e6e9184a8a8bb74637a55696f383561e6 100644
--- a/OpenVR/TimestepData/hellovr_opengl_main.cpp
+++ b/OpenVR/TimestepData/hellovr_opengl_main.cpp
@@ -42,6 +42,8 @@
 #include <string>
 #include <cstdlib>
 #include <algorithm>
+#include <mutex>
+#include <condition_variable>
 
 #include <winsock2.h>
 
@@ -84,6 +86,17 @@
 
 #define NUMPLY (TIMESTEPS * ISOS)
 
+
+typedef struct remotes {
+	int id;
+	bool valid[3];
+	float head[16];
+	vr::HmdMatrix34_t c1, c2;
+	Vector3 UserPos;
+	unsigned char colour[3];
+} remotes_t;
+
+
 class CGLRenderModel
 {
 public:
@@ -352,16 +365,33 @@ private: // OpenGL bookkeeping
 	char **myargv;
 	int currentConfig;
 
-	std::thread *tcpconn;
+	std::thread *tcpconn, *udpconn;
 	void connectTCP();
-	int sock;
+	void UDP();
+	int UDPContr(SOCKET s, char c, int device);
+	int UDPHead(SOCKET s);
+	int sock, s;
+	struct sockaddr_in serv_addr;
 	void Send(char c, int32_t value);
 	void Send(char c, bool value);
 	void SendConfigFile();
 	void SendTimestep();
 	void SendIso();
 	void SendShowAtoms();
+	void SendUserPos();
+	void SendDragDrop(Vector3 pos);  
+
+	std::vector<remotes_t> remotes;
+	int32_t identifier;
+	int FindRemote(int32_t remote);
 
+	Vector3 initDrag;
+	Uint32 msDrag;
+	char whenDrag;// whenDrag=0: start; 1: middle; 2: end; 3: not sending
+
+	char cleanup; //synch TCP and drawing thread on scene load
+	std::mutex cleanupmutex;
+	std::condition_variable cleanupcond;
 };
 
 const float CMainApplication::videospeed = 0.01f;
@@ -456,10 +486,155 @@ int CMainApplication::LoadConfigFile (const char *c)
 	return r;
 }
 
+int CMainApplication::UDPHead(SOCKET s)
+{
+	int n=0;
+	char buff[sizeof(float)*16+5];
+	if ( m_rTrackedDevicePose[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid ) {
+		buff[0]='h';
+		memcpy(buff+1, &identifier, 4);
+		memcpy (buff+5, (char*)m_mat4HMDPose.get(), sizeof(float)*16);
+		n=send(s, buff, sizeof(float)*16+5, 0);
+		if (n<0) {
+			closesocket (s);
+			s=INVALID_SOCKET;
+		}
+		}
+	return n;
+}
+
+int CMainApplication::UDPContr(SOCKET s, char c, int device)
+{
+//datagrams can be reordered, so we cannot split type and payload
+//reading a subset of the datagrams discards the rest, so we need to listen to the largest possible.
+	int n=0;
+	vr::VRControllerState_t cs;
+	vr::TrackedDevicePose_t dp;
+	char buff[sizeof(vr::HmdMatrix34_t) +5];
+	if (!m_pHMD) 
+		return -1;
+	m_pHMD->GetControllerStateWithPose( vr::TrackingUniverseStanding, device, &cs, &dp );
+	if (dp.bPoseIsValid) {
+		vr::HmdMatrix34_t mat=dp.mDeviceToAbsoluteTracking;
+		buff[0]=c;
+		memcpy(buff+1, &identifier, 4);
+		memcpy(buff+5, mat.m, sizeof(mat.m));
+		n=send(s, buff, sizeof(mat.m)+5, 0);
+		if (n<0) {
+			closesocket (s);
+		}
+	}
+	return n;
+}
+
+void CMainApplication::UDP()
+{
+	//struct hostent *he;
+	//if ( (he = gethostbyname(server) ) == nullptr ) {
+	//	eprintf ("Connect to server, could not get host name %s\n", server);
+     // return; /* error */
+	//}
+	//memset((char *) &serv_addr, 0, sizeof(serv_addr));
+	//memcpy(&serv_addr.sin_addr, he->h_addr_list[0], he->h_length);
+	//serv_addr.sin_family = AF_INET;
+	//serv_addr.sin_port = htons(port);
+	s=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
+	if (s==INVALID_SOCKET) {
+		eprintf ("udp socket creation error %d, closing\n", WSAGetLastError());
+		return;
+	}
+	if ( connect(s, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
+		eprintf ("Udp Connect to server, could not get connection %s, got error %d\n", server, WSAGetLastError());
+      return; /* error */
+	}
+
+	char buf[100];
+	while (true) {
+		int n;
+		//Sleep(2000); //rgh fixme, should be approx one frame
+		Sleep (10);
+		if (sock==INVALID_SOCKET || s==INVALID_SOCKET)
+			return;
+
+		struct sockaddr_in sin;
+		int addrlen = sizeof(sin);
+		if(getsockname(s, (struct sockaddr *)&sin, &addrlen) == 0 &&
+		    sin.sin_family == AF_INET &&
+			addrlen == sizeof(sin)) {
+				int local_port = sin.sin_port;
+		}
+		//send head and controllerpos
+		n=0;
+		if (identifier==0)
+			continue;
+		if (firstdevice!=-1 ) {
+			n=UDPContr(s, '1', firstdevice);
+			if (n<0)
+				return;
+		}
+		if (seconddevice!=-1) {
+			n=UDPContr(s, '2', seconddevice);
+			if (n<0)
+				return;
+		}
+
+		if ( UDPHead(s)<0 ) {
+			return;
+		}
+		//receive head and controllers
+		unsigned long l;
+		do {
+			ioctlsocket(s, FIONREAD, &l);
+			if (l>0) { //we have data
+				int32_t remote;
+				n=recv(s, buf, 5*4*4*sizeof(float), 0);
+				if (n<0) { //disconnected
+					eprintf ("udp disconnect\n");
+					return;
+				}
+				//Beep( 750, 300 ); //rgh fixme
+				memcpy (&remote, buf+1, 4);
+				int i=FindRemote(remote);
+				if (buf[0]=='h') {
+					memcpy ((char*)&(remotes[i].head[0]), buf+5, sizeof(float)*16);
+					remotes[i].valid[0]=true;
+				} else if (buf[0]=='1') {
+					memcpy ((char*)remotes[i].c1.m,  buf+5, sizeof(float)*12);
+					remotes[i].valid[1]=true;
+				} else if (buf[0]=='2') {
+					memcpy ((char*)remotes[i].c2.m,  buf+5, sizeof(float)*12);
+					remotes[i].valid[2]=true;
+				} else {
+					eprintf ("Unknown udp command '%c'", buf[0]);
+				}
+			
+			}
+		} while (l>0);
+	}
+}
+
+int CMainApplication::FindRemote(int32_t remote) 
+{
+	int found=-1;
+	for (int i=0;i<remotes.size();i++) {
+		if (remotes[i].id==remote) {
+			return i;
+		}
+	}
+	
+	remotes.push_back(remotes_t());
+	remotes.back().id=remote;
+	for (int i=0;i<3;i++)
+		remotes.back().valid[i]=false;
+	//for now, random, non-consistent colours. Send from server later
+	for (int i=0;i<3;i++)
+		remotes.back().colour[i]=(unsigned char)(rand()/RAND_MAX*256.f);
+	return remotes.size()-1;
+}
+
 void CMainApplication::connectTCP() 
 {
 	//https://stackoverflow.com/questions/5444197/converting-host-to-ip-by-sockaddr-in-gethostname-etc
-	struct sockaddr_in serv_addr;
 	struct hostent *he;
 	if ( (he = gethostbyname(server) ) == nullptr ) {
 		eprintf ("Connect to server, could not get host name %s\n", server);
@@ -474,20 +649,39 @@ void CMainApplication::connectTCP()
 				eprintf ("Connect to server, could not get connection %s\n", server);
       return; /* error */
 	}
+		struct sockaddr_in sin;
+		int addrlen = sizeof(sin);
+		if(getsockname(sock, (struct sockaddr *)&sin, &addrlen) == 0 &&
+		    sin.sin_family == AF_INET &&
+			addrlen == sizeof(sin)) {
+				int local_port = sin.sin_port;
+		}
 	//read state
 	int n;
 	int32_t tmp;
 	tmp=htonl (secret);
 	n = send(sock, (char*)&tmp , sizeof(tmp), 0);
-	if (n<sizeof(tmp))
+	if (n<sizeof(tmp)) {
+		closesocket(sock);
+		sock=INVALID_SOCKET;
+		return;
+	}
+	SendUserPos();
+	if (sock==INVALID_SOCKET)
 		return;
-	char what;
+	udpconn=new std::thread(&CMainApplication::UDP, this);
+
+	char what=0;
+	int found;
+	char previous=0;
 	while (true) {
+		previous=what;
 		n=recv(sock, &what, sizeof(what), 0);
 		if (n<1) {
 			eprintf ("closed socket\n");
 			return;
 		}
+		//Beep( 1750, 300 ); //rgh fixme
 		switch (what) {
 		case 't':
 			n=recv (sock, (char*)&tmp, sizeof(tmp), 0);
@@ -521,15 +715,63 @@ void CMainApplication::connectTCP()
 				return;
 			}
 			//load config file
-			if (currentConfig!=ntohl(tmp)%myargc) {
+			//force reload because hidden user may have requested it
+//			if (currentConfig!=ntohl(tmp)%myargc) 
+			{
 				currentConfig=ntohl(tmp)%myargc;
-				CleanScene();
-				LoadConfigFile(myargv[currentConfig]);
-				SetupScene();
+				//avoid race with drawing thread
+				cleanupmutex.lock();
+				cleanup=1;
+				cleanupmutex.unlock();
+				std::unique_lock<std::mutex> lk(cleanupmutex);
+				cleanupcond.wait(lk);
+
+				//OpenGL commands only in main thread
+				//CleanScene();
+				//LoadConfigFile(myargv[currentConfig]);
+				//SetupScene();
+				//cleanupmutex.lock();
+				//cleanup=0;
+				//cleanupmutex.unlock();
+			}
+			break;
+		case 'X':
+			n=recv (sock, (char*)&identifier, sizeof(tmp), 0);
+			if (n<sizeof(tmp)) {
+				eprintf ("short read at socket\n");
+				return;
+			}
+			break;
+		case 'p':
+			int32_t remote;
+			n=recv (sock, (char*)&remote, sizeof(remote), 0);
+			found=-1;
+			for (int i=0;i<remotes.size();i++) {
+				if (remotes[i].id==remote) {
+					found=i;
+					break;
+				}
+			}
+			if (found<0) {
+				remotes.push_back(remotes_t());
+				remotes.back().id=remote;
+				for (int i=0;i<3;i++)
+					remotes.back().valid[i]=false;
+				//for now, random, non-consistent colours. Send from server later
+				for (int i=0;i<3;i++)
+					remotes.back().colour[i]=(unsigned char)((float)rand()/RAND_MAX*256.f);
+				found=remotes.size()-1;
 			}
+			n=recv (sock, (char*)&(remotes[found].UserPos.x), sizeof(float), 0);
+			n=recv (sock, (char*)&(remotes[found].UserPos.y), sizeof(float), 0);
+			n=recv (sock, (char*)&(remotes[found].UserPos.z), sizeof(float), 0);
+			break;
+		case 'D': //discard remote Drag&Drop
+			char buff[6*sizeof(float)+1+2*sizeof(UINT32)];
+			n=recv (sock, buff, 6*sizeof(float)+1+2*sizeof(UINT32), 0);
 			break;
 		default:
-			eprintf ("Unknown state sent from server: %c\n", what);
+			eprintf ("Unknown state sent from server: %c %d\n", what, what);
 		}
 	}
 }
@@ -538,8 +780,8 @@ void CMainApplication::connectTCP()
 // Purpose: Constructor
 //-----------------------------------------------------------------------------
 CMainApplication::CMainApplication(int argc, char *argv[])
-	: m_pWindow(NULL)
-	, m_pContext(NULL)
+	: m_pWindow(nullptr)
+	, m_pContext(nullptr)
 	, m_nWindowWidth(1920)
 	, m_nWindowHeight(1080)
 	, m_unSceneProgramID(0)
@@ -548,8 +790,8 @@ CMainApplication::CMainApplication(int argc, char *argv[])
 	, m_unRenderModelProgramID(0)
 	, m_unAtomsProgramID(0)
 	, m_unUnitCellProgramID(0)
-	, m_pHMD(NULL)
-	, m_pRenderModels(NULL)
+	, m_pHMD(nullptr)
+	, m_pRenderModels(nullptr)
 	, m_bDebugOpenGL(false)
 	, m_bVerbose(false)
 	, m_bPerf(false)
@@ -558,10 +800,10 @@ CMainApplication::CMainApplication(int argc, char *argv[])
 	, m_glControllerVertBuffer(0)
 	, m_unControllerVAO(0)
 	, m_unLensVAO(0)
-	, m_unSceneVAO(0)
-	, m_unSceneVAOIndices(0)
-	, m_unAtomVAO(0)
-	, m_glAtomVertBuffer(0)
+	, m_unSceneVAO(nullptr)
+	, m_unSceneVAOIndices(nullptr)
+	, m_unAtomVAO(nullptr)
+	, m_glAtomVertBuffer(nullptr)
 	, m_glUnitCellVertBuffer(-1)
 	, m_glUnitCellIndexBuffer(-1)
 	, m_unUnitCellVAO(0)
@@ -585,15 +827,14 @@ CMainApplication::CMainApplication(int argc, char *argv[])
 	, currentiso(-1) // (-> ISOS, but at this point ISOS is not yet initialized)
 	, firstdevice(-1)
 	, seconddevice(-1)
-	, m_iTexture(0)
-	, axisTextures(0)
+	, m_iTexture(nullptr)
+	, axisTextures(nullptr)
 	, peelingFramebuffer(0)
 	, m_uiVertcount(0)
 	, m_glSceneVertBuffer(0)
-	//, UserPosition(Vector3(-101.0f * 0.15f*0.5f*GRID + 12.5f, -15.0f, -101.0f * 0.15f*0.5f*GRID + 101.0f * 0.15f*0.25f))
 	, UserPosition(Vector3(-userpos[0] /** 0.04f*/, -userpos[1] /** 0.04f*/, -userpos[2] /** 0.04f*/))
-	, vertdataarray(0)
-	, vertindicesarray(0)
+	, vertdataarray(nullptr)
+	, vertindicesarray(nullptr)
 	, pixels(0)
 	, framecounter(0)
 	, savetodisk(false)
@@ -602,8 +843,13 @@ CMainApplication::CMainApplication(int argc, char *argv[])
 	, myargc(argc)
 	, myargv(argv)
 	, currentConfig(1)
-	, tcpconn(0)
-	, sock(-1)
+	, tcpconn(nullptr)
+	, udpconn(nullptr)
+	, sock(INVALID_SOCKET)
+	, s(INVALID_SOCKET)
+	, identifier(0)
+	, whenDrag (3)
+	, cleanup (0)
 {
 	LoadConfigFile(argv[currentConfig]);
 	for (int j=0;j<3;j++)
@@ -763,7 +1009,7 @@ bool CMainApplication::BInit()
 	m_strDriver = GetTrackedDeviceString( m_pHMD, vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_TrackingSystemName_String );
 	m_strDisplay = GetTrackedDeviceString( m_pHMD, vr::k_unTrackedDeviceIndex_Hmd, vr::Prop_SerialNumber_String );
 
-	std::string strWindowTitle = "Geophysics OpenVR - " + m_strDriver + " " + m_strDisplay;
+	std::string strWindowTitle = "NOMAD OpenVR - " + m_strDriver + " " + m_strDisplay;
 	SDL_SetWindowTitle( m_pWindow, strWindowTitle.c_str() );
 	
 	// cube array
@@ -885,6 +1131,22 @@ if( *vaos != 0 )
 //-----------------------------------------------------------------------------
 void CMainApplication::Shutdown()
 {
+	if (tcpconn) {
+		closesocket(sock);
+		sock=INVALID_SOCKET;
+		tcpconn->join();
+		delete tcpconn;
+		tcpconn=nullptr;
+	}
+
+	if (udpconn) {
+		closesocket(s);
+		s=INVALID_SOCKET;
+		udpconn->join();
+		delete udpconn;
+		udpconn=nullptr;
+	}
+
 	if( m_pHMD )
 	{
 		vr::VR_Shutdown();
@@ -1001,41 +1263,98 @@ void CMainApplication::Shutdown()
 	SDL_Quit();
 }
 
+void CMainApplication::SendUserPos()
+{
+	if (sock!=INVALID_SOCKET) {
+		int n;
+		const int size=sizeof(float)*3+1;
+		char buff[size];
+		buff[0]='p';
+		memcpy(buff+1, &(UserPosition.x), sizeof(float));
+		memcpy(buff+1+sizeof(float), &(UserPosition.y), sizeof(float));
+		memcpy(buff+1+2*sizeof(float), &(UserPosition.z), sizeof(float));
+		n=send(sock, buff, size, 0);
+		if (n<size) {
+			closesocket(sock);
+			sock=INVALID_SOCKET;
+		}
+	}
+}
+
+void CMainApplication::SendDragDrop(Vector3 pos)  // when=0: start; 1: middle; 2: end; 3: not sending
+{//rgh: send init and end via tcp, but middle via udp?
+
+	if (sock!=INVALID_SOCKET) {
+		if (whenDrag == 3)
+			return;
+		if (whenDrag == 0) {
+			initDrag=pos;
+			msDrag=SDL_GetTicks();
+			whenDrag=1;
+			return;
+		}
+		int n;
+		const int size=sizeof(float)*6+2+sizeof(UINT32);
+		UINT32 elapsed=SDL_GetTicks()-msDrag;
+		if (whenDrag == 1 && elapsed < 100)
+			return;
+		char buff[size];
+		buff[0]='D';
+		memcpy(buff+1, &(initDrag.x), sizeof(float));
+		memcpy(buff+1+sizeof(float), &(initDrag.y), sizeof(float));
+		memcpy(buff+1+2*sizeof(float), &(initDrag.z), sizeof(float));
+		memcpy(buff+1+3*sizeof(float), &(pos.x), sizeof(float));
+		memcpy(buff+1+4*sizeof(float), &(pos.y), sizeof(float));
+		memcpy(buff+1+5*sizeof(float), &(pos.z), sizeof(float));
+		memcpy(buff+1+6*sizeof(float), &(elapsed), sizeof(UINT32));
+		memcpy(buff+1+6*sizeof(float)+ sizeof(UINT32), &whenDrag, 1);
+		n=send(sock, buff, size, 0);
+		if (n<size) {
+			closesocket(sock);
+			sock=INVALID_SOCKET;
+		}
+		if (whenDrag==2)
+			whenDrag=3;
+	}
+
+}
+
 void CMainApplication::Send(char c, int32_t value)
 {
-	if (sock>=0) {
+	if (sock!=INVALID_SOCKET) {
 		int32_t tmp;
 		tmp=htonl(value);
 		int n;
 		n=send(sock, &c, sizeof(c), 0);
 		if (n<sizeof(c)) {
 			closesocket(sock);
-			sock=-1;
+			sock=INVALID_SOCKET;
 		}
 		n=send(sock, (char*)&tmp, sizeof(tmp), 0);
 		if (n<sizeof(tmp)) {
 			closesocket(sock);
-			sock=-1;
+			sock=INVALID_SOCKET;
 		}
 	}
 }
 
 void CMainApplication::Send(char c, bool value)
 {
-	if (sock>=0) {
+	if (sock!=INVALID_SOCKET) {
 		int n;
 		n=send(sock, &c, sizeof(c), 0);
 		if (n<sizeof(c)) {
 			closesocket(sock);
-			sock=-1;
+			sock=INVALID_SOCKET;
 		}
 		n=send(sock, (char*)&value, 1, 0);
 		if (n<1) {
 			closesocket(sock);
-			sock=-1;
+			sock=INVALID_SOCKET;
 		}
 	}
 }
+
 void CMainApplication::SendConfigFile()
 {
 	Send('n', currentConfig);
@@ -1117,19 +1436,23 @@ bool CMainApplication::HandleInput()
 			if (sdlEvent.key.keysym.sym == SDLK_a) {
 				Matrix4 tmp = m_mat4HMDPose;
 				UserPosition += tmp.invert()*Vector3(0, 0, speed);
+				SendUserPos();
 				//UserPosition.x += speed;
 			}
 			if (sdlEvent.key.keysym.sym == SDLK_y) {
 				//UserPosition.x -= speed;
 				Matrix4 tmp = m_mat4HMDPose;
 				UserPosition -= tmp.invert()*Vector3(0, 0, speed);
+				SendUserPos();
 			}
 			if (sdlEvent.key.keysym.sym == SDLK_o) {
 				UserPosition[2] -= 0.1f;
+				SendUserPos();
 				dprintf("%f %f\n", UserPosition[0], UserPosition[2]);
 			}
 			if (sdlEvent.key.keysym.sym == SDLK_p) {
 				UserPosition[2] += 0.1f;
+				SendUserPos();
 				dprintf("%f %f\n", UserPosition[0], UserPosition[2]);
 			}
 		}
@@ -1178,18 +1501,44 @@ bool CMainApplication::HandleInput()
 						if (currentConfig>=myargc)
 							currentConfig=1;
 						SendConfigFile();
-						CleanScene();
-						LoadConfigFile(myargv[currentConfig]);
-						SetupScene();
+						//we will soon receive an order to reload the scene, so avoid double-reloading + race here
+						//CleanScene();
+						//LoadConfigFile(myargv[currentConfig]);
+						//SetupScene();
 					} else if (state.rAxis[0].y < -0.7 && state.rAxis[0].x > -0.4 && state.rAxis[0].x < 0.4) {
 						//prev config file
 						currentConfig--;
 						if (currentConfig<=0)
 							currentConfig=myargc-1;
 						SendConfigFile();
-						CleanScene();
-						LoadConfigFile(myargv[currentConfig]);
-						SetupScene();
+						//CleanScene();
+						//LoadConfigFile(myargv[currentConfig]);
+						//SetupScene();
+					}
+				}
+			} else { //Drag and Drop
+				if (buttonPressed[2][unDevice] && 
+					/*(state.ulButtonTouched&vr::ButtonMaskFromId(vr::k_EButton_Axis0)) == 0 &&*/
+					(state.ulButtonPressed&vr::ButtonMaskFromId(vr::k_EButton_Axis0)) == 0) {
+					buttonPressed[2][unDevice]=false; 
+					whenDrag=2;
+					//rgh FIXME send final Drag command
+				}
+				if ( 
+					(/*state.ulButtonTouched&vr::ButtonMaskFromId(vr::k_EButton_Axis0) || */
+					 state.ulButtonPressed&vr::ButtonMaskFromId(vr::k_EButton_Axis0) 
+					)){
+					
+					if (state.rAxis[0].y > -0.4 && state.rAxis[0].y < 0.4 && state.rAxis[0].x<-0.7) {
+						if (!buttonPressed[2][unDevice]) {
+							buttonPressed[2][unDevice]=true;
+							whenDrag=0;
+						}
+							//rgh FIXME send initial Drag command
+						//} else {
+						//	whenDrag=1;
+						//	//rgh FIXME send intermediate Drag command
+						//}
 					}
 				}
 			}
@@ -1260,12 +1609,14 @@ bool CMainApplication::HandleInput()
 					if (gazenavigation) {
 						Matrix4 tmp = m_mat4HMDPose;
 						UserPosition += tmp.invert()*Vector3(0, 0, speed);
+						SendUserPos();
 					} else {
 						//vr::VRControllerState_t cs;
 						//vr::TrackedDevicePose_t dp;
 						//m_pHMD->GetControllerStateWithPose( vr::TrackingUniverseStanding, firstdevice, &cs, &dp );
 						const Matrix4 tmp = m_rmat4DevicePose[firstdevice];
-						UserPosition += tmp*Vector3(0, 0, speed);	
+						UserPosition += tmp*Vector3(0, 0, speed);
+						SendUserPos();
 					}
 				}
 				else {
@@ -1433,6 +1784,18 @@ void CMainApplication::HapticFeedback(int device)
 //-----------------------------------------------------------------------------
 void CMainApplication::RenderFrame()
 {
+	
+	if (cleanup==1) {
+		CleanScene();
+		LoadConfigFile(myargv[currentConfig]);
+		SetupScene();
+		cleanupmutex.lock();
+		cleanup=0;
+		cleanupmutex.unlock();
+		cleanupcond.notify_one();
+		//return;
+	}
+
 	int e;
 	// for now as fast as possible
 	if ( m_pHMD )
@@ -1688,7 +2051,7 @@ bool CMainApplication::SetupDepthPeeling()
 void CMainApplication::CleanScene()
 { //delete, opposite order from creation
 	//isos
-	if (ISOS) {
+	if (m_glSceneVertBuffer && ISOS) {//if unable to init openvr runtime, all these gl variables are null
 		for (int i=0;i<NUMLODS;i++) {
 			glDeleteBuffers(NUMPLY, m_glSceneVertBuffer[i]);
 			glDeleteBuffers(NUMPLY, m_unSceneVAOIndices[i]);
@@ -1712,7 +2075,7 @@ void CMainApplication::CleanScene()
 		m_unSceneVAO=nullptr;
 		vertdataarray=nullptr;
 		vertindicesarray=nullptr;
-		ISOS=0;
+		//ISOS=0;
 	}
 	//atoms
 	if (atoms) {
@@ -1728,6 +2091,7 @@ void CMainApplication::CleanScene()
 	//infocube
 	::CleanInfoCube(&m_unInfoVAO, &m_unInfoVertBuffer, &m_unInfoIndexBuffer);
 	cleanConfig();
+	ISOS=0;
 }
 
 //-----------------------------------------------------------------------------
@@ -1929,13 +2293,19 @@ void CMainApplication::RenderControllerGlyph (const vr::Hmd_Eye nEye, const int
 	Vector3 pos; 
 	PrepareControllerGlyph(nEye, controller, &pos);
 	if (controller == seconddevice) {
-		if (selectedAtom==-1) { //isos
-			sprintf (string, "%d", currentiso+1);
-		} else {
+		if (selectedAtom!=-1 || whenDrag !=3) {
 			pos /=scaling;
 			pos-=UserPosition;
 			pos=Vector3(pos[0], -pos[2], pos[1]);
-		
+		}
+
+		if (whenDrag != 3) {
+			SendDragDrop(pos);
+		}
+
+		if (selectedAtom==-1) { //isos
+			sprintf (string, "%d", currentiso+1);
+		} else {
 			pos-=Vector3(atoms[currentset][selectedAtom*4+0], atoms[currentset][selectedAtom*4+1], atoms[currentset][selectedAtom*4+2]);
 		
 			sprintf (string, "%0.2fa", pos.length());
@@ -2866,7 +3236,17 @@ glUseProgram(m_unRenderModelProgramID);
 Matrix4 globalScaling;
 globalScaling.scale(scaling, scaling, scaling);
 
+int AtomsInCurrentset;
+if (currentset==0)
+	AtomsInCurrentset=numAtoms[currentset];
+else
+	AtomsInCurrentset=numAtoms[currentset]-numAtoms[currentset-1];
+
 for (int i=0;i<info.size(); i++) {
+	if (info[i].atom-1 > AtomsInCurrentset) {
+		//wrong atom
+		continue;
+	}
 	Matrix4 trans;
 
 	Vector3 iPos(info[i].pos[0], info[i].pos[1], info[i].pos[2]);
@@ -2886,10 +3266,12 @@ for (int i=0;i<info.size(); i++) {
 
 //now line
 glUseProgram(m_unUnitCellProgramID);
+
 for (int i = 0; i < info.size(); i++) {
 	if (info[i].atom < 1)
 		continue;
-	if (info[i].atom-1 > numAtoms[currentset]) {
+	
+	if (info[i].atom-1 > AtomsInCurrentset) {
 		//wrong atom
 		continue;
 	}
@@ -3113,7 +3495,12 @@ void CMainApplication::RenderAllTrackedRenderModels(vr::Hmd_Eye nEye)
 		if (!m_rTrackedDeviceToRenderModel[unTrackedDevice] || !m_rbShowTrackedDevice[unTrackedDevice])
 			continue;
 
-		const vr::TrackedDevicePose_t & pose = m_rTrackedDevicePose[unTrackedDevice];
+		if (firstdevice==-1 && m_pHMD->GetTrackedDeviceClass( unTrackedDevice ) == vr::TrackedDeviceClass_Controller )
+			firstdevice=unTrackedDevice;
+		else if (seconddevice==-1 && firstdevice!=unTrackedDevice && m_pHMD->GetTrackedDeviceClass( unTrackedDevice ) == vr::TrackedDeviceClass_Controller)
+			seconddevice=unTrackedDevice;
+
+		//const vr::TrackedDevicePose_t & pose = m_rTrackedDevicePose[unTrackedDevice];
 
 		const Matrix4 & matDeviceToTracking = m_rmat4DevicePose[unTrackedDevice];
 		Matrix4 matMVP = GetCurrentViewProjectionMatrix(nEye) * matDeviceToTracking;
@@ -3121,6 +3508,45 @@ void CMainApplication::RenderAllTrackedRenderModels(vr::Hmd_Eye nEye)
 
 		m_rTrackedDeviceToRenderModel[unTrackedDevice]->Draw();
 	}
+
+	//now paint remote ones too. Add colour later
+	for (int i=0;i<remotes.size();i++) {
+		if (remotes[i].id==identifier)
+			continue;
+		if (remotes[i].valid[0]) {
+			Vector3 relpos=UserPosition-remotes[i].UserPos;
+			Matrix4 rm(remotes[i].head);
+			rm.invert().translate(relpos*scaling); 
+			Matrix4 matMVP = GetCurrentViewProjectionMatrix(nEye) *rm;
+			//add rest of transforms (their user trans, etc)
+			glUniformMatrix4fv(m_nRenderModelMatrixLocation, 1, GL_FALSE, matMVP.get());
+
+			if (nullptr==m_rTrackedDeviceToRenderModel[vr::k_unTrackedDeviceIndex_Hmd]) {
+				SetupRenderModelForTrackedDevice( vr::k_unTrackedDeviceIndex_Hmd );
+				m_rbShowTrackedDevice[ vr::k_unTrackedDeviceIndex_Hmd ] = false;
+			}
+			m_rTrackedDeviceToRenderModel[vr::k_unTrackedDeviceIndex_Hmd]->Draw();
+		}
+		if (remotes[i].valid[1] && firstdevice!=-1) {
+			Vector3 relpos=UserPosition-remotes[i].UserPos;
+			Matrix4 rm(ConvertSteamVRMatrixToMatrix4(remotes[i].c1));
+			rm.translate(relpos*scaling); 
+			Matrix4 matMVP = GetCurrentViewProjectionMatrix(nEye) *rm;
+			//add rest of transforms (their user trans, etc)
+			glUniformMatrix4fv(m_nRenderModelMatrixLocation, 1, GL_FALSE, matMVP.get());
+			m_rTrackedDeviceToRenderModel[firstdevice]->Draw();
+		}
+		if (remotes[i].valid[2] && seconddevice!=-1) {
+			Vector3 relpos=UserPosition-remotes[i].UserPos;
+			Matrix4 rm(ConvertSteamVRMatrixToMatrix4(remotes[i].c2));
+			rm.translate(relpos*scaling); 
+			Matrix4 matMVP = GetCurrentViewProjectionMatrix(nEye) *rm;
+			//add rest of transforms (their user trans, etc)
+			glUniformMatrix4fv(m_nRenderModelMatrixLocation, 1, GL_FALSE, matMVP.get());
+			m_rTrackedDeviceToRenderModel[seconddevice]->Draw();
+		}
+	}
+
 	GLuint e;
 	if ((e = glGetError()) != GL_NO_ERROR)
 		dprintf("Gl error marker 2: %d, %s\n", e, gluErrorString(e));
@@ -3529,13 +3955,16 @@ int main(int argc, char *argv[])
 {
 	//https://stackoverflow.com/questions/8544090/detected-memory-leaks
 	/*_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
-	 _CrtSetBreakAlloc(89);
-	 _CrtSetBreakAlloc(88);
-	 _CrtSetBreakAlloc(87);
-	 _CrtSetBreakAlloc(86);
-	 _CrtSetBreakAlloc(85);
-	 _CrtSetBreakAlloc(84);
-	 */
+		 _CrtSetBreakAlloc(639);
+	 _CrtSetBreakAlloc(783);
+	 _CrtSetBreakAlloc(676);
+	 _CrtSetBreakAlloc(675);
+	 _CrtSetBreakAlloc(639);
+	 _CrtSetBreakAlloc(125);
+	 _CrtSetBreakAlloc(118);
+	 _CrtSetBreakAlloc(117);
+	 _CrtSetBreakAlloc(94);
+	 _CrtSetBreakAlloc(93);*/
 	TMPDIR=".\\";
 	//http://stackoverflow.com/questions/4991967/how-does-wsastartup-function-initiates-use-of-the-winsock-dll
 	WSADATA wsaData;
diff --git a/RemoteVisualization/Makefile b/RemoteVisualization/Makefile
index 360f85c24b8fdc0826276c1e70d81fd4c41d916f..6902f7b52a97a95bb9f6c0a88d05f42ae39882f1 100644
--- a/RemoteVisualization/Makefile
+++ b/RemoteVisualization/Makefile
@@ -1,4 +1,4 @@
-all: NOMADVRLib/atoms.o eprintf.o happyhttp/happyhttp.o NOMADVRLib/ConfigFileAtoms.o main.o
+all: NOMADVRLib/atoms.o eprintf.o happyhttp/happyhttp.o NOMADVRLib/ConfigFileAtoms.o main.o exportXYZ.o 
 	g++ $^ -o nomad2xyz
 
 .cpp.o:
diff --git a/RemoteVisualization/exportXYZ.cpp b/RemoteVisualization/exportXYZ.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..d75c52d921901299367ce9429ed084968ebdc4d4
--- /dev/null
+++ b/RemoteVisualization/exportXYZ.cpp
@@ -0,0 +1,22 @@
+#include <stdio.h>
+
+#include "NOMADVRLib/ConfigFileAtoms.h"
+#include "NOMADVRLib/atoms.hpp"
+#include "exportXYZ.h"
+
+void exportXYZ(const char * file, const char *mat, int timesteps)
+{
+
+FILE * f=fopen (file, "w");
+for (int i=0;i<timesteps;i++) {
+	fprintf (f, "%d\n", numAtoms[i]);
+	fprintf (f, "Comment: Material=%s\n", mat);
+
+	for (int j=0;j<numAtoms[i];j++) {
+		fprintf (f, "%s\t%f\t%f\t%f\n", atomNames[(int)(atoms[i][j*4+3])], 
+			atoms[i][j*4+0], atoms[i][j*4+1], atoms[i][j*4+2]);
+
+
+	}
+}
+}
diff --git a/RemoteVisualization/exportXYZ.h b/RemoteVisualization/exportXYZ.h
new file mode 100644
index 0000000000000000000000000000000000000000..631a853dd3977bd180027cd459655720e5197935
--- /dev/null
+++ b/RemoteVisualization/exportXYZ.h
@@ -0,0 +1,6 @@
+#ifndef __EXPORTXYZ_H
+#define __EXPORTXYZ_H
+
+void exportXYZ(const char * file, const char *mat, int timesteps);
+
+#endif
diff --git a/RemoteVisualization/main.cpp b/RemoteVisualization/main.cpp
index 47ca9d49eab3692b2a71c318226787b8e45f15b7..0adea73aa88a15b4df49f274c2344bb20d750acc 100644
--- a/RemoteVisualization/main.cpp
+++ b/RemoteVisualization/main.cpp
@@ -17,26 +17,10 @@
 #include "NOMADVRLib/eprintf.h"
 #include "NOMADVRLib/ConfigFileAtoms.h"
 #include "NOMADVRLib/atoms.hpp"
+#include "exportXYZ.h"
 
 	int timesteps;
 
-void exportXYZ(const char * file, const char *mat)
-{
-
-FILE * f=fopen (file, "w");
-for (int i=0;i<timesteps;i++) {
-	fprintf (f, "%d\n", numAtoms[i]);
-	fprintf (f, "Comment: Material=%s\n", mat);
-
-	for (int j=0;j<numAtoms[i];j++) {
-		fprintf (f, "%s\t%f\t%f\t%f\n", atomNames[(int)(atoms[i][j*4+3])], 
-			atoms[i][j*4+0], atoms[i][j*4+1], atoms[i][j*4+2]);
-
-
-	}
-}
-}
-
 void usage (const char * argv0) 
 {
         eprintf ("Usage: \n%s e <basename for encyclopedia json> xyz", argv0);
@@ -56,10 +40,10 @@ int main (int argc, char **argv) {
 		readAtomsJson (argv[2], &numAtoms, &timesteps, &atoms, abc, &clonedAtoms);
 	
 		//now export xyz
-		exportXYZ(argv[3], argv[2]);
+		exportXYZ(argv[3], argv[2], timesteps);
 	} else if (argv[1][0]=='a') {
 		readAtomsAnalyticsJson (argv[2], &numAtoms, &timesteps, &atoms, abc, &clonedAtoms);
-		exportXYZ (argv[3], argv[2]);
+		exportXYZ (argv[3], argv[2], timesteps);
 	} else {
 		usage(argv[0]);	
 		return 2;
diff --git a/proxy/MD-driver/Makefile b/proxy/MD-driver/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..01a6b1141cd996c21c71f7647c391f72ae933762
--- /dev/null
+++ b/proxy/MD-driver/Makefile
@@ -0,0 +1,16 @@
+all: PeriodicTable SimpleMove
+
+SimpleMove: SimpleMove.o PeriodicTable.o exportXYZ.o NOMADVRLib/ConfigFileAtoms.o NOMADVRLib/atoms.o \
+	happyhttp/happyhttp.o eprintf.o myrecv.o
+	g++ -g $^ -o $@
+
+PeriodicTable: PeriodicTable.o NOMADVRLib/ConfigFileAtoms.o NOMADVRLib/atoms.o exportXYZ.o happyhttp/happyhttp.o \
+	eprintf.o PeriodicTableMain.o
+	g++ -g $^ -o $@
+
+.cpp.o:
+	g++ -g -I. -std=c++11 $< -c -o $*.o
+
+clean:
+	rm -f *.o PeriodicTable SimpleMove
+
diff --git a/proxy/MD-driver/PT.xyz b/proxy/MD-driver/PT.xyz
new file mode 100644
index 0000000000000000000000000000000000000000..5b9730693269f4c515b8bc4a1532ae3df7b59e5c
--- /dev/null
+++ b/proxy/MD-driver/PT.xyz
@@ -0,0 +1,120 @@
+118
+Comment: Material=PeriodicTable
+H	0.000000	0.000000	0.000000
+He	17.000000	0.000000	0.000000
+Li	0.000000	0.000000	1.000000
+Be	1.000000	0.000000	1.000000
+B	10.000000	0.000000	1.000000
+C	11.000000	0.000000	1.000000
+N	12.000000	0.000000	1.000000
+O	13.000000	0.000000	1.000000
+F	14.000000	0.000000	1.000000
+Ne	15.000000	0.000000	1.000000
+Na	0.000000	0.000000	2.000000
+Mg	1.000000	0.000000	2.000000
+Al	10.000000	0.000000	2.000000
+Si	11.000000	0.000000	2.000000
+P	12.000000	0.000000	2.000000
+S	13.000000	0.000000	2.000000
+Cl	14.000000	0.000000	2.000000
+Ar	15.000000	0.000000	2.000000
+K	0.000000	0.000000	3.000000
+Ca	1.000000	0.000000	3.000000
+Sc	2.000000	0.000000	3.000000
+Ti	3.000000	0.000000	3.000000
+V	4.000000	0.000000	3.000000
+Cr	5.000000	0.000000	3.000000
+Mn	6.000000	0.000000	3.000000
+Fe	7.000000	0.000000	3.000000
+Co	8.000000	0.000000	3.000000
+Ni	9.000000	0.000000	3.000000
+Cu	10.000000	0.000000	3.000000
+Zn	11.000000	0.000000	3.000000
+Ga	12.000000	0.000000	3.000000
+Ge	13.000000	0.000000	3.000000
+As	14.000000	0.000000	3.000000
+Se	15.000000	0.000000	3.000000
+Br	16.000000	0.000000	3.000000
+Kr	17.000000	0.000000	3.000000
+Rb	0.000000	0.000000	4.000000
+Sr	1.000000	0.000000	4.000000
+Y	2.000000	0.000000	4.000000
+Zr	3.000000	0.000000	4.000000
+Nb	4.000000	0.000000	4.000000
+Mo	5.000000	0.000000	4.000000
+Tc	6.000000	0.000000	4.000000
+Ru	7.000000	0.000000	4.000000
+Rh	8.000000	0.000000	4.000000
+Pd	9.000000	0.000000	4.000000
+Ag	10.000000	0.000000	4.000000
+Cd	11.000000	0.000000	4.000000
+In	12.000000	0.000000	4.000000
+Sn	13.000000	0.000000	4.000000
+Sb	14.000000	0.000000	4.000000
+Te	15.000000	0.000000	4.000000
+I	16.000000	0.000000	4.000000
+Xe	17.000000	0.000000	4.000000
+Cs	0.000000	0.000000	5.000000
+Ba	1.000000	0.000000	5.000000
+La	2.000000	0.000000	5.000000
+Ce	4.000000	0.000000	9.000000
+Pr	5.000000	0.000000	9.000000
+Nd	6.000000	0.000000	9.000000
+Pm	7.000000	0.000000	9.000000
+Sm	8.000000	0.000000	9.000000
+Eu	9.000000	0.000000	9.000000
+Gd	10.000000	0.000000	9.000000
+Tb	11.000000	0.000000	9.000000
+Dy	12.000000	0.000000	9.000000
+Ho	13.000000	0.000000	9.000000
+Er	14.000000	0.000000	9.000000
+Tm	15.000000	0.000000	9.000000
+Yb	16.000000	0.000000	9.000000
+Lu	17.000000	0.000000	9.000000
+Hf	3.000000	0.000000	5.000000
+Ta	4.000000	0.000000	5.000000
+W	5.000000	0.000000	5.000000
+Re	6.000000	0.000000	5.000000
+Os	7.000000	0.000000	5.000000
+Ir	8.000000	0.000000	5.000000
+Pt	9.000000	0.000000	5.000000
+Au	10.000000	0.000000	5.000000
+Hg	11.000000	0.000000	5.000000
+Tl	12.000000	0.000000	5.000000
+Pb	13.000000	0.000000	5.000000
+Bi	14.000000	0.000000	5.000000
+Po	15.000000	0.000000	5.000000
+At	16.000000	0.000000	5.000000
+Rn	17.000000	0.000000	5.000000
+Fr	0.000000	0.000000	6.000000
+Ra	1.000000	0.000000	6.000000
+Ac	2.000000	0.000000	6.000000
+Th	4.000000	0.000000	10.000000
+Pa	5.000000	0.000000	10.000000
+U	6.000000	0.000000	10.000000
+Np	7.000000	0.000000	10.000000
+Pu	8.000000	0.000000	10.000000
+Am	9.000000	0.000000	10.000000
+Cm	10.000000	0.000000	10.000000
+Bk	11.000000	0.000000	10.000000
+Cf	12.000000	0.000000	10.000000
+Es	13.000000	0.000000	10.000000
+Fm	14.000000	0.000000	10.000000
+Md	15.000000	0.000000	10.000000
+No	16.000000	0.000000	10.000000
+Lr	17.000000	0.000000	10.000000
+Rf	3.000000	0.000000	6.000000
+Ha	4.000000	0.000000	6.000000
+Sg	5.000000	0.000000	6.000000
+Ns	6.000000	0.000000	6.000000
+Hs	7.000000	0.000000	6.000000
+Mt	8.000000	0.000000	6.000000
+Ds	9.000000	0.000000	6.000000
+Rg	10.000000	0.000000	6.000000
+Cn	11.000000	0.000000	6.000000
+Nh	12.000000	0.000000	6.000000
+Fl	13.000000	0.000000	6.000000
+Mc	14.000000	0.000000	6.000000
+Lv	15.000000	0.000000	6.000000
+Ts	16.000000	0.000000	6.000000
+Og	17.000000	0.000000	6.000000
diff --git a/proxy/MD-driver/PeriodicTable.cpp b/proxy/MD-driver/PeriodicTable.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7531fbe27b57363a5e5c0d42e9222843d872f127
--- /dev/null
+++ b/proxy/MD-driver/PeriodicTable.cpp
@@ -0,0 +1,89 @@
+#include "PeriodicTable.h"
+
+void CreatePeriodicTable (float * atoms, float scaling)
+{
+
+atoms[0]=0; //H
+atoms[1]=0;
+atoms[2]=0;
+atoms[3]=0;
+atoms[4]=17*scaling; //He
+atoms[5]=0;
+atoms[6]=0;
+atoms[7]=1;
+
+int currentAtom=2;
+
+for (int row=1;row<3;row ++) {
+	for (int j=0; j<2;j++) {//left
+		atoms[currentAtom*4]=j*scaling;
+		atoms[currentAtom*4+1]=0;
+		atoms[currentAtom*4+2]=-row*scaling;
+		atoms[currentAtom*4+3]=currentAtom;
+
+		currentAtom++;
+	}
+
+	for (int j=0; j<6;j++) {//right
+		atoms[currentAtom*4]=(j+12)*scaling;
+		atoms[currentAtom*4+1]=0;
+		atoms[currentAtom*4+2]=-row*scaling;
+		atoms[currentAtom*4+3]=currentAtom;
+
+		currentAtom++;
+	}
+
+}
+
+for (int row=3;row<5;row ++) {
+	for (int j=0; j<18;j++) {//left
+		atoms[currentAtom*4]=j*scaling;
+		atoms[currentAtom*4+1]=0;
+		atoms[currentAtom*4+2]=-row*scaling;
+		atoms[currentAtom*4+3]=currentAtom;
+
+		currentAtom++;
+	}
+
+}
+
+for (int row=5;row<7;row ++) {
+	for (int j=0; j<3;j++) {//left
+		atoms[currentAtom*4]=j*scaling;
+		atoms[currentAtom*4+1]=0;
+		atoms[currentAtom*4+2]=-row*scaling;
+		atoms[currentAtom*4+3]=currentAtom;
+
+		currentAtom++;
+	}
+
+	currentAtom+=14;
+
+	for (int j=0; j<15;j++) {//right
+		atoms[currentAtom*4]=(j+3)*scaling;
+		atoms[currentAtom*4+1]=0;
+		atoms[currentAtom*4+2]=-row*scaling;
+		atoms[currentAtom*4+3]=currentAtom;
+
+		currentAtom++;
+	}
+
+}
+
+//rare earths
+currentAtom=57;
+for (int row=0;row<2;row ++) {
+	for (int j=0; j<14;j++) {
+		atoms[currentAtom*4]=(j+4)*scaling;
+		atoms[currentAtom*4+1]=0;
+		atoms[currentAtom*4+2]=(-row-8)*scaling;
+		atoms[currentAtom*4+3]=currentAtom;
+
+		currentAtom++;	
+	}
+	currentAtom=89;
+
+}
+
+}
+
diff --git a/proxy/MD-driver/PeriodicTable.h b/proxy/MD-driver/PeriodicTable.h
new file mode 100644
index 0000000000000000000000000000000000000000..a68b60bf75af72710c20cb038315644b3ed79761
--- /dev/null
+++ b/proxy/MD-driver/PeriodicTable.h
@@ -0,0 +1,3 @@
+
+void CreatePeriodicTable (float * atoms, float scaling);
+
diff --git a/proxy/MD-driver/PeriodicTableMain.cpp b/proxy/MD-driver/PeriodicTableMain.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..07cd0f07b6f7ea7c74fa7b970abc4493cd454efd
--- /dev/null
+++ b/proxy/MD-driver/PeriodicTableMain.cpp
@@ -0,0 +1,27 @@
+#include "NOMADVRLib/ConfigFileAtoms.h"
+#include "NOMADVRLib/atoms.hpp"
+#include "exportXYZ.h"
+#include "PeriodicTable.h"
+
+int main (int argc, char ** argv) 
+{
+
+//create periodic table
+if (argc!=2) {
+	fprintf (stderr, "Required argument, XYZ filename\n");
+	return 1;
+}
+
+numAtoms=new int[1];
+numAtoms[0]=118;
+
+atoms=new float* [1];
+atoms[0]=new float[118*4];
+
+CreatePeriodicTable(atoms[0], 5);
+
+exportXYZ(argv[1], "PeriodicTable", 1);
+
+return 0;
+}
+
diff --git a/proxy/MD-driver/Readme b/proxy/MD-driver/Readme
new file mode 100644
index 0000000000000000000000000000000000000000..0360ac889c78332b55d95418364c90e95b880e8e
--- /dev/null
+++ b/proxy/MD-driver/Readme
@@ -0,0 +1,19 @@
+Programs:
+PeriodicTable 
+	takes one filename as input and generates an XYZ with the atoms at the standard periodic table positions.
+Simplemove
+	Parameters: <server> <port> <secret>
+	Usage: 
+		Run the program in a shared filesystem folder.
+		The program connects to a NOMADVR proxy server, then generates config and xyz files
+		Other OpenVR NOMADVR instances can use these config files to enable atom Drag-and-drop functionality,
+			using the touchpad in the second controller.
+		The XYZ file in the disk contains the latest atom configuration.
+	
+Simplemove can be used as a sample program to interface OpenVR NOMAD VR with interactive molecular dynamics codes.
+
+Compilation:
+Requires: happyhttp NOMADVRLib rapidjson
+Copy these folders here and run "make".
+
+
diff --git a/proxy/MD-driver/SimpleMove.cpp b/proxy/MD-driver/SimpleMove.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..53d9d6f78326385f93f997f37693ad90963be76f
--- /dev/null
+++ b/proxy/MD-driver/SimpleMove.cpp
@@ -0,0 +1,220 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+
+#include "NOMADVRLib/ConfigFileAtoms.h"
+#include "NOMADVRLib/atoms.hpp"
+#include "PeriodicTable.h"
+#include "exportXYZ.h"
+#include "state.h"
+#include "myrecv.h"
+
+#ifndef INVALID_SOCKET
+#define INVALID_SOCKET -1
+#define closesocket close
+#endif
+
+const int NUMATOMS=118;
+
+int main (int argc, char ** argv)
+{
+
+if (argc!=4) {
+	fprintf (stderr, "Parameters: <server> <port> <secret>\n");
+	return 1;
+}
+
+state_t state;
+
+numAtoms=new int[1];
+numAtoms[0]=NUMATOMS;
+
+atoms=new float* [1];
+atoms[0]=new float[NUMATOMS*4];
+
+CreatePeriodicTable(atoms[0], 5);
+
+exportXYZ("A.xyz", "PeriodicTable", 1);
+
+FILE *ncfg=fopen ("A.ncfg", "w");
+fprintf (ncfg, "xyzfile A.xyz\n"
+	"server %s %s %s\n"
+	"menubutton Infobox\n"
+	"sidebuttontimestep 0\n", argv[1], argv[2], argv[3]);
+fclose (ncfg);
+
+//connect to server, TCP
+
+int sock;
+
+struct hostent *he;
+struct sockaddr_in serv_addr;
+
+	if ( (he = gethostbyname(argv[1]) ) == nullptr ) {
+		fprintf (stderr, "Connect to server, could not get host name %s\n", argv[1]);
+      return 2; /* error */
+	}
+	memset((char *) &serv_addr, 0, sizeof(serv_addr));
+	memcpy(&serv_addr.sin_addr, he->h_addr_list[0], he->h_length);
+	serv_addr.sin_family = AF_INET;
+	serv_addr.sin_port = htons(atoi(argv[2]));
+	sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
+	if ( connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr))) {
+				fprintf (stderr, "Connect to server, could not get connection %s\n", argv[1]);
+      return 3; /* error */
+	}
+
+int n;
+	int32_t tmp;
+	tmp=htonl (atoi(argv[3])); //secret
+	n = send(sock, (char*)&tmp , sizeof(tmp), 0);
+	if (n<sizeof(tmp)) {
+		closesocket(sock);
+		sock=INVALID_SOCKET;
+		fprintf (stderr, "couldn't send secret\n");
+		return 4;
+	}
+
+
+char buffer[100];
+
+uint32_t ID;
+int found;
+while (true) {
+	n=myrecv(sock, buffer, 1, 0);
+	if (n<1) {
+			fprintf (stderr, "couldn't receive data command");
+			return 5;
+	}
+	printf ("received order %c\n", buffer[0]);
+	switch (buffer[0]) {
+		case 'X': // ID
+		n=myrecv(sock, &ID, 4, 0);
+		if (n<4) {
+			fprintf (stderr, "couldn't receive data X");
+			return 5;
+		}
+		break;
+
+		case 't': 
+		n=myrecv(sock, &state.timestep, 4, 0);
+		if (n<4) 
+{
+			fprintf (stderr, "couldn't receive data t");
+			return 5;
+		}
+		break;
+
+		case 'i':
+		n=myrecv(sock, &state.iso, 4, 0);
+		if (n<4) 
+{
+			fprintf (stderr, "couldn't receive data i\n");
+			return 5;
+		}
+		break;
+
+		case 's': 
+		n=myrecv(sock, &state.showatoms, 1, 0);
+		if (n<1) 
+{
+			fprintf (stderr, "couldn't receive data s\n");
+			return 5;
+		}
+		break;
+
+		case 'n': 
+		n=myrecv(sock, &state.ncfg, 4, 0);
+		if (n<4) 
+{
+			fprintf (stderr, "couldn't receive data n\n");
+			return 5;
+		}
+		break;
+
+		case 'p': // user position
+		n=myrecv (sock, buffer+1, sizeof(float)*3 + 4, 0);
+		if (n<sizeof(float)*3 + 4) {
+			fprintf (stderr, "couldn't receive data p\n");
+			return 5;
+		}
+		break;
+
+		case 'd': //disconnect
+		n=myrecv (sock, buffer+1, 4, 0);
+		if (n<4) 
+{
+			fprintf (stderr, "couldn't receive data d\n");
+			return 5;
+		}
+		break;
+
+		case 'D': //Drag
+		n=myrecv (sock, buffer+1, 6*sizeof(float) + 4*2 +1, 0);
+		if (n<6*sizeof(float) + 4*2 +1) 
+{
+			fprintf (stderr, "couldn't receive data D\n");
+			return 5;
+		}
+		//find the nearest atom
+		//test, use atom 0
+		float orig[3];
+		float dest[3];
+		uint32_t ID, time;
+		char button;
+		memcpy (&ID, buffer+1, 4);
+		memcpy (orig, buffer+5, 3*sizeof(float));
+		memcpy (dest, buffer+5+3*sizeof(float), 3*sizeof(float));
+		memcpy (&time, buffer+5+6*sizeof(float), 4);
+		button=buffer[1+6*sizeof(float)+8];
+		if (button!=2) {
+			printf ("intermediate, discarding\n");
+			continue;
+		}
+		found=-1;
+		for (int i=0;i<NUMATOMS;i++) {
+			float sum=0;
+			for (int j=0;j<3;j++) {
+				const float tmp=atoms[0][i*4+j]-orig[j];
+				sum+=tmp*tmp;
+			}
+			const float r=atomRadius(static_cast<int>(atoms[0][i*4+3]));
+			if (sum < r*r) {
+				found=i;
+				break;
+			}
+			
+		}
+		if (found!=-1) {
+			printf ("Starting Drag atom %d\n", found);
+			//drag it
+			for (int i=0;i<3;i++)
+				atoms[0][found*4+i]+=dest[i]-orig[i];
+
+			exportXYZ("A.xyz", "PeriodicTable", 1);
+			//reload scene
+
+			buffer[0]='n';
+			memcpy(buffer+1, (char*)&state.ncfg, 4);
+			n=send (sock, buffer, 5, 0);
+			if (n<5) {
+				fprintf (stderr, "could not send reload command\n");
+				return 6;
+			}
+			printf ("Reload sent\n");
+		}
+		break;
+
+		default: 
+			fprintf (stderr, "Unknown command received: '%c', %d\n", buffer[0], buffer[0]);
+			return -5;
+	}
+}
+
+return 0;
+}
diff --git a/proxy/MD-driver/eprintf.cpp b/proxy/MD-driver/eprintf.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5a27b1cdd7f92884358bcaec49442482a2b29c15
--- /dev/null
+++ b/proxy/MD-driver/eprintf.cpp
@@ -0,0 +1,32 @@
+/*
+# Copyright 2016-2018 Ruben Jesus Garcia Hernandez
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+*/
+
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "NOMADVRLib/eprintf.h"
+
+void eprintf( const char *fmt, ... )
+{
+	va_list args;
+	char buffer[ 2048 ];
+
+	va_start( args, fmt );
+	vsprintf( buffer, fmt, args );
+	va_end( args );
+
+	fprintf(stderr, "%s\n", buffer );
+}
diff --git a/proxy/MD-driver/exportXYZ.cpp b/proxy/MD-driver/exportXYZ.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e07da7ce09e7d4af5ff48f1a39843a9f939e3f52
--- /dev/null
+++ b/proxy/MD-driver/exportXYZ.cpp
@@ -0,0 +1,23 @@
+#include <stdio.h>
+
+#include "NOMADVRLib/ConfigFileAtoms.h"
+#include "NOMADVRLib/atoms.hpp"
+#include "exportXYZ.h"
+
+void exportXYZ(const char * file, const char *mat, int timesteps)
+{
+
+FILE * f=fopen (file, "w");
+for (int i=0;i<timesteps;i++) {
+	fprintf (f, "%d\n", numAtoms[i]);
+	fprintf (f, "Comment: Material=%s\n", mat);
+
+	for (int j=0;j<numAtoms[i];j++) {
+		fprintf (f, "%s\t%f\t%f\t%f\n", atomNames[(int)(atoms[i][j*4+3])], 
+			atoms[i][j*4+0], atoms[i][j*4+1], atoms[i][j*4+2]);
+
+
+	}
+}
+fclose(f);
+}
diff --git a/proxy/MD-driver/exportXYZ.h b/proxy/MD-driver/exportXYZ.h
new file mode 100644
index 0000000000000000000000000000000000000000..631a853dd3977bd180027cd459655720e5197935
--- /dev/null
+++ b/proxy/MD-driver/exportXYZ.h
@@ -0,0 +1,6 @@
+#ifndef __EXPORTXYZ_H
+#define __EXPORTXYZ_H
+
+void exportXYZ(const char * file, const char *mat, int timesteps);
+
+#endif
diff --git a/proxy/MD-driver/myrecv.cpp b/proxy/MD-driver/myrecv.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..928ffc36f46245d90ff7d29b61edd49cc9f9fb13
--- /dev/null
+++ b/proxy/MD-driver/myrecv.cpp
@@ -0,0 +1,18 @@
+#include <sys/socket.h>
+
+#include "myrecv.h"
+
+int myrecv(int sock,void *buffer,int len, int flags) {
+        int n=len;
+        char *mybuff=(char*)buffer;
+        int r;
+        do {
+                r=recv (sock, mybuff, n, flags);
+                if (r<0)
+                        return r;
+                mybuff+=r;
+                n-=r;
+        } while (n>0);
+        return len;
+}
+
diff --git a/proxy/MD-driver/myrecv.h b/proxy/MD-driver/myrecv.h
new file mode 100644
index 0000000000000000000000000000000000000000..3f02e12c045a8e62e546d4f9911e73a999628b22
--- /dev/null
+++ b/proxy/MD-driver/myrecv.h
@@ -0,0 +1,2 @@
+int myrecv(int sock,void *buffer,int len, int flags); 
+
diff --git a/proxy/MD-driver/state.h b/proxy/MD-driver/state.h
new file mode 100644
index 0000000000000000000000000000000000000000..eea804a8f6ede3afca3d7f8f99b213012da93ef3
--- /dev/null
+++ b/proxy/MD-driver/state.h
@@ -0,0 +1,11 @@
+#ifndef __STATE_H
+#define __STATE_H
+
+struct state_t {
+        int32_t timestep;
+        int32_t iso;
+        bool showatoms;
+        int32_t ncfg;
+} state;
+
+#endif
diff --git a/proxy/Makefile b/proxy/Makefile
index 15fd70c545f436a282455ed2f7883829172d8067..a1ce0b485d7440a000215a18f5d2df35f5c49be5 100644
--- a/proxy/Makefile
+++ b/proxy/Makefile
@@ -1,5 +1,5 @@
-proxy:	proxy.cpp Makefile
-	g++ -std=c++11 -g proxy.cpp -o proxy
+NOMADVRproxy:	proxy.cpp Makefile
+	g++ -std=c++11 -g proxy.cpp -o NOMADVRproxy
 
 clean:
-	rm -f proxy.o proxy
+	rm -f proxy.o proxy NOMADVRproxy
diff --git a/proxy/proxy.cpp b/proxy/proxy.cpp
index 561295f7be9d0c4f00caf6e56eca65018de352bc..5f82448efa45474b55b07cd932532076db78d83c 100644
--- a/proxy/proxy.cpp
+++ b/proxy/proxy.cpp
@@ -15,6 +15,9 @@
 */
 #include <stdio.h>
 #include <stdint.h>
+#include <math.h>
+#include <limits>
+
 #ifdef WIN32
 #include <winsock2.h>
 #include <ws2tcpip.h>
@@ -26,6 +29,11 @@
 #include <sys/socket.h>
 #include <netinet/in.h>
 #include <sys/select.h>
+#include <sys/ioctl.h>
+#include <sys/signal.h>
+
+#define closesocket close
+#define INVALID_SOCKET (-1)
 //http://stackoverflow.com/questions/4291149/difference-between-string-h-and-strings-h
 #endif
 #include <string.h>
@@ -33,6 +41,11 @@
 #include <vector>
 
 std::vector<int> sockfds;
+std::vector<struct sockaddr_in> cli_addrs;
+std::vector<struct sockaddr_in> udp_cli_addrs;
+std::vector<float> cli_userpos;
+std::vector<unsigned char> cli_colour;
+
 
 void error(const char *msg)
 {
@@ -63,45 +76,70 @@ struct state_t {
 
 bool initNewSocket (unsigned int secret)
 {
+
 	int n;
 	 int32_t tmp;
 	 int32_t rsec;
-	 char buffer[17];
-	 buffer[0]='t';
+	 char buffer[22];
+	 buffer[0]='X';
+	 tmp=cli_addrs.size();
+	 memcpy(buffer+1, &tmp, sizeof(tmp));
+	 buffer[5]='t';
 	 tmp=htonl(state.timestep);
-	 memcpy (buffer+1, &tmp, sizeof(state.timestep));
-	 buffer[5]='i';
+	 memcpy (buffer+6, &tmp, sizeof(state.timestep));
+	 buffer[10]='i';
 	 tmp=htonl(state.iso);
-	 memcpy (buffer+6, &tmp, sizeof(state.iso));
-	 buffer[10]='s';
-	 buffer[11]=(char)state.showatoms;
-	 buffer[12]='n';
+	 memcpy (buffer+11, &tmp, sizeof(state.iso));
+	 buffer[15]='s';
+	 buffer[16]=(char)state.showatoms;
+	 buffer[17]='n';
 	 tmp=htonl(state.ncfg);
-	 memcpy (buffer+13, &tmp, sizeof(state.iso));
+	 memcpy (buffer+18, &tmp, sizeof(state.iso));
 
-	 //struct sockaddr_in cli_addr;
-	 //socklen_t clilen;
-     sockfds.push_back(accept(sockfds[0], nullptr, nullptr));
+	 struct sockaddr_in cli_addr;
+	 socklen_t clilen=sizeof(cli_addr);
+     sockfds.push_back(accept(sockfds[0], (sockaddr*)&cli_addr, &clilen));
 #ifdef WIN32
 	if (sockfds.back() == INVALID_SOCKET) 
 #else
      if (sockfds.back() < 0) 
 #endif
           error("ERROR on accept");
-
+	
+	cli_addrs.push_back(cli_addr);
+	for (int i=0;i<3;i++)
+		cli_userpos.push_back(0);
+	for (int i=0;i<3;i++)
+		cli_colour.push_back((unsigned char)(rand()/RAND_MAX*256.f));
 	tmp=htonl (secret);
 
 	 n=recv(sockfds.back(), (char*)&rsec, sizeof(rsec), 0);
 	 if (tmp!=rsec) {
+		//verify if extraneous HTTP GET and redirect
+		if (rsec==542393671){ // 'GET '
+			printf ("extraneous HTTP GET, send to NOMAD VR web\n");
+			const char* response="HTTP/1.1 302 Found\r\n"
+				"Location: https://www.nomad-coe.eu/the-project/graphics/VR-prototype\r\n";
+
+		n=send(sockfds.back(), response, strlen(response), 0);
+		
+		}
 #ifdef WIN32
 		closesocket (sockfds.back());
 #else
+		printf ("expected %d, got %d\n", secret, ntohl(rsec));
+		printf ("tmp=%d, rsec=%d\n", tmp, rsec);
+		char l[5];
+		l[4]=0;
+		memcpy(l, &rsec, 4);
+		printf ("ascii %s\n",l);
+
 		close (sockfds.back());
 #endif
 		return false;
 	 }
 
-	n = send(sockfds.back(), buffer , 17, 0);
+	n = send(sockfds.back(), buffer , 22, 0);
 	if (n < 0) {
 		error("ERROR writing to socket");
 		return false;
@@ -126,6 +164,8 @@ int myrecv(int sock,char *buffer,int len, int flags) {
 
 int main(int argc, char *argv[])
 {
+	static_assert( std::numeric_limits<double>::is_iec559, "IEEE 754 floating point" );
+
 	unsigned int secret;
 	//windows sockets are only very loosely based on unix sockets.
 	//initialization and error management are different.
@@ -142,7 +182,6 @@ int main(int argc, char *argv[])
 	 std::vector<socklen_t> clilens;
      char buffer[256];
      struct sockaddr_in serv_addr;
-	 std::vector<struct sockaddr_in> cli_addrs;
      int n;
 
 #ifdef WIN32
@@ -165,6 +204,8 @@ int main(int argc, char *argv[])
         WSACleanup();
         return 1;
     }
+#else
+signal(SIGPIPE, SIG_IGN);
 #endif
 
 
@@ -174,6 +215,7 @@ int main(int argc, char *argv[])
          exit(1);
      }
 	 secret=atoi(argv[2]);
+	printf ("NOMADVR proxy server, accepting connections with secret %d\n", secret);
      sockfds.push_back (socket(AF_INET, SOCK_STREAM, IPPROTO_TCP));
     
 #ifdef WIN32
@@ -186,7 +228,10 @@ int main(int argc, char *argv[])
 	 }
      memset((char *) &serv_addr, 0, sizeof(serv_addr));
 	 cli_addrs.push_back(serv_addr);
-	 cli_addrs.push_back(serv_addr);
+	 for (int i=0;i<3;i++)
+		cli_userpos.push_back(0); //unused
+	 for (int i=0;i<3;i++)
+		 cli_colour.push_back(0); //unused
      portno = atoi(argv[1]);
      serv_addr.sin_family = AF_INET;
      serv_addr.sin_addr.s_addr = INADDR_ANY;
@@ -201,6 +246,19 @@ int main(int argc, char *argv[])
   FD_ZERO (&active_fd_set);
   FD_SET (sockfds[0], &active_fd_set);
 
+  //now udp
+	int udpSock=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
+	if (udpSock==INVALID_SOCKET) 
+	{
+		error("ERROR on udp socket creation");
+	}
+	if (bind(udpSock, (struct sockaddr *) &serv_addr,
+              sizeof(serv_addr)) < 0) 
+              error("ERROR on binding");
+     listen(udpSock,5);
+
+	 FD_SET (udpSock, &active_fd_set);
+
      memset(buffer,0, 256);
 
 
@@ -214,31 +272,50 @@ for (;;) {
 	if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0)
 #endif
 		{
-		error ("select");
+//		error ("select"); //descriptors not currently ready
+		sleep(1);
+		continue;
 		}
 
 	for (unsigned i=1;i<sockfds.size();i++)
 		if (FD_ISSET (sockfds[i], &read_fd_set)) {
-     		n = myrecv(sockfds[i],buffer,1, 0);
-			fprintf (stderr, "received %d bytes from socket %d\n", n, i);
+			//select activates this also on other conditions such as closed socket. Verify.
+			int bytes_available;
+			ioctl(sockfds[i],FIONREAD,&bytes_available);
+			if (bytes_available<1) {
+				fprintf (stderr, "socket %d, bytes=%d, disconnecting\n", 
+					sockfds[i], bytes_available);
+				FD_CLR(sockfds[i], &active_fd_set);
+				closesocket(sockfds[i]);
+				sockfds[i]=-1;
+				continue;
+			}
+	     		n = myrecv(sockfds[i],buffer,1, 0);
+//			fprintf (stderr, "received %d bytes from socket %d\n", n, i);
 			if (n<0) { //disconnected
 				printf  ("client closed socket\n");
+				FD_CLR(sockfds[i], &active_fd_set);
+				closesocket(sockfds[i]);
 				sockfds[i]=-1;
 				continue;
 			}
 			buffer[n]=0;
 			if (n==0) {
 				printf  ("client closed socket\n");
+				FD_CLR(sockfds[i], &active_fd_set);
+				closesocket(sockfds[i]);
 				sockfds[i]=-1;
 				continue;
 			}
 
-			printf("Here is the message: '%s'\n",buffer);
+//			printf("Here is the message: '%s'\n",buffer);
 		
 			//update state
 			if (buffer[0]=='t') {
 				n = myrecv(sockfds[i],buffer+1,4, 0);
 				if (n<0) { //disconnected
+					FD_CLR(sockfds[i], &active_fd_set);
+					closesocket(sockfds[i]);
 					sockfds[i]=-1;
 					continue;
 				}
@@ -250,6 +327,8 @@ for (;;) {
 			} else if (buffer[0]=='i') {
 				n = myrecv(sockfds[i],buffer+1,4, 0);
 				if (n<0) { //disconnected
+					FD_CLR(sockfds[i], &active_fd_set);
+					closesocket(sockfds[i]);
 					sockfds[i]=-1;
 					continue;
 				}
@@ -261,6 +340,8 @@ for (;;) {
 			} else if (buffer[0]=='n') {
 				n = myrecv(sockfds[i],buffer+1,4, 0);
 				if (n<0) { //disconnected
+					FD_CLR(sockfds[i], &active_fd_set);
+					closesocket(sockfds[i]);
 					sockfds[i]=-1;
 					continue;
 				}
@@ -272,13 +353,38 @@ for (;;) {
 			} else if (buffer[0]=='s') {
 				n = myrecv(sockfds[i],buffer+1,1, 0);
 				if (n<0) { //disconnected
+					FD_CLR(sockfds[i], &active_fd_set);
+					closesocket(sockfds[i]);
 					sockfds[i]=-1;
 					continue;
 				}
 				state.showatoms=buffer[1]!=0;
 				printf("showatoms: %d\n",state.showatoms);
+			} else if (buffer[0]=='p') { //local state, response also has identifier
+				memcpy(buffer+1,&i, 4);
+				n = myrecv(sockfds[i],buffer+5,sizeof(float)*3, 0);
+				if (n<0) { //disconnected
+					FD_CLR(sockfds[i], &active_fd_set);
+					closesocket(sockfds[i]);
+					sockfds[i]=-1;
+					continue;
+				}
+				memcpy(&(cli_userpos[3*i]), buffer+5, sizeof(float)*3);
+				n+=5;
+				printf ("user pos\n");
+			} else if (buffer[0]=='D') { //Dragging
+				memcpy(buffer+1,&i, 4);
+				n = myrecv(sockfds[i],buffer+5, sizeof(float)*6+4+1, 0);
+				if (n<0) {
+					FD_CLR(sockfds[i], &active_fd_set);
+					closesocket(sockfds[i]);
+					 sockfds[i]=-1;
+					continue;
+				}	
+				n+=5;
+				printf ("user drag\n");
 			} else {
-				fprintf (stderr, "Unknown state request '%c'\n", buffer[0]);
+				fprintf (stderr, "Unknown state request '%c',%d\n", buffer[0], buffer[0]);
 				error ("unknown state" );
 			}
 
@@ -286,11 +392,17 @@ for (;;) {
 				if (sockfds[j]>=0) {
 					if (buffer[0]=='s')
 						n=2;
-					else 
+					else if (buffer[0]=='p')
+						n=4*3+5;
+					else if (buffer[0]=='D')
+						n=6*sizeof(float)+4+1+5;
+					else
 						n=5;
 					n = send(sockfds[j], buffer , n, 0);
 					if (n < 0) {
 						fprintf(stderr, "ERROR writing to socket, closing\n");
+						FD_CLR(sockfds[j], &active_fd_set);
+						closesocket(sockfds[j]);
 						sockfds[j]=-1;
 					}
 				}
@@ -300,10 +412,62 @@ for (;;) {
 	//unblockingly see if new connections were made and add to sockfds
 	if (FD_ISSET (sockfds[0], &read_fd_set)) {
 		if (initNewSocket (secret)) {
-			printf ("Connected to new client\n");
+			printf ("Connected to new client, %d\n", sockfds.back());
 			FD_SET (sockfds.back(), &active_fd_set);
 		}
 	}
+	//now udp
+	if (FD_ISSET (udpSock, &read_fd_set)) {
+//		printf ("fd_isset on udpSock\n");
+//datagrams can be reordered, so we cannot split type and payload
+//reading a subset of the datagrams discards the rest, so we need all of them to be the same size, and read them in one chunk.
+		struct sockaddr_in src_addr;
+		socklen_t addrlen=sizeof(src_addr);
+		n = recvfrom(udpSock,buffer,5+4+4*4*sizeof(float), 0, (sockaddr*)&src_addr, &addrlen);
+		int found=-1;
+		for (std::vector<int>::size_type i=0;i<udp_cli_addrs.size();i++) {
+			if (src_addr.sin_port == udp_cli_addrs[i].sin_port &&
+				src_addr.sin_addr.s_addr== udp_cli_addrs[i].sin_addr.s_addr) {
+					found=i;
+					break;	
+			}
+		}
+		if (found==-1) {
+			udp_cli_addrs.push_back(src_addr);
+			found=udp_cli_addrs.size()-1;
+                        printf ("adding new udp client %d, p=%d\n", found, 
+				src_addr.sin_port);
+
+		}
+//		printf ("received bytes: %d\nOrder: %c\n", n, buffer[0]);
+		if (n<5+3*4*sizeof(float)) {
+			//may receive a 3x4 from the controllers
+			printf ("error, continuing\n");
+			continue; //error
+		}
+		else if (!(buffer[0]=='1' || buffer[0]=='2' || buffer[0]=='h')) {
+			printf ("udp, unknown message type\n");
+			continue;
+		}
+		uint32_t rem;
+		memcpy (&rem, buffer+1, 4);
+//		printf ("received udp '%c' from %d, retransmitting\n", buffer[0], rem);
+		for (std::vector<int>::size_type i=0;i<udp_cli_addrs.size();i++) {
+			//do not sent to originator
+			if (i==found)
+				continue;
+//			printf ("sending to %d\n", i);
+			if (n!=sendto(udpSock, buffer, n, 0, (sockaddr*)&(udp_cli_addrs[i]), sizeof(sockaddr_in))){
+				printf ("Error sending to %d\n", i);
+				udp_cli_addrs[i]=udp_cli_addrs.back();
+				udp_cli_addrs.pop_back();
+				if (found==udp_cli_addrs.size())
+					found=i;
+				i--; 
+				continue;
+			}
+		}
+	}
 }
 #ifdef WIN32
 WSACleanup();