text: 
  
  //todo(erwincoumans): re-use the upcoming b3RobotSimAPI here
  
  #include "PhysicsServerExample.h"
  
  #include "../CommonInterfaces/Common2dCanvasInterface.h"
  #include "PhysicsServerSharedMemory.h"
  #include "Bullet3Common/b3CommandLineArgs.h"
  #include "SharedMemoryCommon.h"
  #include "Bullet3Common/b3Matrix3x3.h"
  #include "../Utils/b3Clock.h"
  #include "../MultiThreading/b3ThreadSupportInterface.h"
  #include "SharedMemoryPublic.h"
  //#define BT_ENABLE_VR
  #ifdef BT_ENABLE_VR
  #include "../RenderingExamples/TinyVRGui.h"
  #endif  //BT_ENABLE_VR
  
  #include "../CommonInterfaces/CommonParameterInterface.h"
  #include "../Importers/ImportURDFDemo/urdfStringSplit.h"
  
  //@todo(erwincoumans) those globals are hacks for a VR demo, move this to Python/pybullet!
  bool gEnablePicking = true;
  bool gEnableTeleporting = true;
  bool gEnableRendering = true;
  bool gActivedVRRealTimeSimulation = false;
  
  bool gEnableSyncPhysicsRendering = true;
  bool gEnableUpdateDebugDrawLines = true;
  static int gCamVisualizerWidth = 228;
  static int gCamVisualizerHeight = 192;
  
  static bool gEnableDefaultKeyboardShortcuts = true;
  static bool gEnableDefaultMousePicking = true;
  
  btScalar gVRTeleportRotZ = 0;
  
  extern int gInternalSimFlags;
  extern bool gResetSimulation;
  int gGraspingController = -1;
  extern btScalar simTimeScalingFactor;
  bool gBatchUserDebugLines = true;
  
  const char* startFileNameVR = "0_VRDemoSettings.txt";
  
  #include <vector>
  
  static void loadCurrentSettingsVR(b3CommandLineArgs& args)
  {
  	//int currentEntry = 0;
  	FILE* f = fopen(startFileNameVR, "r");
  	if (f)
  	{
  		char oneline[1024];
  		char* argv[] = {0, &oneline[0]};
  
  		while (fgets(oneline, 1024, f) != NULL)
  		{
  			char* pos;
  			if ((pos = strchr(oneline, '\n')) != NULL)
  				*pos = '\0';
  			args.addArgs(2, argv);
  		}
  		fclose(f);
  	}
  };
  
  //remember the settings (you don't want to re-tune again and again...)
  
  static void saveCurrentSettingsVR(const btVector3& VRTeleportPos1)
  {
  	FILE* f = fopen(startFileNameVR, "w");
  	if (f)
  	{
  		fprintf(f, "--camPosX= %f\n", VRTeleportPos1[0]);
  		fprintf(f, "--camPosY= %f\n", VRTeleportPos1[1]);
  		fprintf(f, "--camPosZ= %f\n", VRTeleportPos1[2]);
  		fprintf(f, "--camRotZ= %f\n", gVRTeleportRotZ);
  		fclose(f);
  	}
  };
  bool gDebugRenderToggle = false;
  void MotionThreadFunc(void* userPtr, void* lsMemory);
  void* MotionlsMemoryFunc();
  void MotionlsMemoryReleaseFunc(void* ptr);
  #define MAX_MOTION_NUM_THREADS 1
  enum
  {
  	numCubesX = 20,
  	numCubesY = 20
  };
  
  enum TestExampleBrowserCommunicationEnums
  {
  	eRequestTerminateMotion = 13,
  	eMotionIsUnInitialized,
  	eMotionIsInitialized,
  	eMotionInitializationFailed,
  	eMotionHasTerminated
  };
  
  enum MultiThreadedGUIHelperCommunicationEnums
  {
  	eGUIHelperIdle = 13,
  	eGUIHelperRegisterTexture,
  	eGUIHelperRegisterGraphicsShape,
  	eGUIHelperRegisterGraphicsInstance,
  	eGUIHelperCreateCollisionShapeGraphicsObject,
  	eGUIHelperCreateCollisionObjectGraphicsObject,
  	eGUIHelperCreateRigidBodyGraphicsObject,
  	eGUIHelperRemoveAllGraphicsInstances,
  	eGUIHelperCopyCameraImageData,
  	eGUIHelperDisplayCameraImageData,
  	eGUIHelperAutogenerateGraphicsObjects,
  	eGUIUserDebugAddText,
  	eGUIUserDebugAddLine,
  	eGUIUserDebugAddParameter,
  	eGUIUserDebugRemoveItem,
  	eGUIUserDebugRemoveAllItems,
  	eGUIDumpFramesToVideo,
  	eGUIHelperRemoveGraphicsInstance,
  	eGUIHelperChangeGraphicsInstanceRGBAColor,
  	eGUIHelperChangeGraphicsInstanceSpecularColor,
  	eGUIHelperSetVisualizerFlag,
  	eGUIHelperChangeGraphicsInstanceTextureId,
  	eGUIHelperGetShapeIndexFromInstance,
  	eGUIHelperChangeTexture,
  	eGUIHelperRemoveTexture,
  	eGUIHelperSetVisualizerFlagCheckRenderedFrame,
  };
  
  #include <stdio.h>
  //#include "BulletMultiThreaded/PlatformDefinitions.h"
  
  #ifndef _WIN32
  #include "../MultiThreading/b3PosixThreadSupport.h"
  
  b3ThreadSupportInterface* createMotionThreadSupport(int numThreads)
  {
  	b3PosixThreadSupport::ThreadConstructionInfo constructionInfo("MotionThreads",
  																  MotionThreadFunc,
  																  MotionlsMemoryFunc,
  																  MotionlsMemoryReleaseFunc,
  																  numThreads);
  	b3ThreadSupportInterface* threadSupport = new b3PosixThreadSupport(constructionInfo);
  
  	return threadSupport;
  }
  
  #elif defined(_WIN32)
  #include "../MultiThreading/b3Win32ThreadSupport.h"
  
  b3ThreadSupportInterface* createMotionThreadSupport(int numThreads)
  {
  	b3Win32ThreadSupport::Win32ThreadConstructionInfo threadConstructionInfo("MotionThreads", MotionThreadFunc, MotionlsMemoryFunc, MotionlsMemoryReleaseFunc, numThreads);
  	b3Win32ThreadSupport* threadSupport = new b3Win32ThreadSupport(threadConstructionInfo);
  	return threadSupport;
  }
  #endif
  
  enum MyMouseCommandType
  {
  	MyMouseMove = 1,
  	MyMouseButtonDown,
  	MyMouseButtonUp
  
  };
  struct MyMouseCommand
  {
  	btVector3 m_rayFrom;
  	btVector3 m_rayTo;
  	int m_type;
  };
  
  struct MotionArgs
  {
  	MotionArgs()
  		: m_debugDrawFlags(0),
  		  m_physicsServerPtr(0)
  	{
  		for (int i = 0; i < MAX_VR_CONTROLLERS; i++)
  		{
  			m_vrControllerEvents[i].m_numButtonEvents = 0;
  			m_vrControllerEvents[i].m_numMoveEvents = 0;
  			for (int b = 0; b < MAX_VR_BUTTONS; b++)
  			{
  				m_vrControllerEvents[i].m_buttons[b] = 0;
  			}
  			m_vrControllerPos[i].setValue(0, 0, 0);
  			m_vrControllerOrn[i].setValue(0, 0, 0, 1);
  			m_isVrControllerPicking[i] = false;
  			m_isVrControllerDragging[i] = false;
  			m_isVrControllerReleasing[i] = false;
  			m_isVrControllerTeleporting[i] = false;
  		}
  	}
  	b3CriticalSection* m_cs;
  	b3CriticalSection* m_cs2;
  	b3CriticalSection* m_cs3;
  	b3CriticalSection* m_csGUI;
  
  	int m_debugDrawFlags;
  
  	btAlignedObjectArray<MyMouseCommand> m_mouseCommands;
  
  	b3VRControllerEvent m_vrControllerEvents[MAX_VR_CONTROLLERS];
  	b3VRControllerEvent m_sendVrControllerEvents[MAX_VR_CONTROLLERS];
  
  	btAlignedObjectArray<b3KeyboardEvent> m_keyboardEvents;
  	btAlignedObjectArray<b3KeyboardEvent> m_sendKeyEvents;
  	btAlignedObjectArray<b3MouseEvent> m_allMouseEvents;
  	btAlignedObjectArray<b3MouseEvent> m_sendMouseEvents;
  	PhysicsServerSharedMemory* m_physicsServerPtr;
  	b3AlignedObjectArray<b3Vector3> m_positions;
  
  	btVector3 m_vrControllerPos[MAX_VR_CONTROLLERS];
  	btQuaternion m_vrControllerOrn[MAX_VR_CONTROLLERS];
  	bool m_isVrControllerPicking[MAX_VR_CONTROLLERS];
  	bool m_isVrControllerDragging[MAX_VR_CONTROLLERS];
  	bool m_isVrControllerReleasing[MAX_VR_CONTROLLERS];
  	bool m_isVrControllerTeleporting[MAX_VR_CONTROLLERS];
  };
  
  struct MotionThreadLocalStorage
  {
  	int threadId;
  };
  
  float clampedDeltaTime = 0.2;
  
  void MotionThreadFunc(void* userPtr, void* lsMemory)
  {
  	printf("MotionThreadFunc thread started\n");
  	//MotionThreadLocalStorage* localStorage = (MotionThreadLocalStorage*) lsMemory;
  
  	MotionArgs* args = (MotionArgs*)userPtr;
  	//int workLeft = true;
  	b3Clock clock;
  	clock.reset();
  	b3Clock sleepClock;
  	bool init = true;
  	if (init)
  	{
  		unsigned int cachedSharedParam = eMotionIsInitialized;
  
  		args->m_cs->lock();
  		args->m_cs->setSharedParam(0, eMotionIsInitialized);
  		args->m_cs->unlock();
  
  		double deltaTimeInSeconds = 0;
  		int numCmdSinceSleep1ms = 0;
  		unsigned long long int prevTime = clock.getTimeMicroseconds();
  
  		do
  		{
  			{
  				b3Clock::usleep(0);
  			}
  
  			{
  				if (sleepClock.getTimeMilliseconds() > 1)
  				{
  					sleepClock.reset();
  					numCmdSinceSleep1ms = 0;
  				}
  			}
  
  			unsigned long long int curTime = clock.getTimeMicroseconds();
  			unsigned long long int dtMicro = curTime - prevTime;
  			prevTime = curTime;
  #if 1
  			double dt = double(dtMicro) / 1000000.;
  #else
  			double dt = double(clock.getTimeMicroseconds()) / 1000000.;
  			clock.reset();
  #endif
  			deltaTimeInSeconds += dt;
  
  			{
  				//process special controller commands, such as
  				//VR controller button press/release and controller motion
  
  				for (int c = 0; c < MAX_VR_CONTROLLERS; c++)
  				{
  					btVector3 from = args->m_vrControllerPos[c];
  					btMatrix3x3 mat(args->m_vrControllerOrn[c]);
  
  					btScalar pickDistance = 1000.;
  					btVector3 to = from + mat.getColumn(0) * pickDistance;
  					//					btVector3 toY = from+mat.getColumn(1)*pickDistance;
  					//					btVector3 toZ = from+mat.getColumn(2)*pickDistance;
  
  					if (args->m_isVrControllerTeleporting[c])
  					{
  						args->m_isVrControllerTeleporting[c] = false;
  						args->m_physicsServerPtr->pickBody(from, to);
  						args->m_physicsServerPtr->removePickingConstraint();
  					}
  
  					//					if (!gEnableKukaControl)
  					{
  						if (args->m_isVrControllerPicking[c])
  						{
  							args->m_isVrControllerPicking[c] = false;
  							args->m_isVrControllerDragging[c] = true;
  							args->m_physicsServerPtr->pickBody(from, to);
  							//printf("PICK!\n");
  						}
  					}
  
  					if (args->m_isVrControllerDragging[c])
  					{
  						args->m_physicsServerPtr->movePickedBody(from, to);
  						// printf(".");
  					}
  
  					if (args->m_isVrControllerReleasing[c])
  					{
  						args->m_isVrControllerDragging[c] = false;
  						args->m_isVrControllerReleasing[c] = false;
  						args->m_physicsServerPtr->removePickingConstraint();
  						//printf("Release pick\n");
  					}
  				}
  
  				//don't simulate over a huge timestep if we had some interruption (debugger breakpoint etc)
  				if (deltaTimeInSeconds > clampedDeltaTime)
  				{
  					deltaTimeInSeconds = clampedDeltaTime;
  					//b3Warning("Clamp deltaTime from %f to %f",deltaTimeInSeconds, clampedDeltaTime);
  				}
  
  				args->m_csGUI->lock();
  
  				int numSendVrControllers = 0;
  				for (int i = 0; i < MAX_VR_CONTROLLERS; i++)
  				{
  					if (args->m_vrControllerEvents[i].m_numButtonEvents + args->m_vrControllerEvents[i].m_numMoveEvents)
  					{
  						args->m_sendVrControllerEvents[numSendVrControllers++] =
  							args->m_vrControllerEvents[i];
  
  						if (args->m_vrControllerEvents[i].m_numButtonEvents)
  						{
  							for (int b = 0; b < MAX_VR_BUTTONS; b++)
  							{
  								args->m_vrControllerEvents[i].m_buttons[b] &= eButtonIsDown;
  							}
  						}
  						args->m_vrControllerEvents[i].m_numMoveEvents = 0;
  						args->m_vrControllerEvents[i].m_numButtonEvents = 0;
  					}
  				}
  
  				args->m_sendKeyEvents.resize(args->m_keyboardEvents.size());
  				for (int i = 0; i < args->m_keyboardEvents.size(); i++)
  				{
  					args->m_sendKeyEvents[i] = args->m_keyboardEvents[i];
  					if (args->m_keyboardEvents[i].m_keyState & eButtonReleased)
  					{
  						args->m_keyboardEvents[i].m_keyState = 0;
  					}
  					else
  					{
  						args->m_keyboardEvents[i].m_keyState &= ~eButtonTriggered;
  					}
  				}
  				//remove the 'released' events
  				for (int i = args->m_keyboardEvents.size() - 1; i >= 0; i--)
  				{
  					if (args->m_keyboardEvents[i].m_keyState == 0)
  					{
  						args->m_keyboardEvents.removeAtIndex(i);
  					}
  				}
  
  				b3KeyboardEvent* keyEvents = args->m_sendKeyEvents.size() ? &args->m_sendKeyEvents[0] : 0;
  
  				args->m_csGUI->unlock();
  
  				args->m_csGUI->lock();
  				if (gEnableDefaultMousePicking)
  				{
  					for (int i = 0; i < args->m_mouseCommands.size(); i++)
  					{
  						switch (args->m_mouseCommands[i].m_type)
  						{
  							case MyMouseMove:
  							{
  								args->m_physicsServerPtr->movePickedBody(args->m_mouseCommands[i].m_rayFrom, args->m_mouseCommands[i].m_rayTo);
  								break;
  							};
  							case MyMouseButtonDown:
  							{
  								args->m_physicsServerPtr->pickBody(args->m_mouseCommands[i].m_rayFrom, args->m_mouseCommands[i].m_rayTo);
  								break;
  							}
  							case MyMouseButtonUp:
  							{
  								args->m_physicsServerPtr->removePickingConstraint();
  								break;
  							}
  
  							default:
  							{
  							}
  						}
  					}
  				}
  
  				args->m_sendMouseEvents.resize(args->m_allMouseEvents.size());
  				for (int i = 0; i < args->m_allMouseEvents.size(); i++)
  				{
  					args->m_sendMouseEvents[i] = args->m_allMouseEvents[i];
  				}
  				b3MouseEvent* mouseEvents = args->m_sendMouseEvents.size() ? &args->m_sendMouseEvents[0] : 0;
  
  				args->m_allMouseEvents.clear();
  				args->m_mouseCommands.clear();
  				args->m_csGUI->unlock();
  
  				{
  					args->m_physicsServerPtr->stepSimulationRealTime(deltaTimeInSeconds, args->m_sendVrControllerEvents, numSendVrControllers, keyEvents, args->m_sendKeyEvents.size(), mouseEvents, args->m_sendMouseEvents.size());
  				}
  				{
  					if (gEnableUpdateDebugDrawLines)
  					{
  						args->m_csGUI->lock();
  						args->m_physicsServerPtr->physicsDebugDraw(args->m_debugDrawFlags);
  						gEnableUpdateDebugDrawLines = false;
  						args->m_csGUI->unlock();
  					}
  				}
  				deltaTimeInSeconds = 0;
  			}
  
  			{
  				args->m_physicsServerPtr->processClientCommands();
  				numCmdSinceSleep1ms++;
  			}
  
  			args->m_physicsServerPtr->reportNotifications();
  
  			args->m_cs->lock();
  			cachedSharedParam = args->m_cs->getSharedParam(0);
  			args->m_cs->unlock();
  
  		} while (cachedSharedParam != eRequestTerminateMotion);
  	}
  	else
  	{
  		args->m_cs->lock();
  		args->m_cs->setSharedParam(0, eMotionInitializationFailed);
  		args->m_cs->unlock();
  	}
  
  	args->m_physicsServerPtr->disconnectSharedMemory(true);
  	//do nothing
  }
  
  void* MotionlsMemoryFunc()
  {
  	//don't create local store memory, just return 0
  	return new MotionThreadLocalStorage;
  }
  
  void MotionlsMemoryReleaseFunc(void* ptr)
  {
  	MotionThreadLocalStorage* p = (MotionThreadLocalStorage*)ptr;
  	delete p;
  }
  
  struct UserDebugDrawLine
  {
  	double m_debugLineFromXYZ[3];
  	double m_debugLineToXYZ[3];
  	double m_debugLineColorRGB[3];
  	double m_lineWidth;
  
  	double m_lifeTime;
  	int m_itemUniqueId;
  	int m_trackingVisualShapeIndex;
  	int m_replaceItemUid;
  };
  
  struct UserDebugParameter
  {
  	char m_text[1024];
  	double m_rangeMin;
  	double m_rangeMax;
  	btScalar m_value;
  	int m_itemUniqueId;
  };
  
  struct UserDebugText
  {
  	char m_text[1024];
  	double m_textPositionXYZ1[3];
  	double m_textColorRGB[3];
  	double textSize;
  
  	double m_lifeTime;
  	int m_itemUniqueId;
  	double m_textOrientation[4];
  	int m_trackingVisualShapeIndex;
  	int m_optionFlags;
  };
  
  struct LineSegment
  {
  	btVector3 m_from;
  	btVector3 m_to;
  };
  
  struct ColorWidth
  {
  	btVector3FloatData m_color;
  	int width;
  	int getHash() const
  	{
  		unsigned char r = (unsigned char)m_color.m_floats[0] * 255;
  		unsigned char g = (unsigned char)m_color.m_floats[1] * 255;
  		unsigned char b = (unsigned char)m_color.m_floats[2] * 255;
  		unsigned char w = width;
  		return r + (256 * g) + (256 * 256 * b) + (256 * 256 * 256 * w);
  	}
  	bool equals(const ColorWidth& other) const
  	{
  		bool same = ((width == other.width) && (m_color.m_floats[0] == other.m_color.m_floats[0]) &&
  					 (m_color.m_floats[1] == other.m_color.m_floats[1]) &&
  					 (m_color.m_floats[2] == other.m_color.m_floats[2]));
  		return same;
  	}
  };
  
  ATTRIBUTE_ALIGNED16(class)
  MultithreadedDebugDrawer : public btIDebugDraw
  {
  	struct GUIHelperInterface* m_guiHelper;
  	int m_debugMode;
  
  	btAlignedObjectArray<btAlignedObjectArray<unsigned int> > m_sortedIndices;
  	btAlignedObjectArray<btAlignedObjectArray<btVector3FloatData> > m_sortedLines;
  	btHashMap<ColorWidth, int> m_hashedLines;
  
  public:
  	void drawDebugDrawerLines()
  	{
  		if (m_hashedLines.size())
  		{
  			for (int i = 0; i < m_hashedLines.size(); i++)
  			{
  				ColorWidth cw = m_hashedLines.getKeyAtIndex(i);
  				int index = *m_hashedLines.getAtIndex(i);
  				int stride = sizeof(btVector3FloatData);
  				const float* positions = &m_sortedLines[index][0].m_floats[0];
  				int numPoints = m_sortedLines[index].size();
  				const unsigned int* indices = &m_sortedIndices[index][0];
  				int numIndices = m_sortedIndices[index].size();
  				m_guiHelper->getRenderInterface()->drawLines(positions, cw.m_color.m_floats, numPoints, stride, indices, numIndices, cw.width);
  			}
  		}
  	}
  	MultithreadedDebugDrawer(GUIHelperInterface * guiHelper)
  		: m_guiHelper(guiHelper),
  		  m_debugMode(0)
  	{
  	}
  	virtual ~MultithreadedDebugDrawer()
  	{
  	}
  	virtual void drawLine(const btVector3& from, const btVector3& to, const btVector3& color)
  	{
  		{
  			ColorWidth cw;
  			color.serializeFloat(cw.m_color);
  			cw.width = 1;
  			int index = -1;
  
  			int* indexPtr = m_hashedLines.find(cw);
  			if (indexPtr)
  			{
  				index = *indexPtr;
  			}
  			else
  			{
  				index = m_sortedLines.size();
  				m_sortedLines.expand();
  				m_sortedIndices.expand();
  				m_hashedLines.insert(cw, index);
  			}
  			btAssert(index >= 0);
  			if (index >= 0)
  			{
  				btVector3FloatData from1, toX1;
  				m_sortedIndices[index].push_back(m_sortedLines[index].size());
  				from.serializeFloat(from1);
  				m_sortedLines[index].push_back(from1);
  				m_sortedIndices[index].push_back(m_sortedLines[index].size());
  				to.serializeFloat(toX1);
  				m_sortedLines[index].push_back(toX1);
  			}
  		}
  	}
  
  	virtual void drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, const btVector3& color)
  	{
  		drawLine(PointOnB, PointOnB + normalOnB * distance, color);
  		btVector3 ncolor(0, 0, 0);
  		drawLine(PointOnB, PointOnB + normalOnB * 0.01, ncolor);
  	}
  
  	virtual void reportErrorWarning(const char* warningString)
  	{
  	}
  	virtual void draw3dText(const btVector3& location, const char* textString)
  	{
  	}
  	virtual void setDebugMode(int debugMode)
  	{
  		m_debugMode = debugMode;
  	}
  
  	virtual int getDebugMode() const
  	{
  		return m_debugMode;
  	}
  
  	virtual void clearLines()
  	{
  		m_hashedLines.clear();
  		m_sortedIndices.clear();
  		m_sortedLines.clear();
  	}
  	virtual void flushLines()
  	{
  	}
  };
  
  class MultiThreadedOpenGLGuiHelper : public GUIHelperInterface
  {
  	//	CommonGraphicsApp* m_app;
  
  	b3CriticalSection* m_cs;
  	b3CriticalSection* m_cs2;
  	b3CriticalSection* m_cs3;
  	b3CriticalSection* m_csGUI;
  
  public:
  	MultithreadedDebugDrawer* m_debugDraw;
  	void drawDebugDrawerLines()
  	{
  		if (m_debugDraw)
  		{
  			m_debugDraw->drawDebugDrawerLines();
  		}
  	}
  	GUIHelperInterface* m_childGuiHelper;
  
  	int m_uidGenerator;
  	const unsigned char* m_texels;
  	int m_textureWidth;
  	int m_textureHeight;
  
  	int m_shapeIndex;
  	const float* m_position;
  	const float* m_quaternion;
  	const float* m_color;
  	const float* m_scaling;
  
  	const float* m_vertices;
  	int m_numvertices;
  	const int* m_indices;
  	int m_numIndices;
  	int m_primitiveType;
  	int m_textureId;
  	int m_instanceId;
  	bool m_skipGraphicsUpdate;
  
  	void mainThreadRelease()
  	{
  		BT_PROFILE("mainThreadRelease");
  
  		getCriticalSection()->setSharedParam(1, eGUIHelperIdle);
  		getCriticalSection3()->lock();
  		getCriticalSection2()->unlock();
  		getCriticalSection()->lock();
  		getCriticalSection2()->lock();
  		getCriticalSection()->unlock();
  		getCriticalSection3()->unlock();
  	}
  
  	void workerThreadWait()
  	{
  		BT_PROFILE("workerThreadWait");
  
  		if (m_skipGraphicsUpdate)
  		{
  			getCriticalSection()->setSharedParam(1, eGUIHelperIdle);
  			m_cs->unlock();
  			return;
  		}
  		m_cs2->lock();
  		m_cs->unlock();
  		m_cs2->unlock();
  		m_cs3->lock();
  		m_cs3->unlock();
  
  		while (m_cs->getSharedParam(1) != eGUIHelperIdle)
  		{
  			b3Clock::usleep(0);
  		}
  	}
  
  	MultiThreadedOpenGLGuiHelper(CommonGraphicsApp* app, GUIHelperInterface* guiHelper, int skipGraphicsUpdate)
  		:  //m_app(app),
  		  m_cs(0),
  		  m_cs2(0),
  		  m_cs3(0),
  		  m_csGUI(0),
  		  m_debugDraw(0),
  		  m_uidGenerator(0),
  		  m_texels(0),
  		  m_shapeIndex(-1),
  		  m_textureId(-1),
  		  m_instanceId(-1),
  		  m_skipGraphicsUpdate(skipGraphicsUpdate)
  	{
  		m_childGuiHelper = guiHelper;
  	}
  
  	virtual ~MultiThreadedOpenGLGuiHelper()
  	{
  		//delete m_childGuiHelper;
  		if (m_debugDraw)
  		{
  			delete m_debugDraw;
  			m_debugDraw = 0;
  		}
  
  		for (int i = 0; i < m_userDebugParams.size(); i++)
  		{
  			delete m_userDebugParams[i];
  		}
  		m_userDebugParams.clear();
  	}
  
  	void setCriticalSection(b3CriticalSection* cs)
  	{
  		m_cs = cs;
  	}
  
  	b3CriticalSection* getCriticalSection()
  	{
  		return m_cs;
  	}
  
  	void setCriticalSection2(b3CriticalSection* cs)
  	{
  		m_cs2 = cs;
  	}
  
  	b3CriticalSection* getCriticalSection2()
  	{
  		return m_cs2;
  	}
  
  	void setCriticalSection3(b3CriticalSection* cs)
  	{
  		m_cs3 = cs;
  	}
  
  	void setCriticalSectionGUI(b3CriticalSection* cs)
  	{
  		m_csGUI = cs;
  	}
  
  	b3CriticalSection* getCriticalSection3()
  	{
  		return m_cs3;
  	}
  	btRigidBody* m_body;
  	btVector3 m_color3;
  	virtual void createRigidBodyGraphicsObject(btRigidBody* body, const btVector3& color)
  	{
  		m_body = body;
  		m_color3 = color;
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperCreateRigidBodyGraphicsObject);
  		workerThreadWait();
  	}
  
  	btCollisionObject* m_obj;
  	btVector3 m_color2;
  
  	virtual void createCollisionObjectGraphicsObject(btCollisionObject* obj, const btVector3& color)
  	{
  		m_obj = obj;
  		m_color2 = color;
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperCreateCollisionObjectGraphicsObject);
  		workerThreadWait();
  	}
  
  	btCollisionShape* m_colShape;
  	virtual void createCollisionShapeGraphicsObject(btCollisionShape* collisionShape)
  	{
  		m_colShape = collisionShape;
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperCreateCollisionShapeGraphicsObject);
  		workerThreadWait();
  	}
  
  	virtual void syncPhysicsToGraphics(const btDiscreteDynamicsWorld* rbWorld)
  	{
  		//this check is to prevent a crash, in case we removed all graphics instances, but there are still physics objects.
  		//the check will be obsolete, once we have a better/safer way of synchronizing physics->graphics transforms
  		if (m_childGuiHelper->getRenderInterface() && m_childGuiHelper->getRenderInterface()->getTotalNumInstances() > 0)
  		{
  			m_childGuiHelper->syncPhysicsToGraphics(rbWorld);
  		}
  	}
  
  	virtual void render(const btDiscreteDynamicsWorld* rbWorld)
  	{
  		m_childGuiHelper->render(0);
  	}
  
  	virtual void createPhysicsDebugDrawer(btDiscreteDynamicsWorld* rbWorld)
  	{
  		btAssert(rbWorld);
  		if (m_debugDraw)
  		{
  			delete m_debugDraw;
  			m_debugDraw = 0;
  		}
  
  		m_debugDraw = new MultithreadedDebugDrawer(this);
  
  		rbWorld->setDebugDrawer(m_debugDraw);
  
  		//m_childGuiHelper->createPhysicsDebugDrawer(rbWorld);
  	}
  
  	int m_removeTextureUid;
  
  	virtual void removeTexture(int textureUid)
  	{
  		m_removeTextureUid = textureUid;
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperRemoveTexture);
  
  		workerThreadWait();
  	}
  
  	virtual int registerTexture(const unsigned char* texels, int width, int height)
  	{
  		m_texels = texels;
  		m_textureWidth = width;
  		m_textureHeight = height;
  
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperRegisterTexture);
  
  		workerThreadWait();
  
  		return m_textureId;
  	}
  	virtual int registerGraphicsShape(const float* vertices, int numvertices, const int* indices, int numIndices, int primitiveType, int textureId)
  	{
  		m_vertices = vertices;
  		m_numvertices = numvertices;
  		m_indices = indices;
  		m_numIndices = numIndices;
  		m_primitiveType = primitiveType;
  		m_textureId = textureId;
  
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperRegisterGraphicsShape);
  		workerThreadWait();
  
  		return m_shapeIndex;
  	}
  
  	int m_visualizerFlag;
  	int m_visualizerEnable;
  	int m_renderedFrames;
  
  	void setVisualizerFlag(int flag, int enable)
  	{
  		m_visualizerFlag = flag;
  		m_visualizerEnable = enable;
  
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperSetVisualizerFlag);
  		workerThreadWait();
  	}
  
  	void setVisualizerFlagCallback(VisualizerFlagCallback callback)
  	{
  		m_childGuiHelper->setVisualizerFlagCallback(callback);
  	}
  
  	virtual int registerGraphicsInstance(int shapeIndex, const float* position, const float* quaternion, const float* color, const float* scaling)
  	{
  		m_shapeIndex = shapeIndex;
  		m_position = position;
  		m_quaternion = quaternion;
  		m_color = color;
  		m_scaling = scaling;
  
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperRegisterGraphicsInstance);
  		workerThreadWait();
  		return m_instanceId;
  	}
  
  	virtual void removeAllGraphicsInstances()
  	{
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperRemoveAllGraphicsInstances);
  		workerThreadWait();
  	}
  
  	int m_graphicsInstanceRemove;
  	virtual void removeGraphicsInstance(int graphicsUid)
  	{
  		m_graphicsInstanceRemove = graphicsUid;
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperRemoveGraphicsInstance);
  		workerThreadWait();
  	}
  
  	int m_getShapeIndex_instance;
  	int getShapeIndex_shapeIndex;
  
  	virtual int getShapeIndexFromInstance(int instance)
  	{
  		m_getShapeIndex_instance = instance;
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperGetShapeIndexFromInstance);
  		getShapeIndex_shapeIndex = -1;
  		workerThreadWait();
  		return getShapeIndex_shapeIndex;
  	}
  
  	int m_graphicsInstanceChangeTextureId;
  	int m_graphicsInstanceChangeTextureShapeIndex;
  	virtual void replaceTexture(int shapeIndex, int textureUid)
  	{
  		m_graphicsInstanceChangeTextureShapeIndex = shapeIndex;
  		m_graphicsInstanceChangeTextureId = textureUid;
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperChangeGraphicsInstanceTextureId);
  		workerThreadWait();
  	}
  
  	int m_changeTextureUniqueId;
  	const unsigned char* m_changeTextureRgbTexels;
  	int m_changeTextureWidth;
  	int m_changeTextureHeight;
  
  	virtual void changeTexture(int textureUniqueId, const unsigned char* rgbTexels, int width, int height)
  	{
  		m_changeTextureUniqueId = textureUniqueId;
  		m_changeTextureRgbTexels = rgbTexels;
  		m_changeTextureWidth = width;
  		m_changeTextureHeight = height;
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperChangeTexture);
  		workerThreadWait();
  	}
  
  	double m_rgbaColor[4];
  	int m_graphicsInstanceChangeColor;
  	virtual void changeRGBAColor(int instanceUid, const double rgbaColor[4])
  	{
  		m_graphicsInstanceChangeColor = instanceUid;
  		m_rgbaColor[0] = rgbaColor[0];
  		m_rgbaColor[1] = rgbaColor[1];
  		m_rgbaColor[2] = rgbaColor[2];
  		m_rgbaColor[3] = rgbaColor[3];
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperChangeGraphicsInstanceRGBAColor);
  		workerThreadWait();
  	}
  
  	double m_specularColor[3];
  	int m_graphicsInstanceChangeSpecular;
  	virtual void changeSpecularColor(int instanceUid, const double specularColor[3])
  	{
  		m_graphicsInstanceChangeSpecular = instanceUid;
  		m_specularColor[0] = specularColor[0];
  		m_specularColor[1] = specularColor[1];
  		m_specularColor[2] = specularColor[2];
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperChangeGraphicsInstanceSpecularColor);
  		workerThreadWait();
  	}
  
  	virtual Common2dCanvasInterface* get2dCanvasInterface()
  	{
  		return m_childGuiHelper->get2dCanvasInterface();
  	}
  
  	virtual CommonParameterInterface* getParameterInterface()
  	{
  		return 0;
  	}
  
  	virtual CommonRenderInterface* getRenderInterface()
  	{
  		return m_childGuiHelper->getRenderInterface();
  	}
  
  	virtual CommonGraphicsApp* getAppInterface()
  	{
  		return m_childGuiHelper->getAppInterface();
  	}
  
  	virtual void setUpAxis(int axis)
  	{
  		m_childGuiHelper->setUpAxis(axis);
  	}
  	virtual void resetCamera(float camDist, float yaw, float pitch, float camPosX, float camPosY, float camPosZ)
  	{
  		m_childGuiHelper->resetCamera(camDist, yaw, pitch, camPosX, camPosY, camPosZ);
  	}
  
  	virtual bool getCameraInfo(int* width, int* height, float viewMatrix[16], float projectionMatrix[16], float camUp[3], float camForward[3], float hor[3], float vert[3], float* yaw, float* pitch, float* camDist, float camTarget[3]) const
  	{
  		return m_childGuiHelper->getCameraInfo(width, height, viewMatrix, projectionMatrix, camUp, camForward, hor, vert, yaw, pitch, camDist, camTarget);
  	}
  
  	float m_viewMatrix[16];
  	float m_projectionMatrix[16];
  	unsigned char* m_pixelsRGBA;
  	int m_rgbaBufferSizeInPixels;
  	float* m_depthBuffer;
  	int m_depthBufferSizeInPixels;
  	int* m_segmentationMaskBuffer;
  	int m_segmentationMaskBufferSizeInPixels;
  	int m_startPixelIndex;
  	int m_destinationWidth;
  	int m_destinationHeight;
  	int* m_numPixelsCopied;
  
  	virtual void copyCameraImageData(const float viewMatrix[16], const float projectionMatrix[16],
  									 unsigned char* pixelsRGBA, int rgbaBufferSizeInPixels,
  									 float* depthBuffer, int depthBufferSizeInPixels,
  									 int* segmentationMaskBuffer, int segmentationMaskBufferSizeInPixels,
  									 int startPixelIndex, int destinationWidth,
  									 int destinationHeight, int* numPixelsCopied)
  	{
  		m_cs->lock();
  		for (int i = 0; i < 16; i++)
  		{
  			m_viewMatrix[i] = viewMatrix[i];
  			m_projectionMatrix[i] = projectionMatrix[i];
  		}
  		m_pixelsRGBA = pixelsRGBA;
  		m_rgbaBufferSizeInPixels = rgbaBufferSizeInPixels;
  		m_depthBuffer = depthBuffer;
  		m_depthBufferSizeInPixels = depthBufferSizeInPixels;
  		m_segmentationMaskBuffer = segmentationMaskBuffer;
  		m_segmentationMaskBufferSizeInPixels = segmentationMaskBufferSizeInPixels;
  		m_startPixelIndex = startPixelIndex;
  		m_destinationWidth = destinationWidth;
  		m_destinationHeight = destinationHeight;
  		m_numPixelsCopied = numPixelsCopied;
  
  		m_cs->setSharedParam(1, eGUIHelperCopyCameraImageData);
  		workerThreadWait();
  	}
  
  	virtual void debugDisplayCameraImageData(const float viewMatrix[16], const float projectionMatrix[16],
  											 unsigned char* pixelsRGBA, int rgbaBufferSizeInPixels,
  											 float* depthBuffer, int depthBufferSizeInPixels,
  											 int* segmentationMaskBuffer, int segmentationMaskBufferSizeInPixels,
  											 int startPixelIndex, int destinationWidth, int destinationHeight, int* numPixelsCopied)
  	{
  		m_cs->lock();
  		for (int i = 0; i < 16; i++)
  		{
  			m_viewMatrix[i] = viewMatrix[i];
  			m_projectionMatrix[i] = projectionMatrix[i];
  		}
  		m_pixelsRGBA = pixelsRGBA;
  		m_rgbaBufferSizeInPixels = rgbaBufferSizeInPixels;
  		m_depthBuffer = depthBuffer;
  		m_depthBufferSizeInPixels = depthBufferSizeInPixels;
  		m_segmentationMaskBuffer = segmentationMaskBuffer;
  		m_segmentationMaskBufferSizeInPixels = segmentationMaskBufferSizeInPixels;
  		m_startPixelIndex = startPixelIndex;
  		m_destinationWidth = destinationWidth;
  		m_destinationHeight = destinationHeight;
  		m_numPixelsCopied = numPixelsCopied;
  
  		m_cs->setSharedParam(1, eGUIHelperDisplayCameraImageData);
  		workerThreadWait();
  	}
  
  	virtual void setProjectiveTextureMatrices(const float viewMatrix[16], const float projectionMatrix[16])
  	{
  		if (m_childGuiHelper->getAppInterface() && m_childGuiHelper->getAppInterface()->m_renderer)
  		{
  			m_childGuiHelper->getAppInterface()->m_renderer->setProjectiveTextureMatrices(viewMatrix, projectionMatrix);
  		}
  	}
  
  	virtual void setProjectiveTexture(bool useProjectiveTexture)
  	{
  		if (m_childGuiHelper->getAppInterface() && m_childGuiHelper->getAppInterface()->m_renderer)
  		{
  			m_childGuiHelper->getAppInterface()->m_renderer->setProjectiveTexture(useProjectiveTexture);
  		}
  	}
  
  	btDiscreteDynamicsWorld* m_dynamicsWorld;
  
  	virtual void autogenerateGraphicsObjects(btDiscreteDynamicsWorld* rbWorld)
  	{
  		m_dynamicsWorld = rbWorld;
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIHelperAutogenerateGraphicsObjects);
  		workerThreadWait();
  	}
  
  	virtual void drawText3D(const char* txt, float posX, float posZY, float posZ, float size)
  	{
  	}
  
  	virtual void drawText3D(const char* txt, float position[3], float orientation[4], float color[4], float size, int optionFlag)
  	{
  	}
  
  	btAlignedObjectArray<UserDebugText> m_userDebugText;
  
  	UserDebugText m_tmpText;
  	int m_resultUserDebugTextUid;
  
  	virtual int addUserDebugText3D(const char* txt, const double positionXYZ[3], const double orientation[4], const double textColorRGB[3], double size, double lifeTime, int trackingVisualShapeIndex, int optionFlags, int replaceItemUid)
  	{
  		if (replaceItemUid >= 0)
  		{
  			m_tmpText.m_itemUniqueId = replaceItemUid;
  		}
  		else
  		{
  			m_tmpText.m_itemUniqueId = m_uidGenerator++;
  		}
  		m_tmpText.m_lifeTime = lifeTime;
  		m_tmpText.textSize = size;
  		//int len = strlen(txt);
  		strcpy(m_tmpText.m_text, txt);
  		m_tmpText.m_textPositionXYZ1[0] = positionXYZ[0];
  		m_tmpText.m_textPositionXYZ1[1] = positionXYZ[1];
  		m_tmpText.m_textPositionXYZ1[2] = positionXYZ[2];
  
  		m_tmpText.m_textOrientation[0] = orientation[0];
  		m_tmpText.m_textOrientation[1] = orientation[1];
  		m_tmpText.m_textOrientation[2] = orientation[2];
  		m_tmpText.m_textOrientation[3] = orientation[3];
  
  		m_tmpText.m_textColorRGB[0] = textColorRGB[0];
  		m_tmpText.m_textColorRGB[1] = textColorRGB[1];
  		m_tmpText.m_textColorRGB[2] = textColorRGB[2];
  
  		m_tmpText.m_trackingVisualShapeIndex = trackingVisualShapeIndex;
  
  		m_tmpText.m_optionFlags = optionFlags;
  		m_tmpText.m_textOrientation[0] = orientation[0];
  		m_tmpText.m_textOrientation[1] = orientation[1];
  		m_tmpText.m_textOrientation[2] = orientation[2];
  		m_tmpText.m_textOrientation[3] = orientation[3];
  
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIUserDebugAddText);
  		m_resultUserDebugTextUid = -1;
  		workerThreadWait();
  
  		return m_resultUserDebugTextUid;
  	}
  
  	btAlignedObjectArray<UserDebugParameter*> m_userDebugParams;
  	UserDebugParameter m_tmpParam;
  
  	virtual int readUserDebugParameter(int itemUniqueId, double* value)
  	{
  		for (int i = 0; i < m_userDebugParams.size(); i++)
  		{
  			if (m_userDebugParams[i]->m_itemUniqueId == itemUniqueId)
  			{
  				*value = m_userDebugParams[i]->m_value;
  				return 1;
  			}
  		}
  		return 0;
  	}
  	int m_userDebugParamUid;
  
  	virtual int addUserDebugParameter(const char* txt, double rangeMin, double rangeMax, double startValue)
  	{
  		strcpy(m_tmpParam.m_text, txt);
  		m_tmpParam.m_rangeMin = rangeMin;
  		m_tmpParam.m_rangeMax = rangeMax;
  		m_tmpParam.m_value = startValue;
  		m_tmpParam.m_itemUniqueId = m_uidGenerator++;
  
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIUserDebugAddParameter);
  		m_userDebugParamUid = -1;
  		workerThreadWait();
  
  		return m_userDebugParamUid;
  	}
  
  	btAlignedObjectArray<UserDebugDrawLine> m_userDebugLines;
  	UserDebugDrawLine m_tmpLine;
  	int m_resultDebugLineUid;
  
  	virtual int addUserDebugLine(const double debugLineFromXYZ[3], const double debugLineToXYZ[3], const double debugLineColorRGB[3], double lineWidth, double lifeTime, int trackingVisualShapeIndex, int replaceItemUid)
  	{
  		m_tmpLine.m_lifeTime = lifeTime;
  		m_tmpLine.m_lineWidth = lineWidth;
  
  		m_tmpLine.m_itemUniqueId = replaceItemUid < 0 ? m_uidGenerator++ : replaceItemUid;
  		m_tmpLine.m_debugLineFromXYZ[0] = debugLineFromXYZ[0];
  		m_tmpLine.m_debugLineFromXYZ[1] = debugLineFromXYZ[1];
  		m_tmpLine.m_debugLineFromXYZ[2] = debugLineFromXYZ[2];
  
  		m_tmpLine.m_debugLineToXYZ[0] = debugLineToXYZ[0];
  		m_tmpLine.m_debugLineToXYZ[1] = debugLineToXYZ[1];
  		m_tmpLine.m_debugLineToXYZ[2] = debugLineToXYZ[2];
  
  		m_tmpLine.m_debugLineColorRGB[0] = debugLineColorRGB[0];
  		m_tmpLine.m_debugLineColorRGB[1] = debugLineColorRGB[1];
  		m_tmpLine.m_debugLineColorRGB[2] = debugLineColorRGB[2];
  		m_tmpLine.m_trackingVisualShapeIndex = trackingVisualShapeIndex;
  		m_tmpLine.m_replaceItemUid = replaceItemUid;
  
  		//don't block when replacing an item
  		if (replaceItemUid>=0 && replaceItemUid<m_userDebugLines.size())
  		{
  			//find the right slot
  
  			int slot=-1;
  			for (int i=0;i<m_userDebugLines.size();i++)
  			{
  				if (replaceItemUid == m_userDebugLines[i].m_itemUniqueId)
  				{
  						slot = i;
  				}
  			}
  
  			if (slot>=0)
  			{
  				m_userDebugLines[slot] = m_tmpLine;
  			}
  			m_resultDebugLineUid = replaceItemUid;
  		}
  		else
  		{
  
  			m_cs->lock();
  			m_cs->setSharedParam(1, eGUIUserDebugAddLine);
  			m_resultDebugLineUid = -1;
  			workerThreadWait();
  		}
  		return m_resultDebugLineUid;
  	}
  
  	int m_removeDebugItemUid;
  
  	virtual void removeUserDebugItem(int debugItemUniqueId)
  	{
  		m_removeDebugItemUid = debugItemUniqueId;
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIUserDebugRemoveItem);
  		workerThreadWait();
  	}
  	virtual void removeAllUserDebugItems()
  	{
  		m_cs->lock();
  		m_cs->setSharedParam(1, eGUIUserDebugRemoveAllItems);
  		workerThreadWait();
  	}
  
  	const char* m_mp4FileName;
  	virtual void dumpFramesToVideo(const char* mp4FileName)
  	{
  		m_cs->lock();
  		m_mp4FileName = mp4FileName;
  		m_cs->setSharedParam(1, eGUIDumpFramesToVideo);
  		workerThreadWait();
  		m_mp4FileName = 0;
  	}
  };
  
  class PhysicsServerExample : public SharedMemoryCommon
  {
  	PhysicsServerSharedMemory m_physicsServer;
  	b3ThreadSupportInterface* m_threadSupport;
  	MotionArgs m_args[MAX_MOTION_NUM_THREADS];
  	MultiThreadedOpenGLGuiHelper* m_multiThreadedHelper;
  	bool m_wantsShutdown;
  
  	bool m_isConnected;
  	btClock m_clock;
  	bool m_replay;
  
  	struct Common2dCanvasInterface* m_canvas;
  	int m_canvasRGBIndex;
  	int m_canvasDepthIndex;
  	int m_canvasSegMaskIndex;
  
  	//	int m_options;
  
  #ifdef BT_ENABLE_VR
  	TinyVRGui* m_tinyVrGui;
  #endif
  
  	int m_renderedFrames;
  
  public:
  	PhysicsServerExample(MultiThreadedOpenGLGuiHelper* helper, CommandProcessorCreationInterface* commandProcessorCreator, SharedMemoryInterface* sharedMem = 0, int options = 0);
  
  	virtual ~PhysicsServerExample();
  
  	virtual void initPhysics();
  
  	virtual void stepSimulation(float deltaTime);
  
  	virtual void updateGraphics();
  
  	void enableCommandLogging()
  	{
  		m_physicsServer.enableCommandLogging(true, "BulletPhysicsCommandLog.bin");
  	}
  
  	void replayFromLogFile()
  	{
  		m_replay = true;
  		m_physicsServer.replayFromLogFile("BulletPhysicsCommandLog.bin");
  	}
  
  	virtual void resetCamera()
  	{
  		float dist = 5;
  		float pitch = -35;
  		float yaw = 50;
  		float targetPos[3] = {0, 0, 0};  //-3,2.8,-2.5};
  		m_guiHelper->resetCamera(dist, yaw, pitch, targetPos[0], targetPos[1], targetPos[2]);
  	}
  
  	virtual bool wantsTermination();
  	virtual bool isConnected();
  	virtual void renderScene();
  	void drawUserDebugLines();
  	virtual void exitPhysics();
  
  	virtual void physicsDebugDraw(int debugFlags);
  
  	btVector3 getRayTo(int x, int y);
  
  	virtual void vrControllerButtonCallback(int controllerId, int button, int state, float pos[4], float orn[4]);
  	virtual void vrControllerMoveCallback(int controllerId, float pos[4], float orn[4], float analogAxis, float auxAnalogAxes[10]);
  	virtual void vrHMDMoveCallback(int controllerId, float pos[4], float orientation[4]);
  	virtual void vrGenericTrackerMoveCallback(int controllerId, float pos[4], float orientation[4]);
  
  	virtual bool mouseMoveCallback(float x, float y)
  	{
  		if (m_replay)
  			return false;
  
  		CommonRenderInterface* renderer = m_multiThreadedHelper->m_childGuiHelper->getRenderInterface();  // m_guiHelper->getRenderInterface();
  
  		if (!renderer)
  		{
  			return false;
  		}
  
  		b3MouseEvent event;
  		event.m_buttonState = 0;
  		event.m_buttonIndex = -1;
  		event.m_mousePosX = x;
  		event.m_mousePosY = y;
  		event.m_eventType = MOUSE_MOVE_EVENT;
  		m_args[0].m_csGUI->lock();
  		m_args[0].m_allMouseEvents.push_back(event);
  		m_args[0].m_csGUI->unlock();
  
  		btVector3 rayTo = getRayTo(int(x), int(y));
  		btVector3 rayFrom;
  		renderer->getActiveCamera()->getCameraPosition(rayFrom);
  
  		MyMouseCommand cmd;
  		cmd.m_rayFrom = rayFrom;
  		cmd.m_rayTo = rayTo;
  		cmd.m_type = MyMouseMove;
  		m_args[0].m_csGUI->lock();
  		m_args[0].m_mouseCommands.push_back(cmd);
  		m_args[0].m_csGUI->unlock();
  
  		return false;
  	};
  
  	virtual bool mouseButtonCallback(int button, int state, float x, float y)
  	{
  		if (m_replay)
  			return false;
  
  		CommonRenderInterface* renderer = m_guiHelper->getRenderInterface();
  
  		if (!renderer)
  		{
  			return false;
  		}
  
  		CommonWindowInterface* window = m_guiHelper->getAppInterface()->m_window;
  
  		b3MouseEvent event;
  		event.m_buttonIndex = button;
  		event.m_mousePosX = x;
  		event.m_mousePosY = y;
  		event.m_eventType = MOUSE_BUTTON_EVENT;
  		if (state)
  		{
  			event.m_buttonState = eButtonIsDown + eButtonTriggered;
  		}
  		else
  		{
  			event.m_buttonState = eButtonReleased;
  		}
  
  		m_args[0].m_csGUI->lock();
  		m_args[0].m_allMouseEvents.push_back(event);
  		m_args[0].m_csGUI->unlock();
  
  		if (state == 1)
  		{
  			if (button == 0 && (!window->isModifierKeyPressed(B3G_ALT) && !window->isModifierKeyPressed(B3G_CONTROL)))
  			{
  				btVector3 camPos;
  				renderer->getActiveCamera()->getCameraPosition(camPos);
  
  				btVector3 rayFrom = camPos;
  				btVector3 rayTo = getRayTo(int(x), int(y));
  
  				MyMouseCommand cmd;
  				cmd.m_rayFrom = rayFrom;
  				cmd.m_rayTo = rayTo;
  				cmd.m_type = MyMouseButtonDown;
  
  				m_args[0].m_csGUI->lock();
  				m_args[0].m_mouseCommands.push_back(cmd);
  				m_args[0].m_csGUI->unlock();
  			}
  		}
  		else
  		{
  			if (button == 0)
  			{
  				//m_physicsServer.removePickingConstraint();
  				MyMouseCommand cmd;
  				cmd.m_rayFrom.setValue(0, 0, 0);
  				cmd.m_rayTo.setValue(0, 0, 0);
  				cmd.m_type = MyMouseButtonUp;
  
  				m_args[0].m_csGUI->lock();
  				m_args[0].m_mouseCommands.push_back(cmd);
  				m_args[0].m_csGUI->unlock();
  				//remove p2p
  			}
  		}
  
  		//printf("button=%d, state=%d\n",button,state);
  		return false;
  	}
  	virtual bool keyboardCallback(int key, int state)
  	{
  		//printf("key=%d, state=%d\n", key,state);
  		{
  			int keyIndex = -1;
  			//is already there?
  			for (int i = 0; i < m_args[0].m_keyboardEvents.size(); i++)
  			{
  				if (m_args[0].m_keyboardEvents[i].m_keyCode == key)
  				{
  					keyIndex = i;
  					break;
  				}
  			}
  
  			if (state)
  			{
  				b3KeyboardEvent ev;
  				ev.m_keyCode = key;
  				ev.m_keyState = eButtonIsDown + eButtonTriggered;
  				m_args[0].m_csGUI->lock();
  				if (keyIndex >= 0)
  				{
  					if (0 == (m_args[0].m_keyboardEvents[keyIndex].m_keyState & eButtonIsDown))
  					{
  						m_args[0].m_keyboardEvents[keyIndex] = ev;
  					}
  				}
  				else
  				{
  					m_args[0].m_keyboardEvents.push_back(ev);
  				}
  				m_args[0].m_csGUI->unlock();
  			}
  			else
  			{
  				m_args[0].m_csGUI->lock();
  				b3KeyboardEvent ev;
  				ev.m_keyCode = key;
  				ev.m_keyState = eButtonReleased;
  				if (keyIndex >= 0)
  				{
  					m_args[0].m_keyboardEvents[keyIndex] = ev;
  				}
  				else
  				{
  					m_args[0].m_keyboardEvents.push_back(ev);
  				}
  				m_args[0].m_csGUI->unlock();
  			}
  		}
  		/*printf("m_args[0].m_keyboardEvents.size()=%d\n", m_args[0].m_keyboardEvents.size());
  		for (int i=0;i<m_args[0].m_keyboardEvents.size();i++)
  		{
  			printf("key[%d]=%d state = %d\n",i,m_args[0].m_keyboardEvents[i].m_keyCode,m_args[0].m_keyboardEvents[i].m_keyState);
  		}
  		*/
  		double shift = 0.1;
  
  		CommonWindowInterface* window = m_guiHelper->getAppInterface()->m_window;
  		if (window->isModifierKeyPressed(B3G_SHIFT))
  			shift = 0.01;
  
  		btVector3 VRTeleportPos = this->m_physicsServer.getVRTeleportPosition();
  
  		if (gEnableDefaultKeyboardShortcuts)
  		{
  			if (m_guiHelper->getAppInterface()->m_renderer->getActiveCamera()->isVRCamera())
  			{
  				if (key == 'w' && state)
  				{
  					VRTeleportPos[0] += shift;
  					m_physicsServer.setVRTeleportPosition(VRTeleportPos);
  					saveCurrentSettingsVR(VRTeleportPos);
  				}
  				if (key == 's' && state)
  				{
  					VRTeleportPos[0] -= shift;
  					m_physicsServer.setVRTeleportPosition(VRTeleportPos);
  					saveCurrentSettingsVR(VRTeleportPos);
  				}
  				if (key == 'a' && state)
  				{
  					VRTeleportPos[1] -= shift;
  					m_physicsServer.setVRTeleportPosition(VRTeleportPos);
  					saveCurrentSettingsVR(VRTeleportPos);
  				}
  				if (key == 'd' && state)
  				{
  					VRTeleportPos[1] += shift;
  					m_physicsServer.setVRTeleportPosition(VRTeleportPos);
  					saveCurrentSettingsVR(VRTeleportPos);
  				}
  				if (key == 'q' && state)
  				{
  					VRTeleportPos[2] += shift;
  					m_physicsServer.setVRTeleportPosition(VRTeleportPos);
  					saveCurrentSettingsVR(VRTeleportPos);
  				}
  				if (key == 'e' && state)
  				{
  					VRTeleportPos[2] -= shift;
  					m_physicsServer.setVRTeleportPosition(VRTeleportPos);
  					saveCurrentSettingsVR(VRTeleportPos);
  				}
  				if (key == 'z' && state)
  				{
  					gVRTeleportRotZ += shift;
  					btQuaternion VRTeleportOrn = btQuaternion(btVector3(0, 0, 1), gVRTeleportRotZ);
  					m_physicsServer.setVRTeleportOrientation(VRTeleportOrn);
  					saveCurrentSettingsVR(VRTeleportPos);
  				}
  			}
  		}
  
  		return false;
  	}
  
  	virtual void setSharedMemoryKey(int key)
  	{
  		m_physicsServer.setSharedMemoryKey(key);
  	}
  
  	virtual void processCommandLineArgs(int argc, char* argv[])
  	{
  		b3CommandLineArgs args(argc, argv);
  		loadCurrentSettingsVR(args);
  		int shmemKey;
  
  		if (args.GetCmdLineArgument("sharedMemoryKey", shmemKey))
  		{
  			setSharedMemoryKey(shmemKey);
  		}
  
  		btVector3 vrTeleportPos = m_physicsServer.getVRTeleportPosition();
  
  		if (args.GetCmdLineArgument("camPosX", vrTeleportPos[0]))
  		{
  			printf("camPosX=%f\n", vrTeleportPos[0]);
  		}
  
  		if (args.GetCmdLineArgument("camPosY", vrTeleportPos[1]))
  		{
  			printf("camPosY=%f\n", vrTeleportPos[1]);
  		}
  
  		if (args.GetCmdLineArgument("camPosZ", vrTeleportPos[2]))
  		{
  			printf("camPosZ=%f\n", vrTeleportPos[2]);
  		}
  
  		m_physicsServer.setVRTeleportPosition(vrTeleportPos);
  
  		float camRotZ = 0.f;
  		if (args.GetCmdLineArgument("camRotZ", camRotZ))
  		{
  			printf("camRotZ = %f\n", camRotZ);
  			btQuaternion ornZ(btVector3(0, 0, 1), camRotZ);
  			m_physicsServer.setVRTeleportOrientation(ornZ);
  		}
  
  		if (args.CheckCmdLineFlag("realtimesimulation"))
  		{
  			//gEnableRealTimeSimVR = true;
  			m_physicsServer.enableRealTimeSimulation(true);
  		}
  
  		if (args.CheckCmdLineFlag("disableDefaultKeyboardShortcuts"))
  		{
  			gEnableDefaultKeyboardShortcuts = false;
  		}
  		if (args.CheckCmdLineFlag("enableDefaultKeyboardShortcuts"))
  		{
  			gEnableDefaultKeyboardShortcuts = true;
  		}
  		if (args.CheckCmdLineFlag("disableDefaultMousePicking"))
  		{
  			gEnableDefaultMousePicking = false;
  		}
  		if (args.CheckCmdLineFlag("enableDefaultMousePicking"))
  		{
  			gEnableDefaultMousePicking = true;
  		}
  	}
  };
  
  PhysicsServerExample::PhysicsServerExample(MultiThreadedOpenGLGuiHelper* helper, CommandProcessorCreationInterface* commandProcessorCreator, SharedMemoryInterface* sharedMem, int options)
  	: SharedMemoryCommon(helper),
  	  m_physicsServer(commandProcessorCreator, sharedMem, 0),
  	  m_wantsShutdown(false),
  	  m_isConnected(false),
  	  m_replay(false),
  	  m_canvas(0),
  	  m_canvasRGBIndex(-1),
  	  m_canvasDepthIndex(-1),
  	  m_canvasSegMaskIndex(-1)
  //m_options(options)
  #ifdef BT_ENABLE_VR
  	  ,
  	  m_tinyVrGui(0)
  #endif
  	  ,
  	  m_renderedFrames(0)
  {
  	m_multiThreadedHelper = helper;
  	//	b3Printf("Started PhysicsServer\n");
  }
  
  PhysicsServerExample::~PhysicsServerExample()
  {
  	if (m_canvas)
  	{
  		if (m_canvasRGBIndex >= 0)
  			m_canvas->destroyCanvas(m_canvasRGBIndex);
  		if (m_canvasDepthIndex >= 0)
  			m_canvas->destroyCanvas(m_canvasDepthIndex);
  		if (m_canvasSegMaskIndex >= 0)
  			m_canvas->destroyCanvas(m_canvasSegMaskIndex);
  	}
  
  #ifdef BT_ENABLE_VR
  	delete m_tinyVrGui;
  #endif
  
  	bool deInitializeSharedMemory = true;
  	m_physicsServer.disconnectSharedMemory(deInitializeSharedMemory);
  	m_isConnected = false;
  	delete m_multiThreadedHelper;
  }
  
  bool PhysicsServerExample::isConnected()
  {
  	return m_isConnected;
  }
  
  void PhysicsServerExample::initPhysics()
  {
  	///for this testing we use Z-axis up
  	int upAxis = 2;
  	m_guiHelper->setUpAxis(upAxis);
  
  	m_threadSupport = createMotionThreadSupport(MAX_MOTION_NUM_THREADS);
  
  	m_isConnected = m_physicsServer.connectSharedMemory(m_guiHelper);
  
  	for (int i = 0; i < m_threadSupport->getNumTasks(); i++)
  	{
  		MotionThreadLocalStorage* storage = (MotionThreadLocalStorage*)m_threadSupport->getThreadLocalMemory(i);
  		b3Assert(storage);
  		storage->threadId = i;
  		//storage->m_sharedMem = data->m_sharedMem;
  	}
  
  	for (int w = 0; w < MAX_MOTION_NUM_THREADS; w++)
  	{
  		m_args[w].m_cs = m_threadSupport->createCriticalSection();
  		m_args[w].m_cs2 = m_threadSupport->createCriticalSection();
  		m_args[w].m_cs3 = m_threadSupport->createCriticalSection();
  		m_args[w].m_csGUI = m_threadSupport->createCriticalSection();
  		m_args[w].m_cs->lock();
  		m_args[w].m_cs->setSharedParam(0, eMotionIsUnInitialized);
  		m_args[w].m_cs->unlock();
  		int numMoving = 0;
  		m_args[w].m_positions.resize(numMoving);
  		m_args[w].m_physicsServerPtr = &m_physicsServer;
  		//int index = 0;
  
  		m_threadSupport->runTask(B3_THREAD_SCHEDULE_TASK, (void*)&this->m_args[w], w);
  		bool isUninitialized = true;
  
  		while (isUninitialized)
  		{
  			m_args[w].m_cs->lock();
  			isUninitialized = (m_args[w].m_cs->getSharedParam(0) == eMotionIsUnInitialized);
  			m_args[w].m_cs->unlock();
  #ifdef _WIN32
  			b3Clock::usleep(1000);
  #endif
  		}
  	}
  
  	m_args[0].m_cs->setSharedParam(1, eGUIHelperIdle);
  	m_multiThreadedHelper->setCriticalSection(m_args[0].m_cs);
  	m_multiThreadedHelper->setCriticalSection2(m_args[0].m_cs2);
  	m_multiThreadedHelper->setCriticalSection3(m_args[0].m_cs3);
  	m_multiThreadedHelper->setCriticalSectionGUI(m_args[0].m_csGUI);
  
  	m_args[0].m_cs2->lock();
  
  	{
  		m_canvas = m_guiHelper->get2dCanvasInterface();
  		if (m_canvas)
  		{
  			m_canvasRGBIndex = m_canvas->createCanvas("Synthetic Camera RGB data", gCamVisualizerWidth, gCamVisualizerHeight, 8, 55);
  			m_canvasDepthIndex = m_canvas->createCanvas("Synthetic Camera Depth data", gCamVisualizerWidth, gCamVisualizerHeight, 8, 75 + gCamVisualizerHeight);
  			m_canvasSegMaskIndex = m_canvas->createCanvas("Synthetic Camera Segmentation Mask", gCamVisualizerWidth, gCamVisualizerHeight, 8, 95 + gCamVisualizerHeight * 2);
  
  			for (int i = 0; i < gCamVisualizerWidth; i++)
  			{
  				for (int j = 0; j < gCamVisualizerHeight; j++)
  				{
  					unsigned char red = 255;
  					unsigned char green = 255;
  					unsigned char blue = 255;
  					unsigned char alpha = 255;
  					if (i == j)
  					{
  						red = 0;
  						green = 0;
  						blue = 0;
  					}
  					m_canvas->setPixel(m_canvasRGBIndex, i, j, red, green, blue, alpha);
  					if (m_canvasSegMaskIndex >= 0)
  						m_canvas->setPixel(m_canvasDepthIndex, i, j, red, green, blue, alpha);
  					if (m_canvasSegMaskIndex >= 0)
  						m_canvas->setPixel(m_canvasSegMaskIndex, i, j, red, green, blue, alpha);
  				}
  			}
  			m_canvas->refreshImageData(m_canvasRGBIndex);
  
  			if (m_canvasDepthIndex >= 0)
  				m_canvas->refreshImageData(m_canvasDepthIndex);
  			if (m_canvasSegMaskIndex >= 0)
  				m_canvas->refreshImageData(m_canvasSegMaskIndex);
  		}
  	}
  }
  
  void PhysicsServerExample::exitPhysics()
  {
  	for (int i = 0; i < MAX_MOTION_NUM_THREADS; i++)
  	{
  		m_args[i].m_cs2->unlock();
  		m_args[i].m_cs->lock();
  		m_args[i].m_cs->setSharedParam(0, eRequestTerminateMotion);
  		m_args[i].m_cs->unlock();
  	}
  	int numActiveThreads = MAX_MOTION_NUM_THREADS;
  
  	while (numActiveThreads)
  	{
  		int arg0, arg1;
  		if (m_threadSupport->isTaskCompleted(&arg0, &arg1, 0))
  		{
  			numActiveThreads--;
  			printf("numActiveThreads = %d\n", numActiveThreads);
  		}
  		else
  		{
  			b3Clock::usleep(0);
  		}
  		//we need to call 'stepSimulation' to make sure that
  		//other threads get out of blocking state (workerThreadWait)
  		stepSimulation(0);
  	};
  
  	printf("stopping threads\n");
  
  	m_threadSupport->deleteCriticalSection(m_args[0].m_cs);
  	m_threadSupport->deleteCriticalSection(m_args[0].m_cs2);
  	m_threadSupport->deleteCriticalSection(m_args[0].m_cs3);
  	m_threadSupport->deleteCriticalSection(m_args[0].m_csGUI);
  
  	delete m_threadSupport;
  	m_threadSupport = 0;
  
  	//m_physicsServer.resetDynamicsWorld();
  }
  
  bool PhysicsServerExample::wantsTermination()
  {
  	return m_wantsShutdown;
  }
  
  void PhysicsServerExample::updateGraphics()
  {
  	//check if any graphics related tasks are requested
  
  	switch (m_multiThreadedHelper->getCriticalSection()->getSharedParam(1))
  	{
  		case eGUIHelperCreateCollisionShapeGraphicsObject:
  		{
  			B3_PROFILE("eGUIHelperCreateCollisionShapeGraphicsObject");
  			m_multiThreadedHelper->m_childGuiHelper->createCollisionShapeGraphicsObject(m_multiThreadedHelper->m_colShape);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIHelperCreateCollisionObjectGraphicsObject:
  		{
  			B3_PROFILE("eGUIHelperCreateCollisionObjectGraphicsObject");
  
  			m_multiThreadedHelper->m_childGuiHelper->createCollisionObjectGraphicsObject(m_multiThreadedHelper->m_obj,
  																						 m_multiThreadedHelper->m_color2);
  			m_multiThreadedHelper->mainThreadRelease();
  
  			break;
  		}
  		case eGUIHelperCreateRigidBodyGraphicsObject:
  		{
  			B3_PROFILE("eGUIHelperCreateRigidBodyGraphicsObject");
  			m_multiThreadedHelper->m_childGuiHelper->createRigidBodyGraphicsObject(m_multiThreadedHelper->m_body, m_multiThreadedHelper->m_color3);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIHelperRegisterTexture:
  		{
  			B3_PROFILE("eGUIHelperRegisterTexture");
  			m_multiThreadedHelper->m_textureId = m_multiThreadedHelper->m_childGuiHelper->registerTexture(m_multiThreadedHelper->m_texels,
  																										  m_multiThreadedHelper->m_textureWidth, m_multiThreadedHelper->m_textureHeight);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIHelperRemoveTexture:
  		{
  			B3_PROFILE("eGUIHelperRemoveTexture");
  			m_multiThreadedHelper->m_childGuiHelper->removeTexture(m_multiThreadedHelper->m_removeTextureUid);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIHelperRegisterGraphicsShape:
  		{
  			B3_PROFILE("eGUIHelperRegisterGraphicsShape");
  			m_multiThreadedHelper->m_shapeIndex = m_multiThreadedHelper->m_childGuiHelper->registerGraphicsShape(
  				m_multiThreadedHelper->m_vertices,
  				m_multiThreadedHelper->m_numvertices,
  				m_multiThreadedHelper->m_indices,
  				m_multiThreadedHelper->m_numIndices,
  				m_multiThreadedHelper->m_primitiveType,
  				m_multiThreadedHelper->m_textureId);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  
  		case eGUIHelperSetVisualizerFlag:
  		{
  			B3_PROFILE("eGUIHelperSetVisualizerFlag");
  			int flag = m_multiThreadedHelper->m_visualizerFlag;
  			int enable = m_multiThreadedHelper->m_visualizerEnable;
  
  			if (flag == COV_ENABLE_RGB_BUFFER_PREVIEW)
  			{
  				if (enable)
  				{
  					if (m_canvasRGBIndex < 0)
  					{
  						m_canvasRGBIndex = m_canvas->createCanvas("Synthetic Camera RGB data", gCamVisualizerWidth, gCamVisualizerHeight, 8, 55);
  					}
  				}
  				else
  				{
  					if (m_canvasRGBIndex >= 0)
  					{
  						m_canvas->destroyCanvas(m_canvasRGBIndex);
  						m_canvasRGBIndex = -1;
  					}
  				}
  			}
  
  			if (flag == COV_ENABLE_DEPTH_BUFFER_PREVIEW)
  			{
  				if (enable)
  				{
  					if (m_canvasDepthIndex < 0)
  					{
  						m_canvasDepthIndex = m_canvas->createCanvas("Synthetic Camera Depth data", gCamVisualizerWidth, gCamVisualizerHeight, 8, 75 + gCamVisualizerHeight);
  					}
  				}
  				else
  				{
  					if (m_canvasDepthIndex >= 0)
  					{
  						m_canvas->destroyCanvas(m_canvasDepthIndex);
  						m_canvasDepthIndex = -1;
  					}
  				}
  			}
  
  			if (flag == COV_ENABLE_SEGMENTATION_MARK_PREVIEW)
  			{
  				if (enable)
  				{
  					if (m_canvasSegMaskIndex < 0)
  					{
  						m_canvasSegMaskIndex = m_canvas->createCanvas("Synthetic Camera Segmentation Mask", gCamVisualizerWidth, gCamVisualizerHeight, 8, 95 + gCamVisualizerHeight * 2);
  					}
  				}
  				else
  				{
  					if (m_canvasSegMaskIndex >= 0)
  					{
  						m_canvas->destroyCanvas(m_canvasSegMaskIndex);
  						m_canvasSegMaskIndex = -1;
  					}
  				}
  			}
  
  			if (flag == COV_ENABLE_VR_TELEPORTING)
  			{
  				gEnableTeleporting = (enable != 0);
  			}
  
  			if (flag == COV_ENABLE_VR_PICKING)
  			{
  				gEnablePicking = (enable != 0);
  			}
  
  			if (flag == COV_ENABLE_SYNC_RENDERING_INTERNAL)
  			{
  				gEnableSyncPhysicsRendering = (enable != 0);
  			}
  
  			if (flag == COV_ENABLE_RENDERING)
  			{
  				gEnableRendering = (enable != 0);
  			}
  
  			if (flag == COV_ENABLE_KEYBOARD_SHORTCUTS)
  			{
  				gEnableDefaultKeyboardShortcuts = (enable != 0);
  			}
  
  			if (flag == COV_ENABLE_MOUSE_PICKING)
  			{
  				gEnableDefaultMousePicking = (enable != 0);
  			}
  
  			m_multiThreadedHelper->m_renderedFrames = m_renderedFrames;
  
  			m_multiThreadedHelper->m_childGuiHelper->setVisualizerFlag(m_multiThreadedHelper->m_visualizerFlag, m_multiThreadedHelper->m_visualizerEnable);
  
  			//postpone the release until an actual frame is rendered
  			if (flag == COV_ENABLE_SINGLE_STEP_RENDERING)
  			{
  				m_multiThreadedHelper->getCriticalSection()->setSharedParam(1, eGUIHelperSetVisualizerFlagCheckRenderedFrame);
  			}
  			else
  			{
  				m_multiThreadedHelper->mainThreadRelease();
  			}
  			break;
  		}
  
  		case eGUIHelperSetVisualizerFlagCheckRenderedFrame:
  		{
  			if (m_renderedFrames != m_multiThreadedHelper->m_renderedFrames)
  			{
  				m_multiThreadedHelper->mainThreadRelease();
  			}
  			break;
  		}
  
  		case eGUIHelperRegisterGraphicsInstance:
  		{
  			B3_PROFILE("eGUIHelperRegisterGraphicsInstance");
  			m_multiThreadedHelper->m_instanceId = m_multiThreadedHelper->m_childGuiHelper->registerGraphicsInstance(
  				m_multiThreadedHelper->m_shapeIndex,
  				m_multiThreadedHelper->m_position,
  				m_multiThreadedHelper->m_quaternion,
  				m_multiThreadedHelper->m_color,
  				m_multiThreadedHelper->m_scaling);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIHelperRemoveAllGraphicsInstances:
  		{
  			B3_PROFILE("eGUIHelperRemoveAllGraphicsInstances");
  #ifdef BT_ENABLE_VR
  			if (m_tinyVrGui)
  			{
  				delete m_tinyVrGui;
  				m_tinyVrGui = 0;
  			}
  #endif  //BT_ENABLE_VR
  			m_multiThreadedHelper->m_childGuiHelper->removeAllGraphicsInstances();
  			if (m_multiThreadedHelper->m_childGuiHelper->getRenderInterface())
  			{
  				int numRenderInstances;
  				numRenderInstances = m_multiThreadedHelper->m_childGuiHelper->getRenderInterface()->getTotalNumInstances();
  				b3Assert(numRenderInstances == 0);
  			}
  			m_multiThreadedHelper->mainThreadRelease();
  
  			break;
  		}
  		case eGUIHelperRemoveGraphicsInstance:
  		{
  			B3_PROFILE("eGUIHelperRemoveGraphicsInstance");
  			m_multiThreadedHelper->m_childGuiHelper->removeGraphicsInstance(m_multiThreadedHelper->m_graphicsInstanceRemove);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  
  		case eGUIHelperGetShapeIndexFromInstance:
  		{
  			B3_PROFILE("eGUIHelperGetShapeIndexFromInstance");
  			m_multiThreadedHelper->getShapeIndex_shapeIndex = m_multiThreadedHelper->m_childGuiHelper->getShapeIndexFromInstance(m_multiThreadedHelper->m_getShapeIndex_instance);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  
  		case eGUIHelperChangeGraphicsInstanceTextureId:
  		{
  			B3_PROFILE("eGUIHelperChangeGraphicsInstanceTextureId");
  
  			m_multiThreadedHelper->m_childGuiHelper->replaceTexture(
  				m_multiThreadedHelper->m_graphicsInstanceChangeTextureShapeIndex,
  				m_multiThreadedHelper->m_graphicsInstanceChangeTextureId);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  
  		case eGUIHelperChangeTexture:
  		{
  			B3_PROFILE("eGUIHelperChangeTexture");
  
  			m_multiThreadedHelper->m_childGuiHelper->changeTexture(
  				m_multiThreadedHelper->m_changeTextureUniqueId,
  				m_multiThreadedHelper->m_changeTextureRgbTexels,
  				m_multiThreadedHelper->m_changeTextureWidth,
  				m_multiThreadedHelper->m_changeTextureHeight);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  
  		case eGUIHelperChangeGraphicsInstanceRGBAColor:
  		{
  			B3_PROFILE("eGUIHelperChangeGraphicsInstanceRGBAColor");
  
  			m_multiThreadedHelper->m_childGuiHelper->changeRGBAColor(m_multiThreadedHelper->m_graphicsInstanceChangeColor, m_multiThreadedHelper->m_rgbaColor);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIHelperChangeGraphicsInstanceSpecularColor:
  		{
  			B3_PROFILE("eGUIHelperChangeGraphicsInstanceSpecularColor");
  
  			m_multiThreadedHelper->m_childGuiHelper->changeSpecularColor(m_multiThreadedHelper->m_graphicsInstanceChangeSpecular, m_multiThreadedHelper->m_specularColor);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIHelperDisplayCameraImageData:
  		{
  			B3_PROFILE("eGUIHelperDisplayCameraImageData");
  
  			if (m_canvas)
  			{
  				int numBytesPerPixel = 4;
  				int startRGBIndex = m_multiThreadedHelper->m_startPixelIndex * numBytesPerPixel;
  				int endRGBIndex = startRGBIndex + (*m_multiThreadedHelper->m_numPixelsCopied * numBytesPerPixel);
  
  				int startDepthIndex = m_multiThreadedHelper->m_startPixelIndex;
  				int endDepthIndex = startDepthIndex + (*m_multiThreadedHelper->m_numPixelsCopied);
  
  				int startSegIndex = m_multiThreadedHelper->m_startPixelIndex;
  				int endSegIndex = startSegIndex + (*m_multiThreadedHelper->m_numPixelsCopied);
  
  				//btScalar frustumZNear = m_multiThreadedHelper->m_projectionMatrix[14]/(m_multiThreadedHelper->m_projectionMatrix[10]-1);
  				//btScalar frustumZFar = m_multiThreadedHelper->m_projectionMatrix[14]/(m_multiThreadedHelper->m_projectionMatrix[10]+1);
  
  				for (int i = 0; i < gCamVisualizerWidth; i++)
  				{
  					for (int j = 0; j < gCamVisualizerHeight; j++)
  					{
  						int xIndex = int(float(i) * (float(m_multiThreadedHelper->m_destinationWidth) / float(gCamVisualizerWidth)));
  						int yIndex = int(float(j) * (float(m_multiThreadedHelper->m_destinationHeight) / float(gCamVisualizerHeight)));
  						btClamp(xIndex, 0, m_multiThreadedHelper->m_destinationWidth);
  						btClamp(yIndex, 0, m_multiThreadedHelper->m_destinationHeight);
  						int bytesPerPixel = 4;  //RGBA
  
  						if (m_canvasRGBIndex >= 0)
  						{
  							int rgbPixelIndex = (xIndex + yIndex * m_multiThreadedHelper->m_destinationWidth) * bytesPerPixel;
  							if (rgbPixelIndex >= startRGBIndex && rgbPixelIndex < endRGBIndex)
  							{
  								m_canvas->setPixel(m_canvasRGBIndex, i, j,
  												   m_multiThreadedHelper->m_pixelsRGBA[rgbPixelIndex - startRGBIndex],
  												   m_multiThreadedHelper->m_pixelsRGBA[rgbPixelIndex + 1 - startRGBIndex],
  												   m_multiThreadedHelper->m_pixelsRGBA[rgbPixelIndex + 2 - startRGBIndex],
  												   255);  //alpha set to 255
  							}
  						}
  						if (m_canvasDepthIndex >= 0 && 0 != m_multiThreadedHelper->m_depthBuffer)
  						{
  							int depthPixelIndex = (xIndex + yIndex * m_multiThreadedHelper->m_destinationWidth);
  							if (depthPixelIndex >= startDepthIndex && depthPixelIndex < endDepthIndex)
  							{
  								float depthValue = m_multiThreadedHelper->m_depthBuffer[depthPixelIndex - startDepthIndex];
  								//todo: rescale the depthValue to [0..255]
  								if (depthValue > -1e20)
  								{
  									int rgb = 0;
  									btScalar frustumZNear = 0.1;
  									btScalar frustumZFar = 30;
  									btScalar minDepthValue = frustumZNear;  //todo: compute more reasonably min/max depth range
  									btScalar maxDepthValue = frustumZFar;
  
  									float depth = depthValue;
  									btScalar linearDepth = 255. * (2.0 * frustumZNear) / (frustumZFar + frustumZNear - depth * (frustumZFar - frustumZNear));
  									btClamp(linearDepth, btScalar(0), btScalar(255));
  									rgb = linearDepth;
  
  									m_canvas->setPixel(m_canvasDepthIndex, i, j,
  													   rgb,
  													   rgb,
  													   255, 255);  //alpha set to 255
  								}
  								else
  								{
  									m_canvas->setPixel(m_canvasDepthIndex, i, j,
  													   0,
  													   0,
  													   0, 255);  //alpha set to 255
  								}
  							}
  						}
  
  						if (m_canvasSegMaskIndex >= 0 && (0 != m_multiThreadedHelper->m_segmentationMaskBuffer))
  						{
  							int segmentationMaskPixelIndex = (xIndex + yIndex * m_multiThreadedHelper->m_destinationWidth);
  
  							if (segmentationMaskPixelIndex >= startSegIndex && segmentationMaskPixelIndex < endSegIndex)
  							{
  								int segmentationMask = m_multiThreadedHelper->m_segmentationMaskBuffer[segmentationMaskPixelIndex - startSegIndex];
  								btVector4 palette[4] = {btVector4(32, 255, 32, 255),
  														btVector4(32, 32, 255, 255),
  														btVector4(255, 255, 32, 255),
  														btVector4(32, 255, 255, 255)};
  								if (segmentationMask >= 0)
  								{
  									int obIndex = segmentationMask & ((1 << 24) - 1);
  									int linkIndex = (segmentationMask >> 24) - 1;
  
  									btVector4 rgb = palette[(obIndex + linkIndex) & 3];
  
  									m_canvas->setPixel(m_canvasSegMaskIndex, i, j,
  													   rgb.x(),
  													   rgb.y(),
  													   rgb.z(), 255);  //alpha set to 255
  								}
  								else
  								{
  									m_canvas->setPixel(m_canvasSegMaskIndex, i, j,
  													   0,
  													   0,
  													   0, 255);  //alpha set to 255
  								}
  							}
  						}
  					}
  				}
  
  				if (m_canvasRGBIndex >= 0)
  					m_canvas->refreshImageData(m_canvasRGBIndex);
  
  				if (m_canvasDepthIndex >= 0)
  					m_canvas->refreshImageData(m_canvasDepthIndex);
  				if (m_canvasSegMaskIndex >= 0)
  					m_canvas->refreshImageData(m_canvasSegMaskIndex);
  			}
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIHelperCopyCameraImageData:
  		{
  			B3_PROFILE("eGUIHelperCopyCameraImageData");
  
  			if (m_multiThreadedHelper->m_startPixelIndex == 0)
  			{
  				m_physicsServer.syncPhysicsToGraphics();
  			}
  
  			m_multiThreadedHelper->m_childGuiHelper->copyCameraImageData(m_multiThreadedHelper->m_viewMatrix,
  																		 m_multiThreadedHelper->m_projectionMatrix,
  																		 m_multiThreadedHelper->m_pixelsRGBA,
  																		 m_multiThreadedHelper->m_rgbaBufferSizeInPixels,
  																		 m_multiThreadedHelper->m_depthBuffer,
  																		 m_multiThreadedHelper->m_depthBufferSizeInPixels,
  																		 m_multiThreadedHelper->m_segmentationMaskBuffer,
  																		 m_multiThreadedHelper->m_segmentationMaskBufferSizeInPixels,
  																		 m_multiThreadedHelper->m_startPixelIndex,
  																		 m_multiThreadedHelper->m_destinationWidth,
  																		 m_multiThreadedHelper->m_destinationHeight,
  																		 m_multiThreadedHelper->m_numPixelsCopied);
  
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIHelperAutogenerateGraphicsObjects:
  		{
  			B3_PROFILE("eGUIHelperAutogenerateGraphicsObjects");
  
  			m_multiThreadedHelper->m_childGuiHelper->autogenerateGraphicsObjects(m_multiThreadedHelper->m_dynamicsWorld);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  
  		case eGUIUserDebugAddText:
  		{
  			B3_PROFILE("eGUIUserDebugAddText");
  
  			bool replaced = false;
  
  			for (int i = 0; i < m_multiThreadedHelper->m_userDebugText.size(); i++)
  			{
  				if (m_multiThreadedHelper->m_userDebugText[i].m_itemUniqueId == m_multiThreadedHelper->m_tmpText.m_itemUniqueId)
  				{
  					m_multiThreadedHelper->m_userDebugText[i] = m_multiThreadedHelper->m_tmpText;
  					m_multiThreadedHelper->m_resultUserDebugTextUid = m_multiThreadedHelper->m_tmpText.m_itemUniqueId;
  					replaced = true;
  				}
  			}
  
  			if (!replaced)
  			{
  				m_multiThreadedHelper->m_userDebugText.push_back(m_multiThreadedHelper->m_tmpText);
  				m_multiThreadedHelper->m_resultUserDebugTextUid = m_multiThreadedHelper->m_userDebugText[m_multiThreadedHelper->m_userDebugText.size() - 1].m_itemUniqueId;
  			}
  			m_multiThreadedHelper->mainThreadRelease();
  
  			break;
  		}
  		case eGUIUserDebugAddParameter:
  		{
  			B3_PROFILE("eGUIUserDebugAddParameter");
  
  			UserDebugParameter* param = new UserDebugParameter(m_multiThreadedHelper->m_tmpParam);
  			m_multiThreadedHelper->m_userDebugParams.push_back(param);
  
  			{
  				SliderParams slider(param->m_text, &param->m_value);
  				slider.m_minVal = param->m_rangeMin;
  				slider.m_maxVal = param->m_rangeMax;
  
  				if (m_multiThreadedHelper->m_childGuiHelper->getParameterInterface())
  					m_multiThreadedHelper->m_childGuiHelper->getParameterInterface()->registerSliderFloatParameter(slider);
  			}
  
  			m_multiThreadedHelper->m_userDebugParamUid = (*m_multiThreadedHelper->m_userDebugParams[m_multiThreadedHelper->m_userDebugParams.size() - 1]).m_itemUniqueId;
  
  			//also add actual menu
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIUserDebugAddLine:
  		{
  			B3_PROFILE("eGUIUserDebugAddLine");
  
  			if (m_multiThreadedHelper->m_tmpLine.m_replaceItemUid >= 0)
  			{
  				for (int i = 0; i < m_multiThreadedHelper->m_userDebugLines.size(); i++)
  				{
  					if (m_multiThreadedHelper->m_userDebugLines[i].m_itemUniqueId == m_multiThreadedHelper->m_tmpLine.m_replaceItemUid)
  					{
  						m_multiThreadedHelper->m_userDebugLines[i] = m_multiThreadedHelper->m_tmpLine;
  						m_multiThreadedHelper->m_resultDebugLineUid = m_multiThreadedHelper->m_tmpLine.m_replaceItemUid;
  					}
  				}
  			}
  			else
  			{
  				m_multiThreadedHelper->m_userDebugLines.push_back(m_multiThreadedHelper->m_tmpLine);
  				m_multiThreadedHelper->m_resultDebugLineUid = m_multiThreadedHelper->m_userDebugLines[m_multiThreadedHelper->m_userDebugLines.size() - 1].m_itemUniqueId;
  			}
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIUserDebugRemoveItem:
  		{
  			B3_PROFILE("eGUIUserDebugRemoveItem");
  
  			for (int i = 0; i < m_multiThreadedHelper->m_userDebugLines.size(); i++)
  			{
  				if (m_multiThreadedHelper->m_userDebugLines[i].m_itemUniqueId == m_multiThreadedHelper->m_removeDebugItemUid)
  				{
  					m_multiThreadedHelper->m_userDebugLines.swap(i, m_multiThreadedHelper->m_userDebugLines.size() - 1);
  					m_multiThreadedHelper->m_userDebugLines.pop_back();
  					break;
  				}
  			}
  
  			for (int i = 0; i < m_multiThreadedHelper->m_userDebugText.size(); i++)
  			{
  				if (m_multiThreadedHelper->m_userDebugText[i].m_itemUniqueId == m_multiThreadedHelper->m_removeDebugItemUid)
  				{
  					m_multiThreadedHelper->m_userDebugText.swap(i, m_multiThreadedHelper->m_userDebugText.size() - 1);
  					m_multiThreadedHelper->m_userDebugText.pop_back();
  					break;
  				}
  			}
  
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  		case eGUIUserDebugRemoveAllItems:
  		{
  			B3_PROFILE("eGUIUserDebugRemoveAllItems");
  
  			m_multiThreadedHelper->m_userDebugLines.clear();
  			m_multiThreadedHelper->m_userDebugText.clear();
  			m_multiThreadedHelper->m_uidGenerator = 0;
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  
  		case eGUIDumpFramesToVideo:
  		{
  			B3_PROFILE("eGUIDumpFramesToVideo");
  
  			m_multiThreadedHelper->m_childGuiHelper->dumpFramesToVideo(m_multiThreadedHelper->m_mp4FileName);
  			m_multiThreadedHelper->mainThreadRelease();
  			break;
  		}
  
  		case eGUIHelperIdle:
  		{
  			break;
  		}
  		default:
  		{
  			btAssert(0);
  		}
  	}
  }
  
  void PhysicsServerExample::stepSimulation(float deltaTime)
  {
  	BT_PROFILE("PhysicsServerExample::stepSimulation");
  
  	//this->m_physicsServer.processClientCommands();
  
  	for (int i = m_multiThreadedHelper->m_userDebugLines.size() - 1; i >= 0; i--)
  	{
  		if (m_multiThreadedHelper->m_userDebugLines[i].m_lifeTime)
  		{
  			m_multiThreadedHelper->m_userDebugLines[i].m_lifeTime -= deltaTime;
  			if (m_multiThreadedHelper->m_userDebugLines[i].m_lifeTime <= 0)
  			{
  				m_multiThreadedHelper->m_userDebugLines.swap(i, m_multiThreadedHelper->m_userDebugLines.size() - 1);
  				m_multiThreadedHelper->m_userDebugLines.pop_back();
  			}
  		}
  	}
  
  	for (int i = m_multiThreadedHelper->m_userDebugText.size() - 1; i >= 0; i--)
  	{
  		if (m_multiThreadedHelper->m_userDebugText[i].m_lifeTime)
  		{
  			m_multiThreadedHelper->m_userDebugText[i].m_lifeTime -= deltaTime;
  			if (m_multiThreadedHelper->m_userDebugText[i].m_lifeTime <= 0)
  			{
  				m_multiThreadedHelper->m_userDebugText.swap(i, m_multiThreadedHelper->m_userDebugText.size() - 1);
  				m_multiThreadedHelper->m_userDebugText.pop_back();
  			}
  		}
  	}
  	updateGraphics();
  
  	{
  		if (m_multiThreadedHelper->m_childGuiHelper->getRenderInterface())
  		{
  			m_multiThreadedHelper->m_childGuiHelper->getRenderInterface()->writeTransforms();
  		}
  	}
  }
  
  static float vrOffset[16] = {1, 0, 0, 0,
  							 0, 1, 0, 0,
  							 0, 0, 1, 0,
  							 0, 0, 0, 0};
  
  extern int gDroppedSimulationSteps;
  extern int gNumSteps;
  extern double gDtInSec;
  extern double gSubStep;
  extern btTransform gVRTrackingObjectTr;
  
  void PhysicsServerExample::drawUserDebugLines()
  {
  	//static char line0[1024];
  	//static char line1[1024];
  
  	//draw all user-debug-lines
  
  	//add array of lines
  
  	//draw all user- 'text3d' messages
  	if (m_multiThreadedHelper)
  	{
  		//if gBatchUserDebugLines is true, batch lines based on color+width, to reduce line draw calls
  
  		btAlignedObjectArray<btAlignedObjectArray<unsigned int> > sortedIndices;
  		btAlignedObjectArray<btAlignedObjectArray<btVector3FloatData> > sortedLines;
  
  		btHashMap<ColorWidth, int> hashedLines;
  
  		for (int i = 0; i < m_multiThreadedHelper->m_userDebugLines.size(); i++)
  		{
  			btVector3 from;
  			from.setValue(m_multiThreadedHelper->m_userDebugLines[i].m_debugLineFromXYZ[0],
  						  m_multiThreadedHelper->m_userDebugLines[i].m_debugLineFromXYZ[1],
  						  m_multiThreadedHelper->m_userDebugLines[i].m_debugLineFromXYZ[2]);
  			btVector3 toX;
  			toX.setValue(m_multiThreadedHelper->m_userDebugLines[i].m_debugLineToXYZ[0],
  						 m_multiThreadedHelper->m_userDebugLines[i].m_debugLineToXYZ[1],
  						 m_multiThreadedHelper->m_userDebugLines[i].m_debugLineToXYZ[2]);
  
  			int graphicsIndex = m_multiThreadedHelper->m_userDebugLines[i].m_trackingVisualShapeIndex;
  			if (graphicsIndex >= 0)
  			{
  				CommonRenderInterface* renderer = m_guiHelper->getRenderInterface();
  				if (renderer)
  				{
  					float parentPos[3];
  					float parentOrn[4];
  
  					if (renderer->readSingleInstanceTransformToCPU(parentPos, parentOrn, graphicsIndex))
  					{
  						btTransform parentTrans;
  						parentTrans.setOrigin(btVector3(parentPos[0], parentPos[1], parentPos[2]));
  						parentTrans.setRotation(btQuaternion(parentOrn[0], parentOrn[1], parentOrn[2], parentOrn[3]));
  						from = parentTrans * from;
  						toX = parentTrans * toX;
  					}
  				}
  			}
  
  			btVector3 color;
  			color.setValue(m_multiThreadedHelper->m_userDebugLines[i].m_debugLineColorRGB[0],
  						   m_multiThreadedHelper->m_userDebugLines[i].m_debugLineColorRGB[1],
  						   m_multiThreadedHelper->m_userDebugLines[i].m_debugLineColorRGB[2]);
  			ColorWidth cw;
  			color.serializeFloat(cw.m_color);
  			cw.width = m_multiThreadedHelper->m_userDebugLines[i].m_lineWidth;
  			int index = -1;
  
  			if (gBatchUserDebugLines)
  			{
  				int* indexPtr = hashedLines.find(cw);
  				if (indexPtr)
  				{
  					index = *indexPtr;
  				}
  				else
  				{
  					index = sortedLines.size();
  					sortedLines.expand();
  					sortedIndices.expand();
  					hashedLines.insert(cw, index);
  				}
  				btAssert(index >= 0);
  				if (index >= 0)
  				{
  					btVector3FloatData from1, toX1;
  					sortedIndices[index].push_back(sortedLines[index].size());
  					from.serializeFloat(from1);
  					sortedLines[index].push_back(from1);
  					sortedIndices[index].push_back(sortedLines[index].size());
  					toX.serializeFloat(toX1);
  					sortedLines[index].push_back(toX1);
  				}
  			}
  			else
  			{
  				m_guiHelper->getAppInterface()->m_renderer->drawLine(from, toX, color, m_multiThreadedHelper->m_userDebugLines[i].m_lineWidth);
  			}
  		}
  
  		if (gBatchUserDebugLines)
  		{
  			for (int i = 0; i < hashedLines.size(); i++)
  			{
  				ColorWidth cw = hashedLines.getKeyAtIndex(i);
  				int index = *hashedLines.getAtIndex(i);
  				int stride = sizeof(btVector3FloatData);
  				const float* positions = &sortedLines[index][0].m_floats[0];
  				int numPoints = sortedLines[index].size();
  				const unsigned int* indices = &sortedIndices[index][0];
  				int numIndices = sortedIndices[index].size();
  				m_guiHelper->getAppInterface()->m_renderer->drawLines(positions, cw.m_color.m_floats, numPoints, stride, indices, numIndices, cw.width);
  			}
  		}
  
  		for (int i = 0; i < m_multiThreadedHelper->m_userDebugText.size(); i++)
  		{
  			//int optionFlag = 0;//CommonGraphicsApp::eDrawText3D_OrtogonalFaceCamera|CommonGraphicsApp::eDrawText3D_TrueType;
  			//int optionFlag = CommonGraphicsApp::eDrawText3D_TrueType;
  			float orientation[4] = {0, 0, 0, 1};
  
  			//int optionFlag = CommonGraphicsApp::eDrawText3D_OrtogonalFaceCamera;
  			int optionFlag = 0;
  
  			if (m_multiThreadedHelper->m_userDebugText[i].m_optionFlags & CommonGraphicsApp::eDrawText3D_OrtogonalFaceCamera)
  			{
  				optionFlag |= CommonGraphicsApp::eDrawText3D_OrtogonalFaceCamera;
  			}
  			else
  			{
  				orientation[0] = (float)m_multiThreadedHelper->m_userDebugText[i].m_textOrientation[0];
  				orientation[1] = (float)m_multiThreadedHelper->m_userDebugText[i].m_textOrientation[1];
  				orientation[2] = (float)m_multiThreadedHelper->m_userDebugText[i].m_textOrientation[2];
  				orientation[3] = (float)m_multiThreadedHelper->m_userDebugText[i].m_textOrientation[3];
  				optionFlag |= CommonGraphicsApp::eDrawText3D_TrueType;
  			}
  
  			float colorRGBA[4] = {
  				(float)m_multiThreadedHelper->m_userDebugText[i].m_textColorRGB[0],
  				(float)m_multiThreadedHelper->m_userDebugText[i].m_textColorRGB[1],
  				(float)m_multiThreadedHelper->m_userDebugText[i].m_textColorRGB[2],
  				(float)1.};
  
  			float pos[3] = {(float)m_multiThreadedHelper->m_userDebugText[i].m_textPositionXYZ1[0],
  							(float)m_multiThreadedHelper->m_userDebugText[i].m_textPositionXYZ1[1],
  							(float)m_multiThreadedHelper->m_userDebugText[i].m_textPositionXYZ1[2]};
  
  			int graphicsIndex = m_multiThreadedHelper->m_userDebugText[i].m_trackingVisualShapeIndex;
  			if (graphicsIndex >= 0)
  			{
  				CommonRenderInterface* renderer = m_guiHelper->getRenderInterface();
  				if (renderer)
  				{
  					float parentPos[3];
  					float parentOrn[4];
  
  					if (renderer->readSingleInstanceTransformToCPU(parentPos, parentOrn, graphicsIndex))
  					{
  						btTransform parentTrans;
  						parentTrans.setOrigin(btVector3(parentPos[0], parentPos[1], parentPos[2]));
  						parentTrans.setRotation(btQuaternion(parentOrn[0], parentOrn[1], parentOrn[2], parentOrn[3]));
  						btTransform childTr;
  						childTr.setOrigin(btVector3(pos[0], pos[1], pos[2]));
  						childTr.setRotation(btQuaternion(orientation[0], orientation[1], orientation[2], orientation[3]));
  
  						btTransform siteTr = parentTrans * childTr;
  						pos[0] = siteTr.getOrigin()[0];
  						pos[1] = siteTr.getOrigin()[1];
  						pos[2] = siteTr.getOrigin()[2];
  						btQuaternion siteOrn = siteTr.getRotation();
  						orientation[0] = siteOrn[0];
  						orientation[1] = siteOrn[1];
  						orientation[2] = siteOrn[2];
  						orientation[3] = siteOrn[3];
  					}
  				}
  			}
  
  			{
  				btAlignedObjectArray<std::string> pieces;
  				btAlignedObjectArray<std::string> separators;
  				separators.push_back("\n");
  				urdfStringSplit(pieces, m_multiThreadedHelper->m_userDebugText[i].m_text, separators);
  
  				double sz = m_multiThreadedHelper->m_userDebugText[i].textSize;
  
  				btTransform tr;
  				tr.setIdentity();
  				tr.setOrigin(btVector3(pos[0], pos[1], pos[2]));
  				tr.setRotation(btQuaternion(orientation[0], orientation[1], orientation[2], orientation[3]));
  
  				//float newpos[3]={pos[0]-float(t)*sz,pos[1],pos[2]};
  
  				for (int t = 0; t < pieces.size(); t++)
  				{
  					btTransform offset;
  					offset.setIdentity();
  					offset.setOrigin(btVector3(0, -float(t) * sz, 0));
  					btTransform result = tr * offset;
  					float newpos[3] = {(float)result.getOrigin()[0],
  									   (float)result.getOrigin()[1],
  									   (float)result.getOrigin()[2]};
  
  					m_guiHelper->getAppInterface()->drawText3D(pieces[t].c_str(),
  															   newpos, orientation, colorRGBA,
  															   sz, optionFlag);
  				}
  			}
  
  			/*m_guiHelper->getAppInterface()->drawText3D(m_multiThreadedHelper->m_userDebugText[i].m_text,
  				m_multiThreadedHelper->m_userDebugText[i].m_textPositionXYZ[0],
  				m_multiThreadedHelper->m_userDebugText[i].m_textPositionXYZ[1],
  				m_multiThreadedHelper->m_userDebugText[i].m_textPositionXYZ[2],
  				m_multiThreadedHelper->m_userDebugText[i].textSize);
  				*/
  		}
  	}
  }
  
  void PhysicsServerExample::renderScene()
  {
  	m_renderedFrames++;
  
  	btTransform vrTrans;
  
  	B3_PROFILE("PhysicsServerExample::RenderScene");
  
  	if (m_physicsServer.isRealTimeSimulationEnabled())
  	{
  		static int frameCount = 0;
  		static btScalar prevTime = m_clock.getTimeSeconds();
  		frameCount++;
  
  		static char line0[1024] = {0};
  		static char line1[1024] = {0};
  
  #if 0
  
  		static btScalar worseFps = 1000000;
  		int numFrames = 200;
  		static int count = 0;
  		count++;
  		
  			
  		if (0 == (count & 1))
  		{
  			btScalar curTime = m_clock.getTimeSeconds();
  			btScalar fps = 1. / (curTime - prevTime);
  			prevTime = curTime;
  			if (fps < worseFps)
  			{
  				worseFps = fps;
  			}
  
  			if (count > numFrames)
  			{
  				count = 0;
  				sprintf(line0, "fps:%f frame:%d", worseFps, frameCount / 2);
  				sprintf(line1, "drop:%d tscale:%f dt:%f, substep %f)", gDroppedSimulationSteps, simTimeScalingFactor,gDtInSec, gSubStep);
  				gDroppedSimulationSteps = 0;
  
  				worseFps = 1000000;
  			}
  		}
  #endif
  
  #ifdef BT_ENABLE_VR
  		if ((gInternalSimFlags & 2) && m_tinyVrGui == 0)
  		{
  			ComboBoxParams comboParams;
  			comboParams.m_comboboxId = 0;
  			comboParams.m_numItems = 0;
  			comboParams.m_startItem = 0;
  			comboParams.m_callback = 0;     //MyComboBoxCallback;
  			comboParams.m_userPointer = 0;  //this;
  
  			m_tinyVrGui = new TinyVRGui(comboParams, this->m_multiThreadedHelper->m_childGuiHelper->getRenderInterface());
  			m_tinyVrGui->init();
  		}
  
  		if (m_tinyVrGui)
  		{
  			b3Transform tr;
  			tr.setIdentity();
  			btVector3 VRController2Pos = m_physicsServer.getVRTeleportPosition();
  			btQuaternion VRController2Orn = m_physicsServer.getVRTeleportOrientation();
  			tr.setOrigin(b3MakeVector3(VRController2Pos[0], VRController2Pos[1], VRController2Pos[2]));
  			tr.setRotation(b3Quaternion(VRController2Orn[0], VRController2Orn[1], VRController2Orn[2], VRController2Orn[3]));
  			tr = tr * b3Transform(b3Quaternion(0, 0, -SIMD_HALF_PI), b3MakeVector3(0, 0, 0));
  			b3Scalar dt = 0.01;
  			m_tinyVrGui->clearTextArea();
  			m_tinyVrGui->grapicalPrintf(line0, 0, 0, 0, 0, 0, 255);
  			m_tinyVrGui->grapicalPrintf(line1, 0, 16, 255, 255, 255, 255);
  
  			m_tinyVrGui->tick(dt, tr);
  		}
  #endif  //BT_ENABLE_VR
  	}
  	///debug rendering
  	//m_args[0].m_cs->lock();
  
  	//gVRTeleportPos[0] += 0.01;
  	btTransform tr2a, tr2;
  	tr2a.setIdentity();
  	tr2.setIdentity();
  	tr2.setOrigin(m_physicsServer.getVRTeleportPosition());
  	tr2a.setRotation(m_physicsServer.getVRTeleportOrientation());
  	btTransform trTotal = tr2 * tr2a;
  	btTransform trInv = trTotal.inverse();
  
  	btMatrix3x3 vrOffsetRot;
  	vrOffsetRot.setRotation(trInv.getRotation());
  	for (int i = 0; i < 3; i++)
  	{
  		for (int j = 0; j < 3; j++)
  		{
  			vrOffset[i + 4 * j] = vrOffsetRot[i][j];
  		}
  	}
  
  	vrOffset[12] = trInv.getOrigin()[0];
  	vrOffset[13] = trInv.getOrigin()[1];
  	vrOffset[14] = trInv.getOrigin()[2];
  
  	if (m_multiThreadedHelper->m_childGuiHelper->getRenderInterface())
  	{
  		m_multiThreadedHelper->m_childGuiHelper->getRenderInterface()->getActiveCamera()->setVRCameraOffsetTransform(vrOffset);
  	}
  	if (gEnableRendering)
  	{
  		int renderFlags = 0;
  		if (!gEnableSyncPhysicsRendering)
  		{
  			renderFlags |= 1;  //COV_DISABLE_SYNC_RENDERING;
  		}
  		m_physicsServer.renderScene(renderFlags);
  	}
  
  	if (gEnablePicking)
  	{
  		for (int i = 0; i < MAX_VR_CONTROLLERS; i++)
  		{
  			if (m_args[0].m_isVrControllerPicking[i] || m_args[0].m_isVrControllerDragging[i])
  			{
  				btVector3 from = m_args[0].m_vrControllerPos[i];
  				btMatrix3x3 mat(m_args[0].m_vrControllerOrn[i]);
  
  				btVector3 toX = from + mat.getColumn(0);
  				btVector3 toY = from + mat.getColumn(1);
  				btVector3 toZ = from + mat.getColumn(2);
  
  				int width = 2;
  
  				btVector4 color;
  				color = btVector4(1, 0, 0, 1);
  				m_guiHelper->getAppInterface()->m_renderer->drawLine(from, toX, color, width);
  				color = btVector4(0, 1, 0, 1);
  				m_guiHelper->getAppInterface()->m_renderer->drawLine(from, toY, color, width);
  				color = btVector4(0, 0, 1, 1);
  				m_guiHelper->getAppInterface()->m_renderer->drawLine(from, toZ, color, width);
  			}
  		}
  	}
  
  	if (m_guiHelper->getAppInterface()->m_renderer->getActiveCamera()->isVRCamera())
  	{
  		if (!m_physicsServer.isRealTimeSimulationEnabled() && !gActivedVRRealTimeSimulation)
  		{
  			//only activate real-time simulation once (for backward compatibility)
  			gActivedVRRealTimeSimulation = true;
  			m_physicsServer.enableRealTimeSimulation(1);
  		}
  	}
  
  	drawUserDebugLines();
  
  	//m_args[0].m_cs->unlock();
  }
  
  void PhysicsServerExample::physicsDebugDraw(int debugDrawFlags)
  {
  	m_renderedFrames++;
  
  	if (gEnableSyncPhysicsRendering)
  	{
  		m_physicsServer.syncPhysicsToGraphics();
  	}
  
  	drawUserDebugLines();
  
  	if (gEnableRendering)
  	{
  		///debug rendering
  		//m_physicsServer.physicsDebugDraw(debugDrawFlags);
  		m_args[0].m_csGUI->lock();
  		//draw stuff and flush?
  		this->m_multiThreadedHelper->m_debugDraw->drawDebugDrawerLines();
  		m_args[0].m_debugDrawFlags = debugDrawFlags;
  		gEnableUpdateDebugDrawLines = true;
  		m_args[0].m_csGUI->unlock();
  	}
  }
  
  btVector3 PhysicsServerExample::getRayTo(int x, int y)
  {
  	CommonRenderInterface* renderer = m_guiHelper->getRenderInterface();
  
  	if (!renderer)
  	{
  		btAssert(0);
  		return btVector3(0, 0, 0);
  	}
  
  	float top = 1.f;
  	float bottom = -1.f;
  	float nearPlane = 1.f;
  	float tanFov = (top - bottom) * 0.5f / nearPlane;
  	float fov = btScalar(2.0) * btAtan(tanFov);
  
  	btVector3 camPos, camTarget;
  	renderer->getActiveCamera()->getCameraPosition(camPos);
  	renderer->getActiveCamera()->getCameraTargetPosition(camTarget);
  
  	btVector3 rayFrom = camPos;
  	btVector3 rayForward = (camTarget - camPos);
  	rayForward.normalize();
  	float farPlane = 10000.f;
  	rayForward *= farPlane;
  
  	btVector3 rightOffset;
  	btVector3 cameraUp = btVector3(0, 0, 0);
  	cameraUp[m_guiHelper->getAppInterface()->getUpAxis()] = 1;
  
  	btVector3 vertical = cameraUp;
  
  	btVector3 hor;
  	hor = rayForward.cross(vertical);
  	hor.safeNormalize();
  	vertical = hor.cross(rayForward);
  	vertical.safeNormalize();
  
  	float tanfov = tanf(0.5f * fov);
  
  	hor *= 2.f * farPlane * tanfov;
  	vertical *= 2.f * farPlane * tanfov;
  
  	btScalar aspect;
  	float width = float(renderer->getScreenWidth());
  	float height = float(renderer->getScreenHeight());
  
  	aspect = width / height;
  
  	hor *= aspect;
  
  	btVector3 rayToCenter = rayFrom + rayForward;
  	btVector3 dHor = hor * 1.f / width;
  	btVector3 dVert = vertical * 1.f / height;
  
  	btVector3 rayTo = rayToCenter - 0.5f * hor + 0.5f * vertical;
  	rayTo += btScalar(x) * dHor;
  	rayTo -= btScalar(y) * dVert;
  	return rayTo;
  }
  
  void PhysicsServerExample::vrControllerButtonCallback(int controllerId, int button, int state, float pos[4], float orn[4])
  {
  	//printf("controllerId %d, button=%d\n",controllerId, button);
  
  	if (controllerId < 0 || controllerId >= MAX_VR_CONTROLLERS)
  		return;
  
  	if (gGraspingController < 0)
  	{
  		gGraspingController = controllerId;
  	}
  
  	btTransform trLocal;
  	trLocal.setIdentity();
  	trLocal.setRotation(btQuaternion(btVector3(0, 0, 1), SIMD_HALF_PI) * btQuaternion(btVector3(0, 1, 0), SIMD_HALF_PI));
  
  	btTransform trOrg;
  	trOrg.setIdentity();
  	trOrg.setOrigin(btVector3(pos[0], pos[1], pos[2]));
  	trOrg.setRotation(btQuaternion(orn[0], orn[1], orn[2], orn[3]));
  
  	btTransform tr2a;
  	tr2a.setIdentity();
  	btTransform tr2;
  	tr2.setIdentity();
  
  	tr2.setOrigin(m_physicsServer.getVRTeleportPosition());
  	tr2a.setRotation(m_physicsServer.getVRTeleportOrientation());
  
  	btTransform trTotal = tr2 * tr2a * trOrg * trLocal;
  
  	if (controllerId != gGraspingController)
  	{
  		if (button == 1 && state == 0)
  		{
  		}
  	}
  	else
  	{
  		if (button == 1)
  		{
  			if (state == 1)
  			{
  				gDebugRenderToggle = 1;
  			}
  			else
  			{
  				gDebugRenderToggle = 0;
  #if 0  //it confuses people, make it into a debug option in a VR GUI?
  				if (simTimeScalingFactor==0)
  				{
  					simTimeScalingFactor = 1;
  				} else
  				{
  					if (simTimeScalingFactor==1)
  					{
  						simTimeScalingFactor = 0.25;
  					}
  					else
  					{
  						simTimeScalingFactor = 0;
  					}
  				}
  #endif
  			}
  		}
  		else
  		{
  		}
  	}
  
  	if (button == 32 && state == 0)
  	{
  		if (controllerId == gGraspingController)
  		{
  		}
  		else
  		{
  			//			gEnableKukaControl = !gEnableKukaControl;
  		}
  	}
  
  	if (button == 1 && gEnableTeleporting)
  	{
  		m_args[0].m_isVrControllerTeleporting[controllerId] = true;
  	}
  
  	if (controllerId == gGraspingController && (button == 33))
  	{
  	}
  	else
  	{
  		if (button == 33 && gEnablePicking)
  		{
  			m_args[0].m_isVrControllerPicking[controllerId] = (state != 0);
  			m_args[0].m_isVrControllerReleasing[controllerId] = (state == 0);
  		}
  
  		if ((button == 33) || (button == 1))
  		{
  			//			m_args[0].m_vrControllerPos[controllerId].setValue(pos[0] + gVRTeleportPos[0], pos[1] + gVRTeleportPos[1], pos[2] + gVRTeleportPos[2]);
  			//		m_args[0].m_vrControllerOrn[controllerId].setValue(orn[0], orn[1], orn[2], orn[3]);
  			m_args[0].m_vrControllerPos[controllerId] = trTotal.getOrigin();
  			m_args[0].m_vrControllerOrn[controllerId] = trTotal.getRotation();
  		}
  	}
  
  	m_args[0].m_csGUI->lock();
  	m_args[0].m_vrControllerEvents[controllerId].m_controllerId = controllerId;
  	m_args[0].m_vrControllerEvents[controllerId].m_deviceType = VR_DEVICE_CONTROLLER;
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[0] = trTotal.getOrigin()[0];
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[1] = trTotal.getOrigin()[1];
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[2] = trTotal.getOrigin()[2];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[0] = trTotal.getRotation()[0];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[1] = trTotal.getRotation()[1];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[2] = trTotal.getRotation()[2];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[3] = trTotal.getRotation()[3];
  	m_args[0].m_vrControllerEvents[controllerId].m_numButtonEvents++;
  	if (state)
  	{
  		m_args[0].m_vrControllerEvents[controllerId].m_buttons[button] |= eButtonIsDown + eButtonTriggered;
  	}
  	else
  	{
  		m_args[0].m_vrControllerEvents[controllerId].m_buttons[button] |= eButtonReleased;
  		m_args[0].m_vrControllerEvents[controllerId].m_buttons[button] &= ~eButtonIsDown;
  	}
  	m_args[0].m_csGUI->unlock();
  }
  
  void PhysicsServerExample::vrControllerMoveCallback(int controllerId, float pos[4], float orn[4], float analogAxis, float auxAnalogAxes[10])
  {
  	if (controllerId < 0 || controllerId >= MAX_VR_CONTROLLERS)
  	{
  		printf("Controller Id exceeds max: %d > %d", controllerId, MAX_VR_CONTROLLERS);
  		return;
  	}
  
  	btTransform trLocal;
  	trLocal.setIdentity();
  	trLocal.setRotation(btQuaternion(btVector3(0, 0, 1), SIMD_HALF_PI) * btQuaternion(btVector3(0, 1, 0), SIMD_HALF_PI));
  
  	btTransform trOrg;
  	trOrg.setIdentity();
  	trOrg.setOrigin(btVector3(pos[0], pos[1], pos[2]));
  	trOrg.setRotation(btQuaternion(orn[0], orn[1], orn[2], orn[3]));
  
  	btTransform tr2a;
  	tr2a.setIdentity();
  	btTransform tr2;
  	tr2.setIdentity();
  
  	tr2.setOrigin(m_physicsServer.getVRTeleportPosition());
  	tr2a.setRotation(m_physicsServer.getVRTeleportOrientation());
  
  	btTransform trTotal = tr2 * tr2a * trOrg * trLocal;
  
  	if (controllerId == gGraspingController)
  	{
  	}
  	else
  	{
  		m_args[0].m_vrControllerPos[controllerId] = trTotal.getOrigin();
  		m_args[0].m_vrControllerOrn[controllerId] = trTotal.getRotation();
  	}
  
  	m_args[0].m_csGUI->lock();
  	m_args[0].m_vrControllerEvents[controllerId].m_controllerId = controllerId;
  	m_args[0].m_vrControllerEvents[controllerId].m_deviceType = VR_DEVICE_CONTROLLER;
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[0] = trTotal.getOrigin()[0];
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[1] = trTotal.getOrigin()[1];
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[2] = trTotal.getOrigin()[2];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[0] = trTotal.getRotation()[0];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[1] = trTotal.getRotation()[1];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[2] = trTotal.getRotation()[2];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[3] = trTotal.getRotation()[3];
  	m_args[0].m_vrControllerEvents[controllerId].m_numMoveEvents++;
  	m_args[0].m_vrControllerEvents[controllerId].m_analogAxis = analogAxis;
  	for (int i = 0; i < 10; i++)
  	{
  		m_args[0].m_vrControllerEvents[controllerId].m_auxAnalogAxis[i] = auxAnalogAxes[i];
  	}
  
  	m_args[0].m_csGUI->unlock();
  }
  
  void PhysicsServerExample::vrHMDMoveCallback(int controllerId, float pos[4], float orn[4])
  {
  	if (controllerId < 0 || controllerId >= MAX_VR_CONTROLLERS)
  	{
  		printf("Controller Id exceeds max: %d > %d", controllerId, MAX_VR_CONTROLLERS);
  		return;
  	}
  
  	//we may need to add some trLocal transform, to align the camera to our preferences
  	btTransform trLocal;
  	trLocal.setIdentity();
  	//	trLocal.setRotation(btQuaternion(btVector3(0, 0, 1), SIMD_HALF_PI)*btQuaternion(btVector3(0, 1, 0), SIMD_HALF_PI));
  
  	btTransform trOrg;
  	trOrg.setIdentity();
  	trOrg.setOrigin(btVector3(pos[0], pos[1], pos[2]));
  	trOrg.setRotation(btQuaternion(orn[0], orn[1], orn[2], orn[3]));
  
  	btTransform tr2a;
  	tr2a.setIdentity();
  	btTransform tr2;
  	tr2.setIdentity();
  	tr2.setOrigin(m_physicsServer.getVRTeleportPosition());
  	tr2a.setRotation(m_physicsServer.getVRTeleportOrientation());
  	btTransform trTotal = tr2 * tr2a * trOrg * trLocal;
  
  	m_args[0].m_csGUI->lock();
  	m_args[0].m_vrControllerEvents[controllerId].m_controllerId = controllerId;
  	m_args[0].m_vrControllerEvents[controllerId].m_deviceType = VR_DEVICE_HMD;
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[0] = trTotal.getOrigin()[0];
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[1] = trTotal.getOrigin()[1];
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[2] = trTotal.getOrigin()[2];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[0] = trTotal.getRotation()[0];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[1] = trTotal.getRotation()[1];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[2] = trTotal.getRotation()[2];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[3] = trTotal.getRotation()[3];
  	m_args[0].m_vrControllerEvents[controllerId].m_numMoveEvents++;
  	m_args[0].m_csGUI->unlock();
  }
  void PhysicsServerExample::vrGenericTrackerMoveCallback(int controllerId, float pos[4], float orn[4])
  {
  	if (controllerId < 0 || controllerId >= MAX_VR_CONTROLLERS)
  	{
  		printf("Controller Id exceeds max: %d > %d", controllerId, MAX_VR_CONTROLLERS);
  		return;
  	}
  
  	//we may need to add some trLocal transform, to align the camera to our preferences
  	btTransform trLocal;
  	trLocal.setIdentity();
  	trLocal.setRotation(btQuaternion(btVector3(0, 0, 1), SIMD_HALF_PI) * btQuaternion(btVector3(0, 1, 0), SIMD_HALF_PI));
  
  	btTransform trOrg;
  	trOrg.setIdentity();
  	trOrg.setOrigin(btVector3(pos[0], pos[1], pos[2]));
  	trOrg.setRotation(btQuaternion(orn[0], orn[1], orn[2], orn[3]));
  
  	btTransform tr2a;
  	tr2a.setIdentity();
  	btTransform tr2;
  	tr2.setIdentity();
  	tr2.setOrigin(m_physicsServer.getVRTeleportPosition());
  	tr2a.setRotation(m_physicsServer.getVRTeleportOrientation());
  	btTransform trTotal = tr2 * tr2a * trOrg * trLocal;
  
  	m_args[0].m_csGUI->lock();
  	m_args[0].m_vrControllerEvents[controllerId].m_controllerId = controllerId;
  	m_args[0].m_vrControllerEvents[controllerId].m_deviceType = VR_DEVICE_GENERIC_TRACKER;
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[0] = trTotal.getOrigin()[0];
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[1] = trTotal.getOrigin()[1];
  	m_args[0].m_vrControllerEvents[controllerId].m_pos[2] = trTotal.getOrigin()[2];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[0] = trTotal.getRotation()[0];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[1] = trTotal.getRotation()[1];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[2] = trTotal.getRotation()[2];
  	m_args[0].m_vrControllerEvents[controllerId].m_orn[3] = trTotal.getRotation()[3];
  	m_args[0].m_vrControllerEvents[controllerId].m_numMoveEvents++;
  	m_args[0].m_csGUI->unlock();
  }
  
  extern int gSharedMemoryKey;
  
  class CommonExampleInterface* PhysicsServerCreateFuncInternal(struct CommonExampleOptions& options)
  {
  	MultiThreadedOpenGLGuiHelper* guiHelperWrapper = new MultiThreadedOpenGLGuiHelper(options.m_guiHelper->getAppInterface(), options.m_guiHelper, options.m_skipGraphicsUpdate);
  
  	PhysicsServerExample* example = new PhysicsServerExample(guiHelperWrapper,
  															 options.m_commandProcessorCreation,
  															 options.m_sharedMem,
  															 options.m_option);
  
  	if (gSharedMemoryKey >= 0)
  	{
  		example->setSharedMemoryKey(gSharedMemoryKey);
  	}
  	if (options.m_option & PHYSICS_SERVER_ENABLE_COMMAND_LOGGING)
  	{
  		example->enableCommandLogging();
  	}
  	if (options.m_option & PHYSICS_SERVER_REPLAY_FROM_COMMAND_LOG)
  	{
  		example->replayFromLogFile();
  	}
  	return example;
  }
  