#include "PhysicsServerCommandProcessor.h"
#include "../CommonInterfaces/CommonRenderInterface.h"
#include "plugins/b3PluginCollisionInterface.h"
#include "../Importers/ImportURDFDemo/BulletUrdfImporter.h"
#include "../Importers/ImportURDFDemo/MyMultiBodyCreator.h"
#include "../Importers/ImportURDFDemo/URDF2Bullet.h"
#include "../Extras/InverseDynamics/btMultiBodyTreeCreator.hpp"

#include "BulletCollision/CollisionDispatch/btInternalEdgeUtility.h"

#include "BulletDynamics/MLCPSolvers/btDantzigSolver.h"
#include "BulletDynamics/MLCPSolvers/btSolveProjectedGaussSeidel.h"
#include "BulletDynamics/Featherstone/btMultiBodyMLCPConstraintSolver.h"

#include "BulletDynamics/Featherstone/btMultiBodyConstraintSolver.h"
#include "BulletDynamics/Featherstone/btMultiBodyPoint2Point.h"
#include "BulletDynamics/Featherstone/btMultiBodyLinkCollider.h"
#include "BulletDynamics/Featherstone/btMultiBodyJointFeedback.h"
#include "BulletDynamics/Featherstone/btMultiBodyFixedConstraint.h"
#include "BulletDynamics/Featherstone/btMultiBodyGearConstraint.h"
#include "../Importers/ImportURDFDemo/UrdfParser.h"
#include "../Utils/b3ResourcePath.h"
#include "Bullet3Common/b3FileUtils.h"
#include "../OpenGLWindow/GLInstanceGraphicsShape.h"
#include "BulletDynamics/Featherstone/btMultiBodySliderConstraint.h"
#include "BulletDynamics/Featherstone/btMultiBodyPoint2Point.h"
#include "BulletCollision/NarrowPhaseCollision/btPersistentManifold.h"
#include "Bullet3Common/b3HashMap.h"
#include "../Utils/ChromeTraceUtil.h"
#include "stb_image/stb_image.h"
#include "BulletInverseDynamics/MultiBodyTree.hpp"
#include "IKTrajectoryHelper.h"
#include "btBulletDynamicsCommon.h"
#include "../Utils/RobotLoggingUtil.h"
#include "LinearMath/btTransform.h"
#include "../Importers/ImportMJCFDemo/BulletMJCFImporter.h"
#include "../Importers/ImportObjDemo/LoadMeshFromObj.h"
#include "../Importers/ImportSTLDemo/LoadMeshFromSTL.h"
#include "../Extras/Serialize/BulletWorldImporter/btMultiBodyWorldImporter.h"
#include "BulletDynamics/Featherstone/btMultiBodyJointMotor.h"
#include "LinearMath/btSerializer.h"
#include "Bullet3Common/b3Logging.h"
#include "../CommonInterfaces/CommonGUIHelperInterface.h"
#include "SharedMemoryCommands.h"
#include "LinearMath/btRandom.h"
#include "Bullet3Common/b3ResizablePool.h"
#include "../Utils/b3Clock.h"
#include "b3PluginManager.h"
#include "../Extras/Serialize/BulletFileLoader/btBulletFile.h"
#include "BulletCollision/NarrowPhaseCollision/btRaycastCallback.h"
#include "LinearMath/TaskScheduler/btThreadSupportInterface.h"

#ifndef SKIP_COLLISION_FILTER_PLUGIN
#include "plugins/collisionFilterPlugin/collisionFilterPlugin.h"
#endif

#ifdef ENABLE_STATIC_GRPC_PLUGIN
#include "plugins/grpcPlugin/grpcPlugin.h"
#endif  //ENABLE_STATIC_GRPC_PLUGIN

#ifndef SKIP_STATIC_PD_CONTROL_PLUGIN
#include "plugins/pdControlPlugin/pdControlPlugin.h"
#endif  //SKIP_STATIC_PD_CONTROL_PLUGIN

#ifdef STATIC_LINK_VR_PLUGIN
#include "plugins/vrSyncPlugin/vrSyncPlugin.h"
#endif

#ifdef STATIC_EGLRENDERER_PLUGIN
#include "plugins/eglPlugin/eglRendererPlugin.h"
#endif  //STATIC_EGLRENDERER_PLUGIN

#ifndef SKIP_STATIC_TINYRENDERER_PLUGIN
#include "plugins/tinyRendererPlugin/tinyRendererPlugin.h"
#endif

#ifdef B3_ENABLE_TINY_AUDIO
#include "../TinyAudio/b3SoundEngine.h"
#endif

#ifndef SKIP_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD
#include "BulletSoftBody/btSoftBodyRigidBodyCollisionConfiguration.h"
#include "BulletSoftBody/btSoftBodySolvers.h"
#include "BulletSoftBody/btSoftBodyHelpers.h"
#include "BulletSoftBody/btSoftMultiBodyDynamicsWorld.h"
#include "../SoftDemo/BunnyMesh.h"
#else
#include "BulletDynamics/Featherstone/btMultiBodyDynamicsWorld.h"
#endif

extern bool gJointFeedbackInWorldSpace;
extern bool gJointFeedbackInJointFrame;

int gInternalSimFlags = 0;
bool gResetSimulation = 0;
int gVRTrackingObjectUniqueId = -1;
int gVRTrackingObjectFlag = VR_CAMERA_TRACK_OBJECT_ORIENTATION;

btTransform gVRTrackingObjectTr = btTransform::getIdentity();

btVector3 gVRTeleportPos1(0, 0, 0);
btQuaternion gVRTeleportOrn(0, 0, 0, 1);

btScalar simTimeScalingFactor = 1;
btScalar gRhsClamp = 1.f;

struct UrdfLinkNameMapUtil
{
	btMultiBody* m_mb;
	btAlignedObjectArray<btGeneric6DofSpring2Constraint*> m_rigidBodyJoints;

	btDefaultSerializer* m_memSerializer;

	UrdfLinkNameMapUtil() : m_mb(0), m_memSerializer(0)
	{
	}
	virtual ~UrdfLinkNameMapUtil()
	{
	}
};

class b3ThreadPool
{
public:
	b3ThreadPool(const char* name = "b3ThreadPool")
	{
		btThreadSupportInterface::ConstructionInfo info(name, threadFunction);
		m_threadSupportInterface = btThreadSupportInterface::create(info);
	}

	~b3ThreadPool()
	{
		delete m_threadSupportInterface;
	}

	const int numWorkers() const { return m_threadSupportInterface->getNumWorkerThreads(); }

	void runTask(int threadIdx, btThreadSupportInterface::ThreadFunc func, void* arg)
	{
		FunctionContext& ctx = m_functionContexts[threadIdx];
		ctx.func = func;
		ctx.arg = arg;
		m_threadSupportInterface->runTask(threadIdx, (void*)&ctx);
	}

	void waitForAllTasks()
	{
		BT_PROFILE("b3ThreadPool_waitForAllTasks");
		m_threadSupportInterface->waitForAllTasks();
	}

private:
	struct FunctionContext
	{
		btThreadSupportInterface::ThreadFunc func;
		void* arg;
	};

	static void threadFunction(void* userPtr)
	{
		BT_PROFILE("b3ThreadPool_threadFunction");
		FunctionContext* ctx = (FunctionContext*)userPtr;
		ctx->func(ctx->arg);
	}

	btThreadSupportInterface* m_threadSupportInterface;
	FunctionContext m_functionContexts[BT_MAX_THREAD_COUNT];
};

struct SharedMemoryDebugDrawer : public btIDebugDraw
{
	int m_debugMode;
	btAlignedObjectArray<SharedMemLines> m_lines2;

	SharedMemoryDebugDrawer()
		: m_debugMode(0)
	{
	}
	virtual void drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, const btVector3& color)
	{
	}

	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 drawLine(const btVector3& from, const btVector3& to, const btVector3& color)
	{
		SharedMemLines line;
		line.m_from = from;
		line.m_to = to;
		line.m_color = color;
		m_lines2.push_back(line);
	}
};

struct InternalVisualShapeData
{
	int m_tinyRendererVisualShapeIndex;
	int m_OpenGLGraphicsIndex;

	b3AlignedObjectArray<UrdfVisual> m_visualShapes;

	b3AlignedObjectArray<std::string> m_pathPrefixes;

	void clear()
	{
		m_tinyRendererVisualShapeIndex = -1;
		m_OpenGLGraphicsIndex = -1;
		m_visualShapes.clear();
		m_pathPrefixes.clear();
	}
};

struct InternalCollisionShapeData
{
	btCollisionShape* m_collisionShape;
	b3AlignedObjectArray<UrdfCollision> m_urdfCollisionObjects;
	int m_used;
	InternalCollisionShapeData()
		: m_collisionShape(0),
		  m_used(0)
	{
	}
	void clear()
	{
		m_collisionShape = 0;
		m_used = 0;
	}
};

#include "SharedMemoryUserData.h"

struct InternalBodyData
{
	btMultiBody* m_multiBody;
	btRigidBody* m_rigidBody;
#ifndef SKIP_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD
	btSoftBody* m_softBody;
#endif
	int m_testData;
	std::string m_bodyName;

	btTransform m_rootLocalInertialFrame;
	btAlignedObjectArray<btTransform> m_linkLocalInertialFrames;
	btAlignedObjectArray<btGeneric6DofSpring2Constraint*> m_rigidBodyJoints;
	btAlignedObjectArray<std::string> m_rigidBodyJointNames;
	btAlignedObjectArray<std::string> m_rigidBodyLinkNames;
	btAlignedObjectArray<int> m_userDataHandles;

#ifdef B3_ENABLE_TINY_AUDIO
	b3HashMap<btHashInt, SDFAudioSource> m_audioSources;
#endif  //B3_ENABLE_TINY_AUDIO

	InternalBodyData()
	{
		clear();
	}

	void clear()
	{
		m_multiBody = 0;
		m_rigidBody = 0;
#ifndef SKIP_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD
		m_softBody = 0;
#endif
		m_testData = 0;
		m_bodyName = "";
		m_rootLocalInertialFrame.setIdentity();
		m_linkLocalInertialFrames.clear();
		m_rigidBodyJoints.clear();
		m_rigidBodyJointNames.clear();
		m_rigidBodyLinkNames.clear();
		m_userDataHandles.clear();
	}
};

struct InteralUserConstraintData
{
	btTypedConstraint* m_rbConstraint;
	btMultiBodyConstraint* m_mbConstraint;

	b3UserConstraint m_userConstraintData;

	InteralUserConstraintData()
		: m_rbConstraint(0),
		  m_mbConstraint(0)
	{
	}
};

struct InternalTextureData
{
	int m_tinyRendererTextureId;
	int m_openglTextureId;
	void clear()
	{
		m_tinyRendererTextureId = -1;
		m_openglTextureId = -1;
	}
};

typedef b3PoolBodyHandle<InternalTextureData> InternalTextureHandle;
typedef b3PoolBodyHandle<InternalBodyData> InternalBodyHandle;
typedef b3PoolBodyHandle<InternalCollisionShapeData> InternalCollisionShapeHandle;
typedef b3PoolBodyHandle<InternalVisualShapeData> InternalVisualShapeHandle;

class btCommandChunk
{
public:
	int m_chunkCode;
	int m_length;
	void* m_oldPtr;
	int m_dna_nr;
	int m_number;
};

class bCommandChunkPtr4
{
public:
	bCommandChunkPtr4() {}
	int code;
	int len;
	union {
		int m_uniqueInt;
	};
	int dna_nr;
	int nr;
};

// ----------------------------------------------------- //
class bCommandChunkPtr8
{
public:
	bCommandChunkPtr8() {}
	int code, len;
	union {
		int m_uniqueInts[2];
	};
	int dna_nr, nr;
};

struct CommandLogger
{
	FILE* m_file;

	void writeHeader(unsigned char* buffer) const
	{
#ifdef BT_USE_DOUBLE_PRECISION
		memcpy(buffer, "BT3CMDd", 7);
#else
		memcpy(buffer, "BT3CMDf", 7);
#endif  //BT_USE_DOUBLE_PRECISION

		int littleEndian = 1;
		littleEndian = ((char*)&littleEndian)[0];

		if (sizeof(void*) == 8)
		{
			buffer[7] = '-';
		}
		else
		{
			buffer[7] = '_';
		}

		if (littleEndian)
		{
			buffer[8] = 'v';
		}
		else
		{
			buffer[8] = 'V';
		}

		buffer[9] = 0;
		buffer[10] = 0;
		buffer[11] = 0;

		int ver = btGetVersion();
		if (ver >= 0 && ver < 999)
		{
			sprintf((char*)&buffer[9], "%d", ver);
		}
	}

	void logCommand(const SharedMemoryCommand& command)
	{
		if (m_file)
		{
			btCommandChunk chunk;
			chunk.m_chunkCode = command.m_type;
			chunk.m_oldPtr = 0;
			chunk.m_dna_nr = 0;
			chunk.m_length = sizeof(SharedMemoryCommand);
			chunk.m_number = 1;
			fwrite((const char*)&chunk, sizeof(btCommandChunk), 1, m_file);

			switch (command.m_type)
			{
				case CMD_LOAD_MJCF:
				{
					fwrite((const char*)&command.m_updateFlags, sizeof(int), 1, m_file);
					fwrite((const char*)&command.m_mjcfArguments, sizeof(MjcfArgs), 1, m_file);
					break;
				}
				case CMD_REQUEST_BODY_INFO:
				{
					fwrite((const char*)&command.m_updateFlags, sizeof(int), 1, m_file);
					fwrite((const char*)&command.m_sdfRequestInfoArgs, sizeof(SdfRequestInfoArgs), 1, m_file);
					break;
				}
				case CMD_REQUEST_VISUAL_SHAPE_INFO:
				{
					fwrite((const char*)&command.m_updateFlags, sizeof(int), 1, m_file);
					fwrite((const char*)&command.m_requestVisualShapeDataArguments, sizeof(RequestVisualShapeDataArgs), 1, m_file);
					break;
				}
				case CMD_LOAD_URDF:
				{
					fwrite((const char*)&command.m_updateFlags, sizeof(int), 1, m_file);
					fwrite((const char*)&command.m_urdfArguments, sizeof(UrdfArgs), 1, m_file);
					break;
				}
				case CMD_INIT_POSE:
				{
					fwrite((const char*)&command.m_updateFlags, sizeof(int), 1, m_file);
					fwrite((const char*)&command.m_initPoseArgs, sizeof(InitPoseArgs), 1, m_file);
					break;
				};
				case CMD_REQUEST_ACTUAL_STATE:
				{
					fwrite((const char*)&command.m_updateFlags, sizeof(int), 1, m_file);
					fwrite((const char*)&command.m_requestActualStateInformationCommandArgument,
						   sizeof(RequestActualStateArgs), 1, m_file);
					break;
				};
				case CMD_SEND_DESIRED_STATE:
				{
					fwrite((const char*)&command.m_updateFlags, sizeof(int), 1, m_file);
					fwrite((const char*)&command.m_sendDesiredStateCommandArgument, sizeof(SendDesiredStateArgs), 1, m_file);
					break;
				}
				case CMD_SEND_PHYSICS_SIMULATION_PARAMETERS:
				{
					fwrite((const char*)&command.m_updateFlags, sizeof(int), 1, m_file);
					fwrite((const char*)&command.m_physSimParamArgs, sizeof(b3PhysicsSimulationParameters), 1, m_file);
					break;
				}
				case CMD_REQUEST_CONTACT_POINT_INFORMATION:
				{
					fwrite((const char*)&command.m_updateFlags, sizeof(int), 1, m_file);
					fwrite((const char*)&command.m_requestContactPointArguments, sizeof(RequestContactDataArgs), 1, m_file);
					break;
				}
				case CMD_STEP_FORWARD_SIMULATION:
				case CMD_RESET_SIMULATION:
				case CMD_REQUEST_INTERNAL_DATA:
				{
					break;
				};
				default:
				{
					fwrite((const char*)&command, sizeof(SharedMemoryCommand), 1, m_file);
				}
			};
		}
	}

	CommandLogger(const char* fileName)
	{
		m_file = fopen(fileName, "wb");
		if (m_file)
		{
			unsigned char buf[15];
			buf[12] = 12;
			buf[13] = 13;
			buf[14] = 14;
			writeHeader(buf);
			fwrite(buf, 12, 1, m_file);
		}
	}
	virtual ~CommandLogger()
	{
		if (m_file)
		{
			fclose(m_file);
		}
	}
};

struct CommandLogPlayback
{
	unsigned char m_header[12];
	FILE* m_file;
	bool m_bitsVary;
	bool m_fileIs64bit;

	CommandLogPlayback(const char* fileName)
	{
		m_file = fopen(fileName, "rb");
		if (m_file)
		{
			size_t bytesRead;
			bytesRead = fread(m_header, 12, 1, m_file);
		}
		unsigned char c = m_header[7];
		m_fileIs64bit = (c == '-');

		const bool VOID_IS_8 = ((sizeof(void*) == 8));
		m_bitsVary = (VOID_IS_8 != m_fileIs64bit);
	}
	virtual ~CommandLogPlayback()
	{
		if (m_file)
		{
			fclose(m_file);
			m_file = 0;
		}
	}
	bool processNextCommand(SharedMemoryCommand* cmd)
	{
//for a little while, keep this flag to be able to read 'old' log files
//#define BACKWARD_COMPAT
#if BACKWARD_COMPAT
		SharedMemoryCommand unused;
#endif  //BACKWARD_COMPAT
		bool result = false;
		size_t s = 0;
		if (m_file)
		{
			int commandType = -1;

			if (m_fileIs64bit)
			{
				bCommandChunkPtr8 chunk8;
				s = fread((void*)&chunk8, sizeof(bCommandChunkPtr8), 1, m_file);
				commandType = chunk8.code;
			}
			else
			{
				bCommandChunkPtr4 chunk4;
				s = fread((void*)&chunk4, sizeof(bCommandChunkPtr4), 1, m_file);
				commandType = chunk4.code;
			}

			if (s == 1)
			{
				memset(cmd, 0, sizeof(SharedMemoryCommand));
				cmd->m_type = commandType;

#ifdef BACKWARD_COMPAT
				s = fread(&unused, sizeof(SharedMemoryCommand), 1, m_file);
				cmd->m_updateFlags = unused.m_updateFlags;
#endif

				switch (commandType)
				{
					case CMD_LOAD_MJCF:
					{
#ifdef BACKWARD_COMPAT
						cmd->m_mjcfArguments = unused.m_mjcfArguments;
#else
						s = fread(&cmd->m_updateFlags, sizeof(int), 1, m_file);
						s = fread(&cmd->m_mjcfArguments, sizeof(MjcfArgs), 1, m_file);
#endif
						result = true;
						break;
					}
					case CMD_REQUEST_BODY_INFO:
					{
#ifdef BACKWARD_COMPAT
						cmd->m_sdfRequestInfoArgs = unused.m_sdfRequestInfoArgs;
#else
						s = fread(&cmd->m_updateFlags, sizeof(int), 1, m_file);
						s = fread(&cmd->m_sdfRequestInfoArgs, sizeof(SdfRequestInfoArgs), 1, m_file);
#endif
						result = true;
						break;
					}
					case CMD_REQUEST_VISUAL_SHAPE_INFO:
					{
#ifdef BACKWARD_COMPAT
						cmd->m_requestVisualShapeDataArguments = unused.m_requestVisualShapeDataArguments;
#else
						s = fread(&cmd->m_updateFlags, sizeof(int), 1, m_file);
						s = fread(&cmd->m_requestVisualShapeDataArguments, sizeof(RequestVisualShapeDataArgs), 1, m_file);
#endif
						result = true;
						break;
					}
					case CMD_LOAD_URDF:
					{
#ifdef BACKWARD_COMPAT
						cmd->m_urdfArguments = unused.m_urdfArguments;
#else
						s = fread(&cmd->m_updateFlags, sizeof(int), 1, m_file);
						s = fread(&cmd->m_urdfArguments, sizeof(UrdfArgs), 1, m_file);
#endif
						result = true;
						break;
					}
					case CMD_INIT_POSE:
					{
#ifdef BACKWARD_COMPAT
						cmd->m_initPoseArgs = unused.m_initPoseArgs;
#else
						s = fread(&cmd->m_updateFlags, sizeof(int), 1, m_file);
						s = fread(&cmd->m_initPoseArgs, sizeof(InitPoseArgs), 1, m_file);

#endif
						result = true;
						break;
					};
					case CMD_REQUEST_ACTUAL_STATE:
					{
#ifdef BACKWARD_COMPAT
						cmd->m_requestActualStateInformationCommandArgument = unused.m_requestActualStateInformationCommandArgument;
#else
						s = fread(&cmd->m_updateFlags, sizeof(int), 1, m_file);
						s = fread(&cmd->m_requestActualStateInformationCommandArgument, sizeof(RequestActualStateArgs), 1, m_file);
#endif
						result = true;
						break;
					};
					case CMD_SEND_DESIRED_STATE:
					{
#ifdef BACKWARD_COMPAT
						cmd->m_sendDesiredStateCommandArgument = unused.m_sendDesiredStateCommandArgument;
#else
						s = fread(&cmd->m_updateFlags, sizeof(int), 1, m_file);
						s = fread(&cmd->m_sendDesiredStateCommandArgument, sizeof(SendDesiredStateArgs), 1, m_file);

#endif
						result = true;
						break;
					}
					case CMD_SEND_PHYSICS_SIMULATION_PARAMETERS:
					{
#ifdef BACKWARD_COMPAT
						cmd->m_physSimParamArgs = unused.m_physSimParamArgs;
#else
						s = fread(&cmd->m_updateFlags, sizeof(int), 1, m_file);
						s = fread(&cmd->m_physSimParamArgs, sizeof(b3PhysicsSimulationParameters), 1, m_file);

#endif
						result = true;
						break;
					}
					case CMD_REQUEST_CONTACT_POINT_INFORMATION:
					{
#ifdef BACKWARD_COMPAT
						cmd->m_requestContactPointArguments = unused.m_requestContactPointArguments;
#else
						s = fread(&cmd->m_updateFlags, sizeof(int), 1, m_file);
						s = fread(&cmd->m_requestContactPointArguments, sizeof(RequestContactDataArgs), 1, m_file);

#endif
						result = true;
						break;
					}
					case CMD_STEP_FORWARD_SIMULATION:
					case CMD_RESET_SIMULATION:
					case CMD_REQUEST_INTERNAL_DATA:
					{
						result = true;
						break;
					}
					default:
					{
						s = fread(cmd, sizeof(SharedMemoryCommand), 1, m_file);
						result = (s == 1);
					}
				};
			}
		}
		return result;
	}
};

struct SaveWorldObjectData
{
	b3AlignedObjectArray<int> m_bodyUniqueIds;
	std::string m_fileName;
};

struct MyBroadphaseCallback : public btBroadphaseAabbCallback
{
	b3AlignedObjectArray<int> m_bodyUniqueIds;
	b3AlignedObjectArray<int> m_links;

	MyBroadphaseCallback()
	{
	}
	virtual ~MyBroadphaseCallback()
	{
	}
	void clear()
	{
		m_bodyUniqueIds.clear();
		m_links.clear();
	}
	virtual bool process(const btBroadphaseProxy* proxy)
	{
		btCollisionObject* colObj = (btCollisionObject*)proxy->m_clientObject;
		btMultiBodyLinkCollider* mbl = btMultiBodyLinkCollider::upcast(colObj);
		if (mbl)
		{
			int bodyUniqueId = mbl->m_multiBody->getUserIndex2();
			m_bodyUniqueIds.push_back(bodyUniqueId);
			m_links.push_back(mbl->m_link);
			return true;
		}
		int bodyUniqueId = colObj->getUserIndex2();
		if (bodyUniqueId >= 0)
		{
			m_bodyUniqueIds.push_back(bodyUniqueId);
			//it is not a multibody, so use -1 otherwise
			m_links.push_back(-1);
		}
		return true;
	}
};

struct MyOverlapFilterCallback : public btOverlapFilterCallback
{
	int m_filterMode;
	b3PluginManager* m_pluginManager;

	MyOverlapFilterCallback(b3PluginManager* pluginManager)
		: m_filterMode(B3_FILTER_GROUPAMASKB_AND_GROUPBMASKA),
		  m_pluginManager(pluginManager)
	{
	}

	virtual ~MyOverlapFilterCallback()
	{
	}
	// return true when pairs need collision
	virtual bool needBroadphaseCollision(btBroadphaseProxy* proxy0, btBroadphaseProxy* proxy1) const
	{
		b3PluginCollisionInterface* collisionInterface = m_pluginManager->getCollisionInterface();

		if (collisionInterface && collisionInterface->getNumRules())
		{
			int objectUniqueIdB = -1, linkIndexB = -1;
			btCollisionObject* colObjB = (btCollisionObject*)proxy1->m_clientObject;
			btMultiBodyLinkCollider* mblB = btMultiBodyLinkCollider::upcast(colObjB);
			if (mblB)
			{
				objectUniqueIdB = mblB->m_multiBody->getUserIndex2();
				linkIndexB = mblB->m_link;
			}
			else
			{
				objectUniqueIdB = colObjB->getUserIndex2();
				linkIndexB = -1;
			}
			int objectUniqueIdA = -1, linkIndexA = -1;
			btCollisionObject* colObjA = (btCollisionObject*)proxy0->m_clientObject;
			btMultiBodyLinkCollider* mblA = btMultiBodyLinkCollider::upcast(colObjA);
			if (mblA)
			{
				objectUniqueIdA = mblA->m_multiBody->getUserIndex2();
				linkIndexA = mblA->m_link;
			}
			else
			{
				objectUniqueIdA = colObjA->getUserIndex2();
				linkIndexA = -1;
			}
			int collisionFilterGroupA = proxy0->m_collisionFilterGroup;
			int collisionFilterMaskA = proxy0->m_collisionFilterMask;
			int collisionFilterGroupB = proxy1->m_collisionFilterGroup;
			int collisionFilterMaskB = proxy1->m_collisionFilterMask;

			return collisionInterface->needsBroadphaseCollision(objectUniqueIdA, linkIndexA,
																collisionFilterGroupA, collisionFilterMaskA,
																objectUniqueIdB, linkIndexB, collisionFilterGroupB, collisionFilterMaskB, m_filterMode);
		}
		else
		{
			if (m_filterMode == B3_FILTER_GROUPAMASKB_AND_GROUPBMASKA)
			{
				bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0;
				collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask);
				return collides;
			}

			if (m_filterMode == B3_FILTER_GROUPAMASKB_OR_GROUPBMASKA)
			{
				bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0;
				collides = collides || (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask);
				return collides;
			}
			return false;
		}
	}
};

struct InternalStateLogger
{
	int m_loggingUniqueId;
	int m_loggingType;

	InternalStateLogger()
		: m_loggingUniqueId(0),
		  m_loggingType(0)
	{
	}
	virtual ~InternalStateLogger() {}

	virtual void stop() = 0;
	virtual void logState(btScalar timeStep) = 0;
};

struct VideoMP4Loggger : public InternalStateLogger
{
	struct GUIHelperInterface* m_guiHelper;
	std::string m_fileName;
	VideoMP4Loggger(int loggerUid, const char* fileName, GUIHelperInterface* guiHelper)
		: m_guiHelper(guiHelper)
	{
		m_fileName = fileName;
		m_loggingUniqueId = loggerUid;
		m_loggingType = STATE_LOGGING_VIDEO_MP4;
		m_guiHelper->dumpFramesToVideo(fileName);
	}

	virtual void stop()
	{
		m_guiHelper->dumpFramesToVideo(0);
	}
	virtual void logState(btScalar timeStep)
	{
		//dumping video frames happens in another thread
		//we could add some overlay of timestamp here, if needed/wanted
	}
};

struct MinitaurStateLogger : public InternalStateLogger
{
	int m_loggingTimeStamp;
	std::string m_fileName;
	int m_minitaurBodyUniqueId;
	FILE* m_logFileHandle;

	std::string m_structTypes;
	btMultiBody* m_minitaurMultiBody;
	btAlignedObjectArray<int> m_motorIdList;

	MinitaurStateLogger(int loggingUniqueId, const std::string& fileName, btMultiBody* minitaurMultiBody, btAlignedObjectArray<int>& motorIdList)
		: m_loggingTimeStamp(0),
		  m_logFileHandle(0),
		  m_minitaurMultiBody(minitaurMultiBody)
	{
		m_loggingUniqueId = loggingUniqueId;
		m_loggingType = STATE_LOGGING_MINITAUR;
		m_motorIdList.resize(motorIdList.size());
		for (int m = 0; m < motorIdList.size(); m++)
		{
			m_motorIdList[m] = motorIdList[m];
		}

		btAlignedObjectArray<std::string> structNames;
		//'t', 'r', 'p', 'y', 'q0', 'q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'u0', 'u1', 'u2', 'u3', 'u4', 'u5', 'u6', 'u7', 'xd', 'mo'
		structNames.push_back("t");
		structNames.push_back("r");
		structNames.push_back("p");
		structNames.push_back("y");

		structNames.push_back("q0");
		structNames.push_back("q1");
		structNames.push_back("q2");
		structNames.push_back("q3");
		structNames.push_back("q4");
		structNames.push_back("q5");
		structNames.push_back("q6");
		structNames.push_back("q7");

		structNames.push_back("u0");
		structNames.push_back("u1");
		structNames.push_back("u2");
		structNames.push_back("u3");
		structNames.push_back("u4");
		structNames.push_back("u5");
		structNames.push_back("u6");
		structNames.push_back("u7");

		structNames.push_back("dx");
		structNames.push_back("mo");

		m_structTypes = "IffffffffffffffffffffB";
		const char* fileNameC = fileName.c_str();

		m_logFileHandle = createMinitaurLogFile(fileNameC, structNames, m_structTypes);
	}
	virtual void stop()
	{
		if (m_logFileHandle)
		{
			closeMinitaurLogFile(m_logFileHandle);
			m_logFileHandle = 0;
		}
	}

	virtual void logState(btScalar timeStep)
	{
		if (m_logFileHandle)
		{
			//btVector3 pos = m_minitaurMultiBody->getBasePos();

			MinitaurLogRecord logData;
			//'t', 'r', 'p', 'y', 'q0', 'q1', 'q2', 'q3', 'q4', 'q5', 'q6', 'q7', 'u0', 'u1', 'u2', 'u3', 'u4', 'u5', 'u6', 'u7', 'xd', 'mo'
			btScalar motorDir[8] = {1, 1, 1, 1, 1, 1, 1, 1};

			btQuaternion orn = m_minitaurMultiBody->getBaseWorldTransform().getRotation();
			btMatrix3x3 mat(orn);
			btScalar roll = 0;
			btScalar pitch = 0;
			btScalar yaw = 0;

			mat.getEulerZYX(yaw, pitch, roll);

			logData.m_values.push_back(m_loggingTimeStamp);
			logData.m_values.push_back((float)roll);
			logData.m_values.push_back((float)pitch);
			logData.m_values.push_back((float)yaw);

			for (int i = 0; i < 8; i++)
			{
				float jointAngle = (float)motorDir[i] * m_minitaurMultiBody->getJointPos(m_motorIdList[i]);
				logData.m_values.push_back(jointAngle);
			}
			for (int i = 0; i < 8; i++)
			{
				btMultiBodyJointMotor* motor = (btMultiBodyJointMotor*)m_minitaurMultiBody->getLink(m_motorIdList[i]).m_userPtr;

				if (motor && timeStep > btScalar(0))
				{
					btScalar force = motor->getAppliedImpulse(0) / timeStep;
					logData.m_values.push_back((float)force);
				}
			}
			//x is forward component, estimated speed forward
			float xd_speed = m_minitaurMultiBody->getBaseVel()[0];
			logData.m_values.push_back(xd_speed);
			char mode = 6;
			logData.m_values.push_back(mode);

			//at the moment, appendMinitaurLogData will directly write to disk (potential delay)
			//better to fill a huge memory buffer and once in a while write it to disk
			appendMinitaurLogData(m_logFileHandle, m_structTypes, logData);

			fflush(m_logFileHandle);

			m_loggingTimeStamp++;
		}
	}
};

struct b3VRControllerEvents
{
	b3VRControllerEvent m_vrEvents[MAX_VR_CONTROLLERS];

	b3VRControllerEvents()
	{
		init();
	}

	virtual ~b3VRControllerEvents()
	{
	}

	void init()
	{
		for (int i = 0; i < MAX_VR_CONTROLLERS; i++)
		{
			m_vrEvents[i].m_deviceType = 0;
			m_vrEvents[i].m_numButtonEvents = 0;
			m_vrEvents[i].m_numMoveEvents = 0;
			for (int b = 0; b < MAX_VR_BUTTONS; b++)
			{
				m_vrEvents[i].m_buttons[b] = 0;
			}
		}
	}

	void addNewVREvents(const struct b3VRControllerEvent* vrEvents, int numVREvents)
	{
		//update m_vrEvents
		for (int i = 0; i < numVREvents; i++)
		{
			int controlledId = vrEvents[i].m_controllerId;
			if (vrEvents[i].m_numMoveEvents)
			{
				m_vrEvents[controlledId].m_analogAxis = vrEvents[i].m_analogAxis;
				for (int a = 0; a < 10; a++)
				{
					m_vrEvents[controlledId].m_auxAnalogAxis[a] = vrEvents[i].m_auxAnalogAxis[a];
				}
			}
			else
			{
				m_vrEvents[controlledId].m_analogAxis = 0;
				for (int a = 0; a < 10; a++)
				{
					m_vrEvents[controlledId].m_auxAnalogAxis[a] = 0;
				}
			}
			if (vrEvents[i].m_numMoveEvents + vrEvents[i].m_numButtonEvents)
			{
				m_vrEvents[controlledId].m_controllerId = vrEvents[i].m_controllerId;
				m_vrEvents[controlledId].m_deviceType = vrEvents[i].m_deviceType;

				m_vrEvents[controlledId].m_pos[0] = vrEvents[i].m_pos[0];
				m_vrEvents[controlledId].m_pos[1] = vrEvents[i].m_pos[1];
				m_vrEvents[controlledId].m_pos[2] = vrEvents[i].m_pos[2];
				m_vrEvents[controlledId].m_orn[0] = vrEvents[i].m_orn[0];
				m_vrEvents[controlledId].m_orn[1] = vrEvents[i].m_orn[1];
				m_vrEvents[controlledId].m_orn[2] = vrEvents[i].m_orn[2];
				m_vrEvents[controlledId].m_orn[3] = vrEvents[i].m_orn[3];
			}

			m_vrEvents[controlledId].m_numButtonEvents += vrEvents[i].m_numButtonEvents;
			m_vrEvents[controlledId].m_numMoveEvents += vrEvents[i].m_numMoveEvents;
			for (int b = 0; b < MAX_VR_BUTTONS; b++)
			{
				m_vrEvents[controlledId].m_buttons[b] |= vrEvents[i].m_buttons[b];
				if (vrEvents[i].m_buttons[b] & eButtonIsDown)
				{
					m_vrEvents[controlledId].m_buttons[b] |= eButtonIsDown;
				}
				else
				{
					m_vrEvents[controlledId].m_buttons[b] &= ~eButtonIsDown;
				}
			}
		}
	};
};

struct VRControllerStateLogger : public InternalStateLogger
{
	b3VRControllerEvents m_vrEvents;
	int m_loggingTimeStamp;
	int m_deviceTypeFilter;
	std::string m_fileName;
	FILE* m_logFileHandle;
	std::string m_structTypes;

	VRControllerStateLogger(int loggingUniqueId, int deviceTypeFilter, const std::string& fileName)
		: m_loggingTimeStamp(0),
		  m_deviceTypeFilter(deviceTypeFilter),
		  m_fileName(fileName),
		  m_logFileHandle(0)
	{
		m_loggingUniqueId = loggingUniqueId;
		m_loggingType = STATE_LOGGING_VR_CONTROLLERS;

		btAlignedObjectArray<std::string> structNames;
		structNames.push_back("stepCount");
		structNames.push_back("timeStamp");
		structNames.push_back("controllerId");
		structNames.push_back("numMoveEvents");
		structNames.push_back("m_numButtonEvents");
		structNames.push_back("posX");
		structNames.push_back("posY");
		structNames.push_back("posZ");
		structNames.push_back("oriX");
		structNames.push_back("oriY");
		structNames.push_back("oriZ");
		structNames.push_back("oriW");
		structNames.push_back("analogAxis");
		structNames.push_back("buttons0");
		structNames.push_back("buttons1");
		structNames.push_back("buttons2");
		structNames.push_back("buttons3");
		structNames.push_back("buttons4");
		structNames.push_back("buttons5");
		structNames.push_back("buttons6");
		structNames.push_back("deviceType");
		m_structTypes = "IfIIIffffffffIIIIIIII";

		const char* fileNameC = fileName.c_str();
		m_logFileHandle = createMinitaurLogFile(fileNameC, structNames, m_structTypes);
	}
	virtual void stop()
	{
		if (m_logFileHandle)
		{
			closeMinitaurLogFile(m_logFileHandle);
			m_logFileHandle = 0;
		}
	}
	virtual void logState(btScalar timeStep)
	{
		if (m_logFileHandle)
		{
			int stepCount = m_loggingTimeStamp;
			float timeStamp = m_loggingTimeStamp * timeStep;

			for (int i = 0; i < MAX_VR_CONTROLLERS; i++)
			{
				b3VRControllerEvent& event = m_vrEvents.m_vrEvents[i];
				if (m_deviceTypeFilter & event.m_deviceType)
				{
					if (event.m_numButtonEvents + event.m_numMoveEvents)
					{
						MinitaurLogRecord logData;

						//serverStatusOut.m_sendVREvents.m_controllerEvents[serverStatusOut.m_sendVREvents.m_numVRControllerEvents++] = event;
						//log the event
						logData.m_values.push_back(stepCount);
						logData.m_values.push_back(timeStamp);
						logData.m_values.push_back(event.m_controllerId);
						logData.m_values.push_back(event.m_numMoveEvents);
						logData.m_values.push_back(event.m_numButtonEvents);
						logData.m_values.push_back(event.m_pos[0]);
						logData.m_values.push_back(event.m_pos[1]);
						logData.m_values.push_back(event.m_pos[2]);
						logData.m_values.push_back(event.m_orn[0]);
						logData.m_values.push_back(event.m_orn[1]);
						logData.m_values.push_back(event.m_orn[2]);
						logData.m_values.push_back(event.m_orn[3]);
						logData.m_values.push_back(event.m_analogAxis);
						int packedButtons[7] = {0, 0, 0, 0, 0, 0, 0};

						int packedButtonIndex = 0;
						int packedButtonShift = 0;
						//encode the 64 buttons into 7 int (3 bits each), each int stores 10 buttons
						for (int b = 0; b < MAX_VR_BUTTONS; b++)
						{
							int buttonMask = event.m_buttons[b];
							buttonMask = buttonMask << (packedButtonShift * 3);
							packedButtons[packedButtonIndex] |= buttonMask;
							packedButtonShift++;

							if (packedButtonShift >= 10)
							{
								packedButtonShift = 0;
								packedButtonIndex++;
								if (packedButtonIndex >= 7)
								{
									btAssert(0);
									break;
								}
							}
						}

						for (int b = 0; b < 7; b++)
						{
							logData.m_values.push_back(packedButtons[b]);
						}
						logData.m_values.push_back(event.m_deviceType);
						appendMinitaurLogData(m_logFileHandle, m_structTypes, logData);

						event.m_numButtonEvents = 0;
						event.m_numMoveEvents = 0;
						for (int b = 0; b < MAX_VR_BUTTONS; b++)
						{
							event.m_buttons[b] = 0;
						}
					}
				}
			}

			fflush(m_logFileHandle);
			m_loggingTimeStamp++;
		}
	}
};

struct GenericRobotStateLogger : public InternalStateLogger
{
	float m_loggingTimeStamp;
	std::string m_fileName;
	FILE* m_logFileHandle;
	std::string m_structTypes;
	const btMultiBodyDynamicsWorld* m_dynamicsWorld;
	btAlignedObjectArray<int> m_bodyIdList;
	bool m_filterObjectUniqueId;
	int m_maxLogDof;
	int m_logFlags;

	GenericRobotStateLogger(int loggingUniqueId, const std::string& fileName, const btMultiBodyDynamicsWorld* dynamicsWorld, int maxLogDof, int logFlags)
		: m_loggingTimeStamp(0),
		  m_logFileHandle(0),
		  m_dynamicsWorld(dynamicsWorld),
		  m_filterObjectUniqueId(false),
		  m_maxLogDof(maxLogDof),
		  m_logFlags(logFlags)
	{
		m_loggingUniqueId = loggingUniqueId;
		m_loggingType = STATE_LOGGING_GENERIC_ROBOT;

		btAlignedObjectArray<std::string> structNames;
		structNames.push_back("stepCount");
		structNames.push_back("timeStamp");
		structNames.push_back("objectId");
		structNames.push_back("posX");
		structNames.push_back("posY");
		structNames.push_back("posZ");
		structNames.push_back("oriX");
		structNames.push_back("oriY");
		structNames.push_back("oriZ");
		structNames.push_back("oriW");
		structNames.push_back("velX");
		structNames.push_back("velY");
		structNames.push_back("velZ");
		structNames.push_back("omegaX");
		structNames.push_back("omegaY");
		structNames.push_back("omegaZ");
		structNames.push_back("qNum");

		m_structTypes = "IfifffffffffffffI";

		for (int i = 0; i < m_maxLogDof; i++)
		{
			m_structTypes.append("f");
			char jointName[256];
			sprintf(jointName, "q%d", i);
			structNames.push_back(jointName);
		}

		for (int i = 0; i < m_maxLogDof; i++)
		{
			m_structTypes.append("f");
			char jointName[256];
			sprintf(jointName, "u%d", i);
			structNames.push_back(jointName);
		}

		if (m_logFlags & STATE_LOG_JOINT_TORQUES)
		{
			for (int i = 0; i < m_maxLogDof; i++)
			{
				m_structTypes.append("f");
				char jointName[256];
				sprintf(jointName, "t%d", i);
				structNames.push_back(jointName);
			}
		}

		const char* fileNameC = fileName.c_str();

		m_logFileHandle = createMinitaurLogFile(fileNameC, structNames, m_structTypes);
	}
	virtual void stop()
	{
		if (m_logFileHandle)
		{
			closeMinitaurLogFile(m_logFileHandle);
			m_logFileHandle = 0;
		}
	}

	virtual void logState(btScalar timeStep)
	{
		if (m_logFileHandle)
		{
			for (int i = 0; i < m_dynamicsWorld->getNumMultibodies(); i++)
			{
				const btMultiBody* mb = m_dynamicsWorld->getMultiBody(i);
				int objectUniqueId = mb->getUserIndex2();
				if (m_filterObjectUniqueId && m_bodyIdList.findLinearSearch2(objectUniqueId) < 0)
				{
					continue;
				}

				MinitaurLogRecord logData;
				int stepCount = m_loggingTimeStamp;
				float timeStamp = m_loggingTimeStamp * m_dynamicsWorld->getSolverInfo().m_timeStep;
				logData.m_values.push_back(stepCount);
				logData.m_values.push_back(timeStamp);

				btVector3 pos = mb->getBasePos();
				btQuaternion ori = mb->getWorldToBaseRot().inverse();
				btVector3 vel = mb->getBaseVel();
				btVector3 omega = mb->getBaseOmega();

				float posX = pos[0];
				float posY = pos[1];
				float posZ = pos[2];
				float oriX = ori.x();
				float oriY = ori.y();
				float oriZ = ori.z();
				float oriW = ori.w();
				float velX = vel[0];
				float velY = vel[1];
				float velZ = vel[2];
				float omegaX = omega[0];
				float omegaY = omega[1];
				float omegaZ = omega[2];

				logData.m_values.push_back(objectUniqueId);
				logData.m_values.push_back(posX);
				logData.m_values.push_back(posY);
				logData.m_values.push_back(posZ);
				logData.m_values.push_back(oriX);
				logData.m_values.push_back(oriY);
				logData.m_values.push_back(oriZ);
				logData.m_values.push_back(oriW);
				logData.m_values.push_back(velX);
				logData.m_values.push_back(velY);
				logData.m_values.push_back(velZ);
				logData.m_values.push_back(omegaX);
				logData.m_values.push_back(omegaY);
				logData.m_values.push_back(omegaZ);

				int numDofs = mb->getNumDofs();
				logData.m_values.push_back(numDofs);
				int numJoints = mb->getNumLinks();

				for (int j = 0; j < numJoints; ++j)
				{
					if (mb->getLink(j).m_jointType == 0 || mb->getLink(j).m_jointType == 1)
					{
						float q = mb->getJointPos(j);
						logData.m_values.push_back(q);
					}
				}
				for (int j = numDofs; j < m_maxLogDof; ++j)
				{
					float q = 0.0;
					logData.m_values.push_back(q);
				}

				for (int j = 0; j < numJoints; ++j)
				{
					if (mb->getLink(j).m_jointType == 0 || mb->getLink(j).m_jointType == 1)
					{
						float v = mb->getJointVel(j);
						logData.m_values.push_back(v);
					}
				}
				for (int j = numDofs; j < m_maxLogDof; ++j)
				{
					float v = 0.0;
					logData.m_values.push_back(v);
				}

				if (m_logFlags & STATE_LOG_JOINT_TORQUES)
				{
					for (int j = 0; j < numJoints; ++j)
					{
						if (mb->getLink(j).m_jointType == 0 || mb->getLink(j).m_jointType == 1)
						{
							float jointTorque = 0;
							if (m_logFlags & STATE_LOG_JOINT_MOTOR_TORQUES)
							{
								btMultiBodyJointMotor* motor = (btMultiBodyJointMotor*)mb->getLink(j).m_userPtr;
								if (motor)
								{
									jointTorque += motor->getAppliedImpulse(0) / timeStep;
								}
							}
							if (m_logFlags & STATE_LOG_JOINT_USER_TORQUES)
							{
								if (mb->getLink(j).m_jointType == 0 || mb->getLink(j).m_jointType == 1)
								{
									jointTorque += mb->getJointTorque(j);  //these are the 'user' applied external torques
								}
							}
							logData.m_values.push_back(jointTorque);
						}
					}
					for (int j = numDofs; j < m_maxLogDof; ++j)
					{
						float u = 0.0;
						logData.m_values.push_back(u);
					}
				}

				//at the moment, appendMinitaurLogData will directly write to disk (potential delay)
				//better to fill a huge memory buffer and once in a while write it to disk
				appendMinitaurLogData(m_logFileHandle, m_structTypes, logData);
				fflush(m_logFileHandle);
			}

			m_loggingTimeStamp++;
		}
	}
};
struct ContactPointsStateLogger : public InternalStateLogger
{
	int m_loggingTimeStamp;

	std::string m_fileName;
	FILE* m_logFileHandle;
	std::string m_structTypes;
	btMultiBodyDynamicsWorld* m_dynamicsWorld;
	bool m_filterLinkA;
	bool m_filterLinkB;
	int m_linkIndexA;
	int m_linkIndexB;
	int m_bodyUniqueIdA;
	int m_bodyUniqueIdB;

	ContactPointsStateLogger(int loggingUniqueId, const std::string& fileName, btMultiBodyDynamicsWorld* dynamicsWorld)
		: m_loggingTimeStamp(0),
		  m_fileName(fileName),
		  m_logFileHandle(0),
		  m_dynamicsWorld(dynamicsWorld),
		  m_filterLinkA(false),
		  m_filterLinkB(false),
		  m_linkIndexA(-2),
		  m_linkIndexB(-2),
		  m_bodyUniqueIdA(-1),
		  m_bodyUniqueIdB(-1)
	{
		m_loggingUniqueId = loggingUniqueId;
		m_loggingType = STATE_LOGGING_CONTACT_POINTS;

		btAlignedObjectArray<std::string> structNames;
		structNames.push_back("stepCount");
		structNames.push_back("timeStamp");
		structNames.push_back("contactFlag");
		structNames.push_back("bodyUniqueIdA");
		structNames.push_back("bodyUniqueIdB");
		structNames.push_back("linkIndexA");
		structNames.push_back("linkIndexB");
		structNames.push_back("positionOnAX");
		structNames.push_back("positionOnAY");
		structNames.push_back("positionOnAZ");
		structNames.push_back("positionOnBX");
		structNames.push_back("positionOnBY");
		structNames.push_back("positionOnBZ");
		structNames.push_back("contactNormalOnBX");
		structNames.push_back("contactNormalOnBY");
		structNames.push_back("contactNormalOnBZ");
		structNames.push_back("contactDistance");
		structNames.push_back("normalForce");
		m_structTypes = "IfIiiiifffffffffff";

		const char* fileNameC = fileName.c_str();
		m_logFileHandle = createMinitaurLogFile(fileNameC, structNames, m_structTypes);
	}
	virtual void stop()
	{
		if (m_logFileHandle)
		{
			closeMinitaurLogFile(m_logFileHandle);
			m_logFileHandle = 0;
		}
	}
	virtual void logState(btScalar timeStep)
	{
		if (m_logFileHandle)
		{
			int numContactManifolds = m_dynamicsWorld->getDispatcher()->getNumManifolds();
			for (int i = 0; i < numContactManifolds; i++)
			{
				const btPersistentManifold* manifold = m_dynamicsWorld->getDispatcher()->getInternalManifoldPointer()[i];
				int linkIndexA = -1;
				int linkIndexB = -1;

				int objectIndexB = -1;

				const btRigidBody* bodyB = btRigidBody::upcast(manifold->getBody1());
				if (bodyB)
				{
					objectIndexB = bodyB->getUserIndex2();
				}
				const btMultiBodyLinkCollider* mblB = btMultiBodyLinkCollider::upcast(manifold->getBody1());
				if (mblB && mblB->m_multiBody)
				{
					linkIndexB = mblB->m_link;
					objectIndexB = mblB->m_multiBody->getUserIndex2();
					if (m_filterLinkB && (m_linkIndexB != linkIndexB))
					{
						continue;
					}
				}

				int objectIndexA = -1;
				const btRigidBody* bodyA = btRigidBody::upcast(manifold->getBody0());
				if (bodyA)
				{
					objectIndexA = bodyA->getUserIndex2();
				}
				const btMultiBodyLinkCollider* mblA = btMultiBodyLinkCollider::upcast(manifold->getBody0());
				if (mblA && mblA->m_multiBody)
				{
					linkIndexA = mblA->m_link;
					objectIndexA = mblA->m_multiBody->getUserIndex2();
					if (m_filterLinkA && (m_linkIndexA != linkIndexA))
					{
						continue;
					}
				}

				btAssert(bodyA || mblA);

				//apply the filter, if the user provides it
				if (m_bodyUniqueIdA >= 0)
				{
					if ((m_bodyUniqueIdA != objectIndexA) &&
						(m_bodyUniqueIdA != objectIndexB))
						continue;
				}

				//apply the second object filter, if the user provides it
				if (m_bodyUniqueIdB >= 0)
				{
					if ((m_bodyUniqueIdB != objectIndexA) &&
						(m_bodyUniqueIdB != objectIndexB))
						continue;
				}

				for (int p = 0; p < manifold->getNumContacts(); p++)
				{
					MinitaurLogRecord logData;
					int stepCount = m_loggingTimeStamp;
					float timeStamp = m_loggingTimeStamp * timeStep;
					logData.m_values.push_back(stepCount);
					logData.m_values.push_back(timeStamp);

					const btManifoldPoint& srcPt = manifold->getContactPoint(p);

					logData.m_values.push_back(0);  // reserved contact flag
					logData.m_values.push_back(objectIndexA);
					logData.m_values.push_back(objectIndexB);
					logData.m_values.push_back(linkIndexA);
					logData.m_values.push_back(linkIndexB);
					logData.m_values.push_back((float)(srcPt.getPositionWorldOnA()[0]));
					logData.m_values.push_back((float)(srcPt.getPositionWorldOnA()[1]));
					logData.m_values.push_back((float)(srcPt.getPositionWorldOnA()[2]));
					logData.m_values.push_back((float)(srcPt.getPositionWorldOnB()[0]));
					logData.m_values.push_back((float)(srcPt.getPositionWorldOnB()[1]));
					logData.m_values.push_back((float)(srcPt.getPositionWorldOnB()[2]));
					logData.m_values.push_back((float)(srcPt.m_normalWorldOnB[0]));
					logData.m_values.push_back((float)(srcPt.m_normalWorldOnB[1]));
					logData.m_values.push_back((float)(srcPt.m_normalWorldOnB[2]));
					logData.m_values.push_back((float)(srcPt.getDistance()));
					logData.m_values.push_back((float)(srcPt.getAppliedImpulse() / timeStep));

					appendMinitaurLogData(m_logFileHandle, m_structTypes, logData);
					fflush(m_logFileHandle);
				}
			}
			m_loggingTimeStamp++;
		}
	}
};

struct SaveStateData
{
	bParse::btBulletFile* m_bulletFile;
	btSerializer* m_serializer;
};

struct PhysicsServerCommandProcessorInternalData
{
	///handle management
	b3ResizablePool<InternalTextureHandle> m_textureHandles;
	b3ResizablePool<InternalBodyHandle> m_bodyHandles;
	b3ResizablePool<InternalCollisionShapeHandle> m_userCollisionShapeHandles;
	b3ResizablePool<InternalVisualShapeHandle> m_userVisualShapeHandles;
	b3ResizablePool<b3PoolBodyHandle<SharedMemoryUserData> > m_userDataHandles;
	btHashMap<SharedMemoryUserDataHashKey, int> m_userDataHandleLookup;

	b3PluginManager m_pluginManager;

	bool m_useRealTimeSimulation;

	b3VRControllerEvents m_vrControllerEvents;

	btAlignedObjectArray<SaveStateData> m_savedStates;

	btAlignedObjectArray<b3KeyboardEvent> m_keyboardEvents;
	btAlignedObjectArray<b3MouseEvent> m_mouseEvents;

	CommandLogger* m_commandLogger;
	int m_commandLoggingUid;

	CommandLogPlayback* m_logPlayback;
	int m_logPlaybackUid;

	btScalar m_physicsDeltaTime;
	btScalar m_numSimulationSubSteps;
	btAlignedObjectArray<btMultiBodyJointFeedback*> m_multiBodyJointFeedbacks;
	b3HashMap<btHashPtr, btInverseDynamics::MultiBodyTree*> m_inverseDynamicsBodies;
	b3HashMap<btHashPtr, IKTrajectoryHelper*> m_inverseKinematicsHelpers;

	int m_userConstraintUIDGenerator;
	b3HashMap<btHashInt, InteralUserConstraintData> m_userConstraints;

	b3AlignedObjectArray<SaveWorldObjectData> m_saveWorldBodyData;

	btAlignedObjectArray<btMultiBodyWorldImporter*> m_worldImporters;

	btAlignedObjectArray<std::string*> m_strings;

	btAlignedObjectArray<btCollisionShape*> m_collisionShapes;
	btAlignedObjectArray<int> m_allocatedTextures;
	btHashMap<btHashPtr, UrdfCollision> m_bulletCollisionShape2UrdfCollision;
	btAlignedObjectArray<btStridingMeshInterface*> m_meshInterfaces;

	MyOverlapFilterCallback* m_broadphaseCollisionFilterCallback;
	btHashedOverlappingPairCache* m_pairCache;
	btBroadphaseInterface* m_broadphase;
	btCollisionDispatcher* m_dispatcher;
	btMultiBodyConstraintSolver* m_solver;
	btDefaultCollisionConfiguration* m_collisionConfiguration;

#ifndef SKIP_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD
	btSoftMultiBodyDynamicsWorld* m_dynamicsWorld;
	btSoftBodySolver* m_softbodySolver;
#else
	btMultiBodyDynamicsWorld* m_dynamicsWorld;
#endif

	int m_constraintSolverType;
	SharedMemoryDebugDrawer* m_remoteDebugDrawer;

	btAlignedObjectArray<b3ContactPointData> m_cachedContactPoints;
	MyBroadphaseCallback m_cachedOverlappingObjects;

	btAlignedObjectArray<int> m_sdfRecentLoadedBodies;

	btAlignedObjectArray<InternalStateLogger*> m_stateLoggers;
	int m_stateLoggersUniqueId;
	int m_profileTimingLoggingUid;
	std::string m_profileTimingFileName;

	struct GUIHelperInterface* m_guiHelper;
	int m_sharedMemoryKey;
	bool m_enableTinyRenderer;

	bool m_verboseOutput;

	//data for picking objects
	class btRigidBody* m_pickedBody;
	int m_savedActivationState;
	class btTypedConstraint* m_pickedConstraint;
	class btMultiBodyPoint2Point* m_pickingMultiBodyPoint2Point;
	btVector3 m_oldPickingPos;
	btVector3 m_hitPos;
	btScalar m_oldPickingDist;
	bool m_prevCanSleep;
	int m_pdControlPlugin;
	int m_collisionFilterPlugin;
	int m_grpcPlugin;

#ifdef B3_ENABLE_TINY_AUDIO
	b3SoundEngine m_soundEngine;
#endif

	b3HashMap<b3HashString, char*> m_profileEvents;
	b3HashMap<b3HashString, UrdfVisualShapeCache> m_cachedVUrdfisualShapes;

	b3ThreadPool* m_threadPool;
	btScalar m_defaultCollisionMargin;

	PhysicsServerCommandProcessorInternalData(PhysicsCommandProcessorInterface* proc)
		: m_pluginManager(proc),
		  m_useRealTimeSimulation(false),
		  m_commandLogger(0),
		  m_commandLoggingUid(-1),
		  m_logPlayback(0),
		  m_logPlaybackUid(-1),
		  m_physicsDeltaTime(1. / 240.),
		  m_numSimulationSubSteps(0),
		  m_userConstraintUIDGenerator(1),
		  m_broadphaseCollisionFilterCallback(0),
		  m_pairCache(0),
		  m_broadphase(0),
		  m_dispatcher(0),
		  m_solver(0),
		  m_collisionConfiguration(0),
		  m_dynamicsWorld(0),
		  m_constraintSolverType(-1),
		  m_remoteDebugDrawer(0),
		  m_stateLoggersUniqueId(0),
		  m_profileTimingLoggingUid(-1),
		  m_guiHelper(0),
		  m_sharedMemoryKey(SHARED_MEMORY_KEY),
		  m_enableTinyRenderer(true),
		  m_verboseOutput(false),
		  m_pickedBody(0),
		  m_pickedConstraint(0),
		  m_pickingMultiBodyPoint2Point(0),
		  m_pdControlPlugin(-1),
		  m_collisionFilterPlugin(-1),
		  m_grpcPlugin(-1),
		  m_threadPool(0),
		  m_defaultCollisionMargin(0.001)
	{
		{
			//register static plugins:
#ifdef STATIC_LINK_VR_PLUGIN
			m_pluginManager.registerStaticLinkedPlugin("vrSyncPlugin", initPlugin_vrSyncPlugin, exitPlugin_vrSyncPlugin, executePluginCommand_vrSyncPlugin, preTickPluginCallback_vrSyncPlugin, 0, 0, 0, 0);
#endif  //STATIC_LINK_VR_PLUGIN

#ifndef SKIP_STATIC_PD_CONTROL_PLUGIN
			{
				m_pdControlPlugin = m_pluginManager.registerStaticLinkedPlugin("pdControlPlugin", initPlugin_pdControlPlugin, exitPlugin_pdControlPlugin, executePluginCommand_pdControlPlugin, preTickPluginCallback_pdControlPlugin, 0, 0, 0, 0);
			}
#endif  //SKIP_STATIC_PD_CONTROL_PLUGIN

#ifndef SKIP_COLLISION_FILTER_PLUGIN
			{
				m_collisionFilterPlugin = m_pluginManager.registerStaticLinkedPlugin("collisionFilterPlugin", initPlugin_collisionFilterPlugin, exitPlugin_collisionFilterPlugin, executePluginCommand_collisionFilterPlugin, 0, 0, 0, 0, getCollisionInterface_collisionFilterPlugin);
				m_pluginManager.selectCollisionPlugin(m_collisionFilterPlugin);
			}
#endif

#ifdef ENABLE_STATIC_GRPC_PLUGIN
			{
				m_grpcPlugin = m_pluginManager.registerStaticLinkedPlugin("grpcPlugin", initPlugin_grpcPlugin, exitPlugin_grpcPlugin, executePluginCommand_grpcPlugin, 0, 0, 0, processClientCommands_grpcPlugin, 0);
			}
#endif  //ENABLE_STATIC_GRPC_PLUGIN

#ifdef STATIC_EGLRENDERER_PLUGIN
			{
				bool initPlugin = false;
				int renderPluginId = m_pluginManager.registerStaticLinkedPlugin("eglRendererPlugin", initPlugin_eglRendererPlugin, exitPlugin_eglRendererPlugin, executePluginCommand_eglRendererPlugin, 0, 0, getRenderInterface_eglRendererPlugin, 0, 0, initPlugin);
				m_pluginManager.selectPluginRenderer(renderPluginId);
			}
#endif  //STATIC_EGLRENDERER_PLUGIN

#ifndef SKIP_STATIC_TINYRENDERER_PLUGIN
			{
				int renderPluginId = m_pluginManager.registerStaticLinkedPlugin("tinyRendererPlugin", initPlugin_tinyRendererPlugin, exitPlugin_tinyRendererPlugin, executePluginCommand_tinyRendererPlugin, 0, 0, getRenderInterface_tinyRendererPlugin, 0, 0);
				m_pluginManager.selectPluginRenderer(renderPluginId);
			}
#endif
		}

		m_vrControllerEvents.init();

		m_bodyHandles.exitHandles();
		m_bodyHandles.initHandles();
		m_userCollisionShapeHandles.exitHandles();
		m_userCollisionShapeHandles.initHandles();

		m_userVisualShapeHandles.exitHandles();
		m_userVisualShapeHandles.initHandles();
	}

	btInverseDynamics::MultiBodyTree* findOrCreateTree(btMultiBody* multiBody)
	{
		btInverseDynamics::MultiBodyTree* tree = 0;

		btInverseDynamics::MultiBodyTree** treePtrPtr =
			m_inverseDynamicsBodies.find(multiBody);

		if (treePtrPtr)
		{
			tree = *treePtrPtr;
		}
		else
		{
			btInverseDynamics::btMultiBodyTreeCreator id_creator;
			if (-1 == id_creator.createFromBtMultiBody(multiBody, false))
			{
			}
			else
			{
				tree = btInverseDynamics::CreateMultiBodyTree(id_creator);
				m_inverseDynamicsBodies.insert(multiBody, tree);
			}
		}

		return tree;
	}
};

void PhysicsServerCommandProcessor::setGuiHelper(struct GUIHelperInterface* guiHelper)
{
	if (guiHelper)
	{
		guiHelper->createPhysicsDebugDrawer(m_data->m_dynamicsWorld);
	}
	else
	{
		//state loggers use guiHelper, so remove them before the guiHelper is deleted
		deleteStateLoggers();
		if (m_data->m_guiHelper && m_data->m_dynamicsWorld && m_data->m_dynamicsWorld->getDebugDrawer())
		{
			m_data->m_dynamicsWorld->setDebugDrawer(0);
		}
	}
	m_data->m_guiHelper = guiHelper;
}

PhysicsServerCommandProcessor::PhysicsServerCommandProcessor()
	: m_data(0)
{
	m_data = new PhysicsServerCommandProcessorInternalData(this);

	createEmptyDynamicsWorld();
}

PhysicsServerCommandProcessor::~PhysicsServerCommandProcessor()
{
	deleteDynamicsWorld();
	if (m_data->m_commandLogger)
	{
		delete m_data->m_commandLogger;
		m_data->m_commandLogger = 0;
	}
	for (int i = 0; i < m_data->m_profileEvents.size(); i++)
	{
		char* event = *m_data->m_profileEvents.getAtIndex(i);
		delete[] event;
	}
	if (m_data->m_threadPool)
		delete m_data->m_threadPool;

	delete m_data;
}

void preTickCallback(btDynamicsWorld* world, btScalar timeStep)
{
	PhysicsServerCommandProcessor* proc = (PhysicsServerCommandProcessor*)world->getWorldUserInfo();

	proc->tickPlugins(timeStep, true);
}

void logCallback(btDynamicsWorld* world, btScalar timeStep)
{
	//handle the logging and playing sounds
	PhysicsServerCommandProcessor* proc = (PhysicsServerCommandProcessor*)world->getWorldUserInfo();
	proc->processCollisionForces(timeStep);
	proc->logObjectStates(timeStep);

	proc->tickPlugins(timeStep, false);
}

bool MyContactAddedCallback(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1)
{
	btAdjustInternalEdgeContacts(cp, colObj1Wrap, colObj0Wrap, partId1, index1);
	return true;
}

bool MyContactDestroyedCallback(void* userPersistentData)
{
	//printf("destroyed\n");
	return false;
}

bool MyContactProcessedCallback(btManifoldPoint& cp, void* body0, void* body1)
{
	//printf("processed\n");
	return false;
}
void MyContactStartedCallback(btPersistentManifold* const& manifold)
{
	//printf("started\n");
}
void MyContactEndedCallback(btPersistentManifold* const& manifold)
{
	//	printf("ended\n");
}

void PhysicsServerCommandProcessor::processCollisionForces(btScalar timeStep)
{
#ifdef B3_ENABLE_TINY_AUDIO
	//this is experimental at the moment: impulse thresholds, sound parameters will be exposed in C-API/pybullet.
	//audio will go into a wav file, as well as real-time output to speakers/headphones using RtAudio/DAC.

	int numContactManifolds = m_data->m_dynamicsWorld->getDispatcher()->getNumManifolds();
	for (int i = 0; i < numContactManifolds; i++)
	{
		const btPersistentManifold* manifold = m_data->m_dynamicsWorld->getDispatcher()->getInternalManifoldPointer()[i];

		bool objHasSound[2];
		objHasSound[0] = (0 != (manifold->getBody0()->getCollisionFlags() & btCollisionObject::CF_HAS_COLLISION_SOUND_TRIGGER));
		objHasSound[1] = (0 != (manifold->getBody1()->getCollisionFlags() & btCollisionObject::CF_HAS_COLLISION_SOUND_TRIGGER));
		const btCollisionObject* colObjs[2] = {manifold->getBody0(), manifold->getBody1()};

		for (int ob = 0; ob < 2; ob++)
		{
			if (objHasSound[ob])
			{
				int uid0 = -1;
				int linkIndex = -2;

				const btMultiBodyLinkCollider* mblB = btMultiBodyLinkCollider::upcast(colObjs[ob]);
				if (mblB && mblB->m_multiBody)
				{
					linkIndex = mblB->m_link;
					uid0 = mblB->m_multiBody->getUserIndex2();
				}
				const btRigidBody* bodyB = btRigidBody::upcast(colObjs[ob]);
				if (bodyB)
				{
					uid0 = bodyB->getUserIndex2();
					linkIndex = -1;
				}

				if ((uid0 < 0) || (linkIndex < -1))
					continue;

				InternalBodyHandle* bodyHandle0 = m_data->m_bodyHandles.getHandle(uid0);
				SDFAudioSource* audioSrc = bodyHandle0->m_audioSources[linkIndex];
				if (audioSrc == 0)
					continue;

				for (int p = 0; p < manifold->getNumContacts(); p++)
				{
					double imp = manifold->getContactPoint(p).getAppliedImpulse();
					//printf ("manifold %d, contact %d, lifeTime:%d, appliedImpulse:%f\n",i,p, manifold->getContactPoint(p).getLifeTime(),imp);

					if (imp > audioSrc->m_collisionForceThreshold && manifold->getContactPoint(p).getLifeTime() == 1)
					{
						int soundSourceIndex = m_data->m_soundEngine.getAvailableSoundSource();
						if (soundSourceIndex >= 0)
						{
							b3SoundMessage msg;
							msg.m_attackRate = audioSrc->m_attackRate;
							msg.m_decayRate = audioSrc->m_decayRate;
							msg.m_sustainLevel = audioSrc->m_sustainLevel;
							msg.m_releaseRate = audioSrc->m_releaseRate;
							msg.m_amplitude = audioSrc->m_gain;
							msg.m_frequency = audioSrc->m_pitch;
							msg.m_type = B3_SOUND_SOURCE_WAV_FILE;
							msg.m_wavId = audioSrc->m_userIndex;
							msg.m_autoKeyOff = true;
							m_data->m_soundEngine.startSound(soundSourceIndex, msg);
						}
					}
				}
			}
		}
	}
#endif  //B3_ENABLE_TINY_AUDIO
}

void PhysicsServerCommandProcessor::processClientCommands()
{
	m_data->m_pluginManager.tickPlugins(0, B3_PROCESS_CLIENT_COMMANDS_TICK);
}

void PhysicsServerCommandProcessor::reportNotifications()
{
	m_data->m_pluginManager.reportNotifications();
}

void PhysicsServerCommandProcessor::tickPlugins(btScalar timeStep, bool isPreTick)
{
	b3PluginManagerTickMode tickMode = isPreTick ? B3_PRE_TICK_MODE : B3_POST_TICK_MODE;
	m_data->m_pluginManager.tickPlugins(timeStep, tickMode);
	if (!isPreTick)
	{
		//clear events after each postTick, so we don't receive events multiple ticks
		m_data->m_pluginManager.clearEvents();
	}
}

void PhysicsServerCommandProcessor::logObjectStates(btScalar timeStep)
{
	for (int i = 0; i < m_data->m_stateLoggers.size(); i++)
	{
		m_data->m_stateLoggers[i]->logState(timeStep);
	}
}

struct ProgrammaticUrdfInterface : public URDFImporterInterface
{
	int m_bodyUniqueId;

	const b3CreateMultiBodyArgs& m_createBodyArgs;
	mutable b3AlignedObjectArray<btCollisionShape*> m_allocatedCollisionShapes;
	PhysicsServerCommandProcessorInternalData* m_data;

	ProgrammaticUrdfInterface(const b3CreateMultiBodyArgs& bodyArgs, PhysicsServerCommandProcessorInternalData* data)
		: m_bodyUniqueId(-1),
		  m_createBodyArgs(bodyArgs),
		  m_data(data)
	{
	}

	virtual ~ProgrammaticUrdfInterface()
	{
	}

	virtual bool loadURDF(const char* fileName, bool forceFixedBase = false)
	{
		b3Assert(0);
		return false;
	}

	virtual const char* getPathPrefix()
	{
		return "";
	}

	///return >=0 for the root link index, -1 if there is no root link
	virtual int getRootLinkIndex() const
	{
		return m_createBodyArgs.m_baseLinkIndex;
	}

	///pure virtual interfaces, precondition is a valid linkIndex (you can assert/terminate if the linkIndex is out of range)
	virtual std::string getLinkName(int linkIndex) const
	{
		std::string linkName = "link";
		char numstr[21];  // enough to hold all numbers up to 64-bits
		sprintf(numstr, "%d", linkIndex);
		linkName = linkName + numstr;
		return linkName;
	}

	//various derived class in internal source code break with new pure virtual methods, so provide some default implementation
	virtual std::string getBodyName() const
	{
		return m_createBodyArgs.m_bodyName;
	}

	/// optional method to provide the link color. return true if the color is available and copied into colorRGBA, return false otherwise
	virtual bool getLinkColor(int linkIndex, btVector4& colorRGBA) const
	{
		b3Assert(0);
		return false;
	}

	virtual bool getLinkColor2(int linkIndex, struct UrdfMaterialColor& matCol) const
	{
		if (m_createBodyArgs.m_linkVisualShapeUniqueIds[linkIndex] >= 0)
		{
			const InternalVisualShapeHandle* visHandle = m_data->m_userVisualShapeHandles.getHandle(m_createBodyArgs.m_linkVisualShapeUniqueIds[linkIndex]);
			if (visHandle)
			{
				for (int i = 0; i < visHandle->m_visualShapes.size(); i++)
				{
					if (visHandle->m_visualShapes[i].m_geometry.m_hasLocalMaterial)
					{
						matCol = visHandle->m_visualShapes[i].m_geometry.m_localMaterial.m_matColor;
						return true;
					}
				}
			}
		}
		return false;
	}

	virtual int getCollisionGroupAndMask(int linkIndex, int& colGroup, int& colMask) const
	{
		return 0;
	}
	///this API will likely change, don't override it!
	virtual bool getLinkContactInfo(int linkIndex, URDFLinkContactInfo& contactInfo) const
	{
		return false;
	}

	virtual bool getLinkAudioSource(int linkIndex, SDFAudioSource& audioSource) const
	{
		b3Assert(0);
		return false;
	}

	virtual std::string getJointName(int linkIndex) const
	{
		std::string jointName = "joint";
		char numstr[21];  // enough to hold all numbers up to 64-bits
		sprintf(numstr, "%d", linkIndex);
		jointName = jointName + numstr;
		return jointName;
	}

	//fill mass and inertial data. If inertial data is missing, please initialize mass, inertia to sensitive values, and inertialFrame to identity.
	virtual void getMassAndInertia(int urdfLinkIndex, btScalar& mass, btVector3& localInertiaDiagonal, btTransform& inertialFrame) const
	{
		if (urdfLinkIndex >= 0 && urdfLinkIndex < m_createBodyArgs.m_numLinks)
		{
			mass = m_createBodyArgs.m_linkMasses[urdfLinkIndex];
			localInertiaDiagonal.setValue(
				m_createBodyArgs.m_linkInertias[urdfLinkIndex * 3 + 0],
				m_createBodyArgs.m_linkInertias[urdfLinkIndex * 3 + 1],
				m_createBodyArgs.m_linkInertias[urdfLinkIndex * 3 + 2]);
			inertialFrame.setOrigin(btVector3(
				m_createBodyArgs.m_linkInertialFramePositions[urdfLinkIndex * 3 + 0],
				m_createBodyArgs.m_linkInertialFramePositions[urdfLinkIndex * 3 + 1],
				m_createBodyArgs.m_linkInertialFramePositions[urdfLinkIndex * 3 + 2]));
			inertialFrame.setRotation(btQuaternion(
				m_createBodyArgs.m_linkInertialFrameOrientations[urdfLinkIndex * 4 + 0],
				m_createBodyArgs.m_linkInertialFrameOrientations[urdfLinkIndex * 4 + 1],
				m_createBodyArgs.m_linkInertialFrameOrientations[urdfLinkIndex * 4 + 2],
				m_createBodyArgs.m_linkInertialFrameOrientations[urdfLinkIndex * 4 + 3]));
		}
		else
		{
			mass = 0;
			localInertiaDiagonal.setValue(0, 0, 0);
			inertialFrame.setIdentity();
		}
	}

	///fill an array of child link indices for this link, btAlignedObjectArray behaves like a std::vector so just use push_back and resize(0) if needed
	virtual void getLinkChildIndices(int urdfLinkIndex, btAlignedObjectArray<int>& childLinkIndices) const
	{
		for (int i = 0; i < m_createBodyArgs.m_numLinks; i++)
		{
			if (m_createBodyArgs.m_linkParentIndices[i] == urdfLinkIndex)
			{
				childLinkIndices.push_back(i);
			}
		}
	}

	virtual bool getJointInfo(int urdfLinkIndex, btTransform& parent2joint, btTransform& linkTransformInWorld, btVector3& jointAxisInJointSpace, int& jointType, btScalar& jointLowerLimit, btScalar& jointUpperLimit, btScalar& jointDamping, btScalar& jointFriction) const
	{
		return false;
	};

	virtual bool getJointInfo2(int urdfLinkIndex, btTransform& parent2joint, btTransform& linkTransformInWorld, btVector3& jointAxisInJointSpace, int& jointType, btScalar& jointLowerLimit, btScalar& jointUpperLimit, btScalar& jointDamping, btScalar& jointFriction, btScalar& jointMaxForce, btScalar& jointMaxVelocity) const
	{
		bool isValid = false;

		int jointTypeOrg = m_createBodyArgs.m_linkJointTypes[urdfLinkIndex];

		switch (jointTypeOrg)
		{
			case eRevoluteType:
			{
				isValid = true;
				jointType = URDFRevoluteJoint;
				break;
			}
			case ePrismaticType:
			{
				isValid = true;
				jointType = URDFPrismaticJoint;
				break;
			}
			case eFixedType:
			{
				isValid = true;
				jointType = URDFFixedJoint;
				break;
			}
			//case	eSphericalType:
			//case	ePlanarType:
			//case	eFixedType:
			//case ePoint2PointType:
			//case eGearType:
			default:
			{
			}
		};

		if (isValid)
		{
			//backwards compatibility for custom file importers
			jointMaxForce = 0;
			jointMaxVelocity = 0;
			jointFriction = 0;
			jointDamping = 0;
			jointLowerLimit = 1;
			jointUpperLimit = -1;

			parent2joint.setOrigin(btVector3(
				m_createBodyArgs.m_linkPositions[urdfLinkIndex * 3 + 0],
				m_createBodyArgs.m_linkPositions[urdfLinkIndex * 3 + 1],
				m_createBodyArgs.m_linkPositions[urdfLinkIndex * 3 + 2]));
			parent2joint.setRotation(btQuaternion(
				m_createBodyArgs.m_linkOrientations[urdfLinkIndex * 4 + 0],
				m_createBodyArgs.m_linkOrientations[urdfLinkIndex * 4 + 1],
				m_createBodyArgs.m_linkOrientations[urdfLinkIndex * 4 + 2],
				m_createBodyArgs.m_linkOrientations[urdfLinkIndex * 4 + 3]));

			linkTransformInWorld.setIdentity();

			jointAxisInJointSpace.setValue(
				m_createBodyArgs.m_linkJointAxis[3 * urdfLinkIndex + 0],
				m_createBodyArgs.m_linkJointAxis[3 * urdfLinkIndex + 1],
				m_createBodyArgs.m_linkJointAxis[3 * urdfLinkIndex + 2]);
		}
		return isValid;
	};

	virtual bool getRootTransformInWorld(btTransform& rootTransformInWorld) const
	{
		int baseLinkIndex = m_createBodyArgs.m_baseLinkIndex;

		rootTransformInWorld.setOrigin(btVector3(
			m_createBodyArgs.m_linkPositions[baseLinkIndex * 3 + 0],
			m_createBodyArgs.m_linkPositions[baseLinkIndex * 3 + 1],
			m_createBodyArgs.m_linkPositions[baseLinkIndex * 3 + 2]));
		rootTransformInWorld.setRotation(btQuaternion(
			m_createBodyArgs.m_linkOrientations[baseLinkIndex * 4 + 0],
			m_createBodyArgs.m_linkOrientations[baseLinkIndex * 4 + 1],
			m_createBodyArgs.m_linkOrientations[baseLinkIndex * 4 + 2],
			m_createBodyArgs.m_linkOrientations[baseLinkIndex * 4 + 3]));
		return true;
	}
	virtual void setRootTransformInWorld(const btTransform& rootTransformInWorld)
	{
		b3Assert(0);
	}

	virtual int convertLinkVisualShapes(int linkIndex, const char* pathPrefix, const btTransform& localInertiaFrame) const
	{
		int graphicsIndex = -1;
		double globalScaling = 1.f;  //todo!
		int flags = 0;
		BulletURDFImporter u2b(m_data->m_guiHelper, m_data->m_pluginManager.getRenderInterface(), globalScaling, flags);
		u2b.setEnableTinyRenderer(m_data->m_enableTinyRenderer);

		btAlignedObjectArray<GLInstanceVertex> vertices;
		btAlignedObjectArray<int> indices;
		btTransform startTrans;
		startTrans.setIdentity();
		btAlignedObjectArray<BulletURDFTexture> textures;

		if (m_createBodyArgs.m_linkVisualShapeUniqueIds[linkIndex] >= 0)
		{
			InternalVisualShapeHandle* visHandle = m_data->m_userVisualShapeHandles.getHandle(m_createBodyArgs.m_linkVisualShapeUniqueIds[linkIndex]);
			if (visHandle)
			{
				if (visHandle->m_OpenGLGraphicsIndex >= 0)
				{
					//instancing. assume the inertial frame is identical
					graphicsIndex = visHandle->m_OpenGLGraphicsIndex;
				}
				else
				{
					for (int v = 0; v < visHandle->m_visualShapes.size(); v++)
					{
						u2b.convertURDFToVisualShapeInternal(&visHandle->m_visualShapes[v], pathPrefix, localInertiaFrame.inverse() * visHandle->m_visualShapes[v].m_linkLocalFrame, vertices, indices, textures);
					}

					if (vertices.size() && indices.size())
					{
						if (1)
						{
							int textureIndex = -1;
							if (textures.size())
							{
								textureIndex = m_data->m_guiHelper->registerTexture(textures[0].textureData1, textures[0].m_width, textures[0].m_height);
							}

							{
								B3_PROFILE("registerGraphicsShape");
								graphicsIndex = m_data->m_guiHelper->registerGraphicsShape(&vertices[0].xyzw[0], vertices.size(), &indices[0], indices.size(), B3_GL_TRIANGLES, textureIndex);
								visHandle->m_OpenGLGraphicsIndex = graphicsIndex;
							}
						}
					}
				}
			}
		}
		return graphicsIndex;
	}

	virtual void convertLinkVisualShapes2(int linkIndex, int urdfIndex, const char* pathPrefix, const btTransform& localInertiaFrame, class btCollisionObject* colObj, int bodyUniqueId) const
	{
		//if there is a visual, use it, otherwise convert collision shape back into UrdfCollision...

		UrdfModel model;  // = m_data->m_urdfParser.getModel();
		UrdfLink link;

		if (m_createBodyArgs.m_linkVisualShapeUniqueIds[urdfIndex] >= 0)
		{
			const InternalVisualShapeHandle* visHandle = m_data->m_userVisualShapeHandles.getHandle(m_createBodyArgs.m_linkVisualShapeUniqueIds[urdfIndex]);
			if (visHandle)
			{
				for (int i = 0; i < visHandle->m_visualShapes.size(); i++)
				{
					link.m_visualArray.push_back(visHandle->m_visualShapes[i]);
				}
			}
		}

		if (link.m_visualArray.size() == 0)
		{
			int colShapeUniqueId = m_createBodyArgs.m_linkCollisionShapeUniqueIds[urdfIndex];
			if (colShapeUniqueId >= 0)
			{
				InternalCollisionShapeHandle* handle = m_data->m_userCollisionShapeHandles.getHandle(colShapeUniqueId);
				if (handle)
				{
					for (int i = 0; i < handle->m_urdfCollisionObjects.size(); i++)
					{
						link.m_collisionArray.push_back(handle->m_urdfCollisionObjects[i]);
					}
				}
			}
		}
		//UrdfVisual vis;
		//link.m_visualArray.push_back(vis);
		//UrdfLink*const* linkPtr = model.m_links.getAtIndex(urdfIndex);
		if (m_data->m_pluginManager.getRenderInterface())
		{
			m_data->m_pluginManager.getRenderInterface()->convertVisualShapes(linkIndex, pathPrefix, localInertiaFrame, &link, &model, colObj->getBroadphaseHandle()->getUid(), bodyUniqueId);
		}
	}
	virtual void setBodyUniqueId(int bodyId)
	{
		m_bodyUniqueId = bodyId;
	}
	virtual int getBodyUniqueId() const
	{
		return m_bodyUniqueId;
	}

	//default implementation for backward compatibility
	virtual class btCompoundShape* convertLinkCollisionShapes(int linkIndex, const char* pathPrefix, const btTransform& localInertiaFrame) const
	{
		btCompoundShape* compound = new btCompoundShape();

		int colShapeUniqueId = m_createBodyArgs.m_linkCollisionShapeUniqueIds[linkIndex];
		if (colShapeUniqueId >= 0)
		{
			InternalCollisionShapeHandle* handle = m_data->m_userCollisionShapeHandles.getHandle(colShapeUniqueId);
			if (handle && handle->m_collisionShape)
			{
				handle->m_used++;
				if (handle->m_collisionShape->getShapeType() == COMPOUND_SHAPE_PROXYTYPE)
				{
					btCompoundShape* childCompound = (btCompoundShape*)handle->m_collisionShape;
					for (int c = 0; c < childCompound->getNumChildShapes(); c++)
					{
						btTransform childTrans = childCompound->getChildTransform(c);
						btCollisionShape* childShape = childCompound->getChildShape(c);
						btTransform tr = localInertiaFrame.inverse() * childTrans;
						compound->addChildShape(tr, childShape);
					}
				}
				else
				{
					btTransform childTrans;
					childTrans.setIdentity();
					compound->addChildShape(localInertiaFrame.inverse() * childTrans, handle->m_collisionShape);
				}
			}
		}
		m_allocatedCollisionShapes.push_back(compound);
		return compound;
	}

	virtual int getNumAllocatedCollisionShapes() const
	{
		return m_allocatedCollisionShapes.size();
	}

	virtual class btCollisionShape* getAllocatedCollisionShape(int index)
	{
		return m_allocatedCollisionShapes[index];
	}
	virtual int getNumModels() const
	{
		return 1;
	}
	virtual void activateModel(int /*modelIndex*/)
	{
	}
};

void PhysicsServerCommandProcessor::createEmptyDynamicsWorld()
{
	m_data->m_constraintSolverType = eConstraintSolverLCP_SI;
	///collision configuration contains default setup for memory, collision setup
	//m_collisionConfiguration->setConvexConvexMultipointIterations();
#ifndef SKIP_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD
	m_data->m_collisionConfiguration = new btSoftBodyRigidBodyCollisionConfiguration();
#else
	m_data->m_collisionConfiguration = new btDefaultCollisionConfiguration();
#endif
	///use the default collision dispatcher. For parallel processing you can use a diffent dispatcher (see Extras/BulletMultiThreaded)
	m_data->m_dispatcher = new btCollisionDispatcher(m_data->m_collisionConfiguration);

	m_data->m_broadphaseCollisionFilterCallback = new MyOverlapFilterCallback(&m_data->m_pluginManager);
	m_data->m_broadphaseCollisionFilterCallback->m_filterMode = B3_FILTER_GROUPAMASKB_OR_GROUPBMASKA;

	m_data->m_pairCache = new btHashedOverlappingPairCache();

	m_data->m_pairCache->setOverlapFilterCallback(m_data->m_broadphaseCollisionFilterCallback);

	//int maxProxies = 32768;
	//m_data->m_broadphase = new btSimpleBroadphase(maxProxies, m_data->m_pairCache);
	btDbvtBroadphase* bv = new btDbvtBroadphase(m_data->m_pairCache);
	bv->setVelocityPrediction(0);
	m_data->m_broadphase = bv;

	m_data->m_solver = new btMultiBodyConstraintSolver;

#ifndef SKIP_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD
	m_data->m_dynamicsWorld = new btSoftMultiBodyDynamicsWorld(m_data->m_dispatcher, m_data->m_broadphase, m_data->m_solver, m_data->m_collisionConfiguration);
#else
	m_data->m_dynamicsWorld = new btMultiBodyDynamicsWorld(m_data->m_dispatcher, m_data->m_broadphase, m_data->m_solver, m_data->m_collisionConfiguration);
#endif

	//Workaround: in a VR application, where we avoid synchronizing between GFX/Physics threads, we don't want to resize this array, so pre-allocate it
	m_data->m_dynamicsWorld->getCollisionObjectArray().reserve(32768);

	m_data->m_remoteDebugDrawer = new SharedMemoryDebugDrawer();

	m_data->m_dynamicsWorld->setGravity(btVector3(0, 0, 0));
	m_data->m_dynamicsWorld->getSolverInfo().m_erp2 = 0.08;

	m_data->m_dynamicsWorld->getSolverInfo().m_frictionERP = 0.2;  //need to check if there are artifacts with frictionERP
	m_data->m_dynamicsWorld->getSolverInfo().m_linearSlop = 0.00001;
	m_data->m_dynamicsWorld->getSolverInfo().m_numIterations = 50;
	m_data->m_dynamicsWorld->getSolverInfo().m_minimumSolverBatchSize = 0;
	m_data->m_dynamicsWorld->getSolverInfo().m_warmstartingFactor = 0.1;
	gDbvtMargin = btScalar(0);
	m_data->m_dynamicsWorld->getSolverInfo().m_leastSquaresResidualThreshold = 1e-7;

	if (m_data->m_guiHelper)
	{
		m_data->m_guiHelper->createPhysicsDebugDrawer(m_data->m_dynamicsWorld);
	}
	bool isPreTick = false;
	m_data->m_dynamicsWorld->setInternalTickCallback(logCallback, this, isPreTick);
	isPreTick = true;
	m_data->m_dynamicsWorld->setInternalTickCallback(preTickCallback, this, isPreTick);

	gContactAddedCallback = MyContactAddedCallback;

#ifdef B3_ENABLE_TINY_AUDIO
	m_data->m_soundEngine.init(16, true);

//we don't use those callbacks (yet), experimental

//	gContactDestroyedCallback = MyContactDestroyedCallback;
//	gContactProcessedCallback = MyContactProcessedCallback;
//	gContactStartedCallback = MyContactStartedCallback;
//	gContactEndedCallback = MyContactEndedCallback;
#endif
}

void PhysicsServerCommandProcessor::deleteStateLoggers()
{
	for (int i = 0; i < m_data->m_stateLoggers.size(); i++)
	{
		m_data->m_stateLoggers[i]->stop();
		delete m_data->m_stateLoggers[i];
	}
	m_data->m_stateLoggers.clear();
}

void PhysicsServerCommandProcessor::deleteCachedInverseKinematicsBodies()
{
	for (int i = 0; i < m_data->m_inverseKinematicsHelpers.size(); i++)
	{
		IKTrajectoryHelper** ikHelperPtr = m_data->m_inverseKinematicsHelpers.getAtIndex(i);
		if (ikHelperPtr)
		{
			IKTrajectoryHelper* ikHelper = *ikHelperPtr;
			delete ikHelper;
		}
	}
	m_data->m_inverseKinematicsHelpers.clear();
}
void PhysicsServerCommandProcessor::deleteCachedInverseDynamicsBodies()
{
	for (int i = 0; i < m_data->m_inverseDynamicsBodies.size(); i++)
	{
		btInverseDynamics::MultiBodyTree** treePtrPtr = m_data->m_inverseDynamicsBodies.getAtIndex(i);
		if (treePtrPtr)
		{
			btInverseDynamics::MultiBodyTree* tree = *treePtrPtr;
			delete tree;
		}
	}
	m_data->m_inverseDynamicsBodies.clear();
}

void PhysicsServerCommandProcessor::deleteDynamicsWorld()
{
#ifdef B3_ENABLE_TINY_AUDIO
	m_data->m_soundEngine.exit();
	//gContactDestroyedCallback = 0;
	//gContactProcessedCallback = 0;
	//gContactStartedCallback = 0;
	//gContactEndedCallback = 0;
#endif

	deleteCachedInverseDynamicsBodies();
	deleteCachedInverseKinematicsBodies();
	deleteStateLoggers();

	m_data->m_userConstraints.clear();
	m_data->m_saveWorldBodyData.clear();

	for (int i = 0; i < m_data->m_multiBodyJointFeedbacks.size(); i++)
	{
		delete m_data->m_multiBodyJointFeedbacks[i];
	}
	m_data->m_multiBodyJointFeedbacks.clear();

	for (int i = 0; i < m_data->m_worldImporters.size(); i++)
	{
		m_data->m_worldImporters[i]->deleteAllData();
		delete m_data->m_worldImporters[i];
	}
	m_data->m_worldImporters.clear();

#ifdef ENABLE_LINK_MAPPER
	for (int i = 0; i < m_data->m_urdfLinkNameMapper.size(); i++)
	{
		delete m_data->m_urdfLinkNameMapper[i];
	}
	m_data->m_urdfLinkNameMapper.clear();
#endif  //ENABLE_LINK_MAPPER

	for (int i = 0; i < m_data->m_strings.size(); i++)
	{
		delete m_data->m_strings[i];
	}
	m_data->m_strings.clear();

	btAlignedObjectArray<btTypedConstraint*> constraints;
	btAlignedObjectArray<btMultiBodyConstraint*> mbconstraints;

	if (m_data->m_dynamicsWorld)
	{
		int i;
		for (i = m_data->m_dynamicsWorld->getNumConstraints() - 1; i >= 0; i--)
		{
			btTypedConstraint* constraint = m_data->m_dynamicsWorld->getConstraint(i);
			constraints.push_back(constraint);
			m_data->m_dynamicsWorld->removeConstraint(constraint);
		}
		for (i = m_data->m_dynamicsWorld->getNumMultiBodyConstraints() - 1; i >= 0; i--)
		{
			btMultiBodyConstraint* mbconstraint = m_data->m_dynamicsWorld->getMultiBodyConstraint(i);
			mbconstraints.push_back(mbconstraint);
			m_data->m_dynamicsWorld->removeMultiBodyConstraint(mbconstraint);
		}

		for (i = m_data->m_dynamicsWorld->getNumCollisionObjects() - 1; i >= 0; i--)
		{
			btCollisionObject* obj = m_data->m_dynamicsWorld->getCollisionObjectArray()[i];
			btRigidBody* body = btRigidBody::upcast(obj);
			if (body && body->getMotionState())
			{
				delete body->getMotionState();
			}
			m_data->m_dynamicsWorld->removeCollisionObject(obj);
			delete obj;
		}
		for (i = m_data->m_dynamicsWorld->getNumMultibodies() - 1; i >= 0; i--)
		{
			btMultiBody* mb = m_data->m_dynamicsWorld->getMultiBody(i);
			m_data->m_dynamicsWorld->removeMultiBody(mb);
			delete mb;
		}
#ifndef SKIP_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD
		for (i = m_data->m_dynamicsWorld->getSoftBodyArray().size() - 1; i >= 0; i--)
		{
			btSoftBody* sb = m_data->m_dynamicsWorld->getSoftBodyArray()[i];
			m_data->m_dynamicsWorld->removeSoftBody(sb);
			delete sb;
		}
#endif
	}

	for (int i = 0; i < constraints.size(); i++)
	{
		delete constraints[i];
	}
	constraints.clear();
	for (int i = 0; i < mbconstraints.size(); i++)
	{
		delete mbconstraints[i];
	}
	mbconstraints.clear();
	//delete collision shapes
	for (int j = 0; j < m_data->m_collisionShapes.size(); j++)
	{
		btCollisionShape* shape = m_data->m_collisionShapes[j];

		//check for internal edge utility, delete memory
		if (shape->getShapeType() == TRIANGLE_MESH_SHAPE_PROXYTYPE)
		{
			btBvhTriangleMeshShape* trimesh = (btBvhTriangleMeshShape*)shape;
			if (trimesh->getTriangleInfoMap())
			{
				delete trimesh->getTriangleInfoMap();
			}
		}
		delete shape;
	}
	for (int j = 0; j < m_data->m_meshInterfaces.size(); j++)
	{
		delete m_data->m_meshInterfaces[j];
	}

	if (m_data->m_guiHelper)
	{
		for (int j = 0; j < m_data->m_allocatedTextures.size(); j++)
		{
			int texId = m_data->m_allocatedTextures[j];
			m_data->m_guiHelper->removeTexture(texId);
		}
	}
	m_data->m_allocatedTextures.clear();
	m_data->m_meshInterfaces.clear();
	m_data->m_collisionShapes.clear();
	m_data->m_bulletCollisionShape2UrdfCollision.clear();

	delete m_data->m_dynamicsWorld;
	m_data->m_dynamicsWorld = 0;

	delete m_data->m_remoteDebugDrawer;
	m_data->m_remoteDebugDrawer = 0;

	delete m_data->m_solver;
	m_data->m_solver = 0;

	delete m_data->m_broadphase;
	m_data->m_broadphase = 0;

	delete m_data->m_pairCache;
	m_data->m_pairCache = 0;

	delete m_data->m_broadphaseCollisionFilterCallback;
	m_data->m_broadphaseCollisionFilterCallback = 0;

	delete m_data->m_dispatcher;
	m_data->m_dispatcher = 0;

	delete m_data->m_collisionConfiguration;
	m_data->m_collisionConfiguration = 0;
	m_data->m_userConstraintUIDGenerator = 1;
}

bool PhysicsServerCommandProcessor::supportsJointMotor(btMultiBody* mb, int mbLinkIndex)
{
	bool canHaveMotor = (mb->getLink(mbLinkIndex).m_jointType == btMultibodyLink::eRevolute || mb->getLink(mbLinkIndex).m_jointType == btMultibodyLink::ePrismatic);
	return canHaveMotor;
}

//for testing, create joint motors for revolute and prismatic joints
void PhysicsServerCommandProcessor::createJointMotors(btMultiBody* mb)
{
	int numLinks = mb->getNumLinks();
	for (int i = 0; i < numLinks; i++)
	{
		int mbLinkIndex = i;

		if (supportsJointMotor(mb, mbLinkIndex))
		{
			float maxMotorImpulse = 1.f;
			int dof = 0;
			btScalar desiredVelocity = 0.f;
			btMultiBodyJointMotor* motor = new btMultiBodyJointMotor(mb, mbLinkIndex, dof, desiredVelocity, maxMotorImpulse);
			motor->setPositionTarget(0, 0);
			motor->setVelocityTarget(0, 1);
			//motor->setRhsClamp(gRhsClamp);
			//motor->setMaxAppliedImpulse(0);
			mb->getLink(mbLinkIndex).m_userPtr = motor;
			m_data->m_dynamicsWorld->addMultiBodyConstraint(motor);
			motor->finalizeMultiDof();
		}
	}
}

bool PhysicsServerCommandProcessor::processImportedObjects(const char* fileName, char* bufferServerToClient, int bufferSizeInBytes, bool useMultiBody, int flags, URDFImporterInterface& u2b)
{
	bool loadOk = true;

	btTransform rootTrans;
	rootTrans.setIdentity();
	if (m_data->m_verboseOutput)
	{
		b3Printf("loaded %s OK!", fileName);
	}
	SaveWorldObjectData sd;
	sd.m_fileName = fileName;

	int currentOpenGLTextureIndex = 0;

	for (int m = 0; m < u2b.getNumModels(); m++)
	{
		u2b.activateModel(m);
		btMultiBody* mb = 0;
		btRigidBody* rb = 0;

		//get a body index
		int bodyUniqueId = m_data->m_bodyHandles.allocHandle();

		InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(bodyUniqueId);

		sd.m_bodyUniqueIds.push_back(bodyUniqueId);

		u2b.setBodyUniqueId(bodyUniqueId);
		{
			btScalar mass = 0;
			bodyHandle->m_rootLocalInertialFrame.setIdentity();
			bodyHandle->m_bodyName = u2b.getBodyName();
			btVector3 localInertiaDiagonal(0, 0, 0);
			int urdfLinkIndex = u2b.getRootLinkIndex();
			u2b.getMassAndInertia2(urdfLinkIndex, mass, localInertiaDiagonal, bodyHandle->m_rootLocalInertialFrame, flags);
		}

		//todo: move these internal API called inside the 'ConvertURDF2Bullet' call, hidden from the user
		//int rootLinkIndex = u2b.getRootLinkIndex();
		//b3Printf("urdf root link index = %d\n",rootLinkIndex);
		MyMultiBodyCreator creation(m_data->m_guiHelper);

		u2b.getRootTransformInWorld(rootTrans);
		//CUF_RESERVED is a temporary flag, for backward compatibility purposes
		flags |= CUF_RESERVED;

		if (flags & CUF_ENABLE_CACHED_GRAPHICS_SHAPES)
		{
			{
				UrdfVisualShapeCache* tmpPtr = m_data->m_cachedVUrdfisualShapes[fileName];
				if (tmpPtr == 0)
				{
					m_data->m_cachedVUrdfisualShapes.insert(fileName, UrdfVisualShapeCache());
				}
			}
			UrdfVisualShapeCache* cachedVisualShapesPtr = m_data->m_cachedVUrdfisualShapes[fileName];
			ConvertURDF2Bullet(u2b, creation, rootTrans, m_data->m_dynamicsWorld, useMultiBody, u2b.getPathPrefix(), flags, cachedVisualShapesPtr);
		}
		else
		{
			ConvertURDF2Bullet(u2b, creation, rootTrans, m_data->m_dynamicsWorld, useMultiBody, u2b.getPathPrefix(), flags);
		}

		mb = creation.getBulletMultiBody();
		rb = creation.getRigidBody();
		if (rb)
			rb->setUserIndex2(bodyUniqueId);

		if (mb)
			mb->setUserIndex2(bodyUniqueId);

		if (mb)
		{
			bodyHandle->m_multiBody = mb;

			m_data->m_sdfRecentLoadedBodies.push_back(bodyUniqueId);

			createJointMotors(mb);

#ifdef B3_ENABLE_TINY_AUDIO
			{
				SDFAudioSource audioSource;
				int urdfRootLink = u2b.getRootLinkIndex();  //LinkIndex = creation.m_mb2urdfLink[-1];
				if (u2b.getLinkAudioSource(urdfRootLink, audioSource))
				{
					int flags = mb->getBaseCollider()->getCollisionFlags();
					mb->getBaseCollider()->setCollisionFlags(flags | btCollisionObject::CF_HAS_COLLISION_SOUND_TRIGGER);
					audioSource.m_userIndex = m_data->m_soundEngine.loadWavFile(audioSource.m_uri.c_str());
					if (audioSource.m_userIndex >= 0)
					{
						bodyHandle->m_audioSources.insert(-1, audioSource);
					}
				}
			}
#endif
			//disable serialization of the collision objects (they are too big, and the client likely doesn't need them);

			bodyHandle->m_linkLocalInertialFrames.reserve(mb->getNumLinks());
			for (int i = 0; i < mb->getNumLinks(); i++)
			{
				//disable serialization of the collision objects

				int urdfLinkIndex = creation.m_mb2urdfLink[i];
				btScalar mass;
				btVector3 localInertiaDiagonal(0, 0, 0);
				btTransform localInertialFrame;
				u2b.getMassAndInertia2(urdfLinkIndex, mass, localInertiaDiagonal, localInertialFrame, flags);
				bodyHandle->m_linkLocalInertialFrames.push_back(localInertialFrame);

				std::string* linkName = new std::string(u2b.getLinkName(urdfLinkIndex).c_str());
				m_data->m_strings.push_back(linkName);

				mb->getLink(i).m_linkName = linkName->c_str();

				std::string* jointName = new std::string(u2b.getJointName(urdfLinkIndex).c_str());
				m_data->m_strings.push_back(jointName);

				mb->getLink(i).m_jointName = jointName->c_str();

#ifdef B3_ENABLE_TINY_AUDIO
				{
					SDFAudioSource audioSource;
					int urdfLinkIndex = creation.m_mb2urdfLink[link];
					if (u2b.getLinkAudioSource(urdfLinkIndex, audioSource))
					{
						int flags = mb->getLink(link).m_collider->getCollisionFlags();
						mb->getLink(i).m_collider->setCollisionFlags(flags | btCollisionObject::CF_HAS_COLLISION_SOUND_TRIGGER);
						audioSource.m_userIndex = m_data->m_soundEngine.loadWavFile(audioSource.m_uri.c_str());
						if (audioSource.m_userIndex >= 0)
						{
							bodyHandle->m_audioSources.insert(link, audioSource);
						}
					}
				}
#endif
			}
			std::string* baseName = new std::string(u2b.getLinkName(u2b.getRootLinkIndex()));
			m_data->m_strings.push_back(baseName);
			mb->setBaseName(baseName->c_str());
		}
		else
		{
			//b3Warning("No multibody loaded from URDF. Could add btRigidBody+btTypedConstraint solution later.");
			bodyHandle->m_rigidBody = rb;
			rb->setUserIndex2(bodyUniqueId);
			m_data->m_sdfRecentLoadedBodies.push_back(bodyUniqueId);

			std::string* baseName = new std::string(u2b.getLinkName(u2b.getRootLinkIndex()));
			m_data->m_strings.push_back(baseName);
			bodyHandle->m_bodyName = *baseName;

			int numJoints = creation.getNum6DofConstraints();
			bodyHandle->m_rigidBodyJoints.reserve(numJoints);
			bodyHandle->m_rigidBodyJointNames.reserve(numJoints);
			bodyHandle->m_rigidBodyLinkNames.reserve(numJoints);
			for (int i = 0; i < numJoints; i++)
			{
				int urdfLinkIndex = creation.m_mb2urdfLink[i];

				btGeneric6DofSpring2Constraint* con = creation.get6DofConstraint(i);

				std::string* linkName = new std::string(u2b.getLinkName(urdfLinkIndex).c_str());
				m_data->m_strings.push_back(linkName);

				std::string* jointName = new std::string(u2b.getJointName(urdfLinkIndex).c_str());
				m_data->m_strings.push_back(jointName);

				bodyHandle->m_rigidBodyJointNames.push_back(*jointName);
				bodyHandle->m_rigidBodyLinkNames.push_back(*linkName);

				bodyHandle->m_rigidBodyJoints.push_back(con);
			}
		}

		{
			int startShapeIndex = 0;

			if (m_data->m_pluginManager.getRenderInterface())
			{
				int totalNumVisualShapes = m_data->m_pluginManager.getRenderInterface()->getNumVisualShapes(bodyUniqueId);
				//int totalBytesPerVisualShape = sizeof (b3VisualShapeData);
				//int visualShapeStorage = bufferSizeInBytes / totalBytesPerVisualShape - 1;
				b3VisualShapeData tmpShape;

				int remain = totalNumVisualShapes - startShapeIndex;
				int shapeIndex = startShapeIndex;

				int success = m_data->m_pluginManager.getRenderInterface()->getVisualShapesData(bodyUniqueId, shapeIndex, &tmpShape);
				if (success)
				{
					if (tmpShape.m_tinyRendererTextureId >= 0)
					{
						int openglTextureUniqueId = -1;

						//find companion opengl texture unique id and create a 'textureUid'
						if (currentOpenGLTextureIndex < u2b.getNumAllocatedTextures())
						{
							openglTextureUniqueId = u2b.getAllocatedTexture(currentOpenGLTextureIndex);
							currentOpenGLTextureIndex++;
						}

						int texHandle = m_data->m_textureHandles.allocHandle();
						InternalTextureHandle* texH = m_data->m_textureHandles.getHandle(texHandle);
						if (texH)
						{
							texH->m_tinyRendererTextureId = tmpShape.m_tinyRendererTextureId;
							texH->m_openglTextureId = openglTextureUniqueId;
						}
					}
				}
			}
		}

		b3Notification notification;
		notification.m_notificationType = BODY_ADDED;
		notification.m_bodyArgs.m_bodyUniqueId = bodyUniqueId;
		m_data->m_pluginManager.addNotification(notification);
	}

	for (int i = 0; i < u2b.getNumAllocatedTextures(); i++)
	{
		int texId = u2b.getAllocatedTexture(i);
		m_data->m_allocatedTextures.push_back(texId);
	}

	/*
 int texHandle = m_data->m_textureHandles.allocHandle();
 InternalTextureHandle* texH = m_data->m_textureHandles.getHandle(texHandle);
 if(texH)
 {
 texH->m_tinyRendererTextureId = -1;
 texH->m_openglTextureId = -1;
 */

	for (int i = 0; i < u2b.getNumAllocatedMeshInterfaces(); i++)
	{
		m_data->m_meshInterfaces.push_back(u2b.getAllocatedMeshInterface(i));
	}

	for (int i = 0; i < u2b.getNumAllocatedCollisionShapes(); i++)
	{
		btCollisionShape* shape = u2b.getAllocatedCollisionShape(i);
		m_data->m_collisionShapes.push_back(shape);
		UrdfCollision urdfCollision;
		if (u2b.getUrdfFromCollisionShape(shape, urdfCollision))
		{
			m_data->m_bulletCollisionShape2UrdfCollision.insert(shape, urdfCollision);
		}
		if (shape->getShapeType() == COMPOUND_SHAPE_PROXYTYPE)
		{
			btCompoundShape* compound = (btCompoundShape*)shape;
			for (int c = 0; c < compound->getNumChildShapes(); c++)
			{
				btCollisionShape* childShape = compound->getChildShape(c);
				if (u2b.getUrdfFromCollisionShape(childShape, urdfCollision))
				{
					m_data->m_bulletCollisionShape2UrdfCollision.insert(childShape, urdfCollision);
				}
			}
		}
	}

	m_data->m_saveWorldBodyData.push_back(sd);

	return loadOk;
}

struct MyMJCFLogger2 : public MJCFErrorLogger
{
	virtual void reportError(const char* error)
	{
		b3Error(error);
	}
	virtual void reportWarning(const char* warning)
	{
		b3Warning(warning);
	}
	virtual void printMessage(const char* msg)
	{
		b3Printf(msg);
	}
};

bool PhysicsServerCommandProcessor::loadMjcf(const char* fileName, char* bufferServerToClient, int bufferSizeInBytes, bool useMultiBody, int flags)
{
	btAssert(m_data->m_dynamicsWorld);
	if (!m_data->m_dynamicsWorld)
	{
		b3Error("loadSdf: No valid m_dynamicsWorld");
		return false;
	}

	m_data->m_sdfRecentLoadedBodies.clear();

	BulletMJCFImporter u2b(m_data->m_guiHelper, m_data->m_pluginManager.getRenderInterface(), flags);

	bool useFixedBase = false;
	MyMJCFLogger2 logger;
	bool loadOk = u2b.loadMJCF(fileName, &logger, useFixedBase);
	if (loadOk)
	{
		processImportedObjects(fileName, bufferServerToClient, bufferSizeInBytes, useMultiBody, flags, u2b);
	}
	return loadOk;
}

bool PhysicsServerCommandProcessor::loadSdf(const char* fileName, char* bufferServerToClient, int bufferSizeInBytes, bool useMultiBody, int flags, btScalar globalScaling)
{
	btAssert(m_data->m_dynamicsWorld);
	if (!m_data->m_dynamicsWorld)
	{
		b3Error("loadSdf: No valid m_dynamicsWorld");
		return false;
	}

	m_data->m_sdfRecentLoadedBodies.clear();

	BulletURDFImporter u2b(m_data->m_guiHelper, m_data->m_pluginManager.getRenderInterface(), globalScaling, flags);
	u2b.setEnableTinyRenderer(m_data->m_enableTinyRenderer);

	bool forceFixedBase = false;
	bool loadOk = u2b.loadSDF(fileName, forceFixedBase);

	if (loadOk)
	{
		processImportedObjects(fileName, bufferServerToClient, bufferSizeInBytes, useMultiBody, flags, u2b);
	}
	return loadOk;
}

bool PhysicsServerCommandProcessor::loadUrdf(const char* fileName, const btVector3& pos, const btQuaternion& orn,
											 bool useMultiBody, bool useFixedBase, int* bodyUniqueIdPtr, char* bufferServerToClient, int bufferSizeInBytes, int orgFlags, btScalar globalScaling)
{
	//clear the LOAD_SDF_FILE=1 bit, which is reserved for internal use of loadSDF command.
	int flags = orgFlags & ~1;
	m_data->m_sdfRecentLoadedBodies.clear();
	*bodyUniqueIdPtr = -1;

	BT_PROFILE("loadURDF");
	btAssert(m_data->m_dynamicsWorld);
	if (!m_data->m_dynamicsWorld)
	{
		b3Error("loadUrdf: No valid m_dynamicsWorld");
		return false;
	}

	BulletURDFImporter u2b(m_data->m_guiHelper, m_data->m_pluginManager.getRenderInterface(), globalScaling, flags);
	u2b.setEnableTinyRenderer(m_data->m_enableTinyRenderer);
	bool loadOk = u2b.loadURDF(fileName, useFixedBase);

	if (loadOk)
	{
		btTransform rootTrans;
		rootTrans.setOrigin(pos);
		rootTrans.setRotation(orn);
		u2b.setRootTransformInWorld(rootTrans);
		bool ok = processImportedObjects(fileName, bufferServerToClient, bufferSizeInBytes, useMultiBody, flags, u2b);
		if (ok)
		{
			if (m_data->m_sdfRecentLoadedBodies.size() == 1)
			{
				*bodyUniqueIdPtr = m_data->m_sdfRecentLoadedBodies[0];
			}
			m_data->m_sdfRecentLoadedBodies.clear();
		}
		return ok;
	}
	return false;
}

void PhysicsServerCommandProcessor::replayLogCommand(char* bufferServerToClient, int bufferSizeInBytes)
{
	if (m_data->m_logPlayback)
	{
		SharedMemoryCommand clientCmd;
		SharedMemoryStatus serverStatus;

		bool hasCommand = m_data->m_logPlayback->processNextCommand(&clientCmd);
		if (hasCommand)
		{
			processCommand(clientCmd, serverStatus, bufferServerToClient, bufferSizeInBytes);
		}
	}
}

int PhysicsServerCommandProcessor::createBodyInfoStream(int bodyUniqueId, char* bufferServerToClient, int bufferSizeInBytes)
{
	int streamSizeInBytes = 0;
	//serialize the btMultiBody and send the data to the client. This is one way to get the link/joint names across the (shared memory) wire

	InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(bodyUniqueId);
	btMultiBody* mb = bodyHandle ? bodyHandle->m_multiBody : 0;
	if (mb)
	{
		UrdfLinkNameMapUtil utilBla;
		UrdfLinkNameMapUtil* util = &utilBla;
		btDefaultSerializer ser(bufferSizeInBytes, (unsigned char*)bufferServerToClient);

		util->m_mb = mb;
		util->m_memSerializer = &ser;
		util->m_memSerializer->startSerialization();

		//disable serialization of the collision objects (they are too big, and the client likely doesn't need them);
		util->m_memSerializer->m_skipPointers.insert(mb->getBaseCollider(), 0);
		if (mb->getBaseName())
		{
			util->m_memSerializer->registerNameForPointer(mb->getBaseName(), mb->getBaseName());
		}

		bodyHandle->m_linkLocalInertialFrames.reserve(mb->getNumLinks());
		for (int i = 0; i < mb->getNumLinks(); i++)
		{
			//disable serialization of the collision objects
			util->m_memSerializer->m_skipPointers.insert(mb->getLink(i).m_collider, 0);
			util->m_memSerializer->registerNameForPointer(mb->getLink(i).m_linkName, mb->getLink(i).m_linkName);
			util->m_memSerializer->registerNameForPointer(mb->getLink(i).m_jointName, mb->getLink(i).m_jointName);
		}

		util->m_memSerializer->registerNameForPointer(mb->getBaseName(), mb->getBaseName());

		int len = mb->calculateSerializeBufferSize();
		btChunk* chunk = util->m_memSerializer->allocate(len, 1);
		const char* structType = mb->serialize(chunk->m_oldPtr, util->m_memSerializer);
		util->m_memSerializer->finalizeChunk(chunk, structType, BT_MULTIBODY_CODE, mb);
		streamSizeInBytes = util->m_memSerializer->getCurrentBufferSize();
	}
	else
	{
		btRigidBody* rb = bodyHandle ? bodyHandle->m_rigidBody : 0;
		if (rb)
		{
			UrdfLinkNameMapUtil utilBla;
			UrdfLinkNameMapUtil* util = &utilBla;
			btDefaultSerializer ser(bufferSizeInBytes, (unsigned char*)bufferServerToClient);

			util->m_memSerializer = &ser;
			util->m_memSerializer->startSerialization();
			util->m_memSerializer->registerNameForPointer(bodyHandle->m_rigidBody, bodyHandle->m_bodyName.c_str());
			//disable serialization of the collision objects (they are too big, and the client likely doesn't need them);
			for (int i = 0; i < bodyHandle->m_rigidBodyJoints.size(); i++)
			{
				const btGeneric6DofSpring2Constraint* con = bodyHandle->m_rigidBodyJoints.at(i);

				util->m_memSerializer->registerNameForPointer(con, bodyHandle->m_rigidBodyJointNames[i].c_str());
				util->m_memSerializer->registerNameForPointer(&con->getRigidBodyB(), bodyHandle->m_rigidBodyLinkNames[i].c_str());

				const btRigidBody& bodyA = con->getRigidBodyA();

				int len = con->calculateSerializeBufferSize();
				btChunk* chunk = util->m_memSerializer->allocate(len, 1);
				const char* structType = con->serialize(chunk->m_oldPtr, util->m_memSerializer);
				util->m_memSerializer->finalizeChunk(chunk, structType, BT_CONSTRAINT_CODE, (void*)con);
			}

			streamSizeInBytes = util->m_memSerializer->getCurrentBufferSize();
		}
	}

	return streamSizeInBytes;
}

bool PhysicsServerCommandProcessor::processStateLoggingCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	BT_PROFILE("CMD_STATE_LOGGING");

	serverStatusOut.m_type = CMD_STATE_LOGGING_FAILED;
	bool hasStatus = true;

	if (clientCmd.m_updateFlags & STATE_LOGGING_START_LOG)
	{
		if (clientCmd.m_stateLoggingArguments.m_logType == STATE_LOGGING_ALL_COMMANDS)
		{
			if (m_data->m_commandLogger == 0)
			{
				enableCommandLogging(true, clientCmd.m_stateLoggingArguments.m_fileName);
				serverStatusOut.m_type = CMD_STATE_LOGGING_START_COMPLETED;
				int loggerUid = m_data->m_stateLoggersUniqueId++;
				m_data->m_commandLoggingUid = loggerUid;
				serverStatusOut.m_stateLoggingResultArgs.m_loggingUniqueId = loggerUid;
			}
		}

		if (clientCmd.m_stateLoggingArguments.m_logType == STATE_REPLAY_ALL_COMMANDS)
		{
			if (m_data->m_logPlayback == 0)
			{
				replayFromLogFile(clientCmd.m_stateLoggingArguments.m_fileName);
				serverStatusOut.m_type = CMD_STATE_LOGGING_START_COMPLETED;
				int loggerUid = m_data->m_stateLoggersUniqueId++;
				m_data->m_logPlaybackUid = loggerUid;
				serverStatusOut.m_stateLoggingResultArgs.m_loggingUniqueId = loggerUid;
			}
		}

		if (clientCmd.m_stateLoggingArguments.m_logType == STATE_LOGGING_PROFILE_TIMINGS)
		{
			if (m_data->m_profileTimingLoggingUid < 0)
			{
				b3ChromeUtilsStartTimings();
				m_data->m_profileTimingFileName = clientCmd.m_stateLoggingArguments.m_fileName;
				int loggerUid = m_data->m_stateLoggersUniqueId++;
				serverStatusOut.m_type = CMD_STATE_LOGGING_START_COMPLETED;
				serverStatusOut.m_stateLoggingResultArgs.m_loggingUniqueId = loggerUid;
				m_data->m_profileTimingLoggingUid = loggerUid;
			}
		}
		if (clientCmd.m_stateLoggingArguments.m_logType == STATE_LOGGING_VIDEO_MP4)
		{
			//if (clientCmd.m_stateLoggingArguments.m_fileName)
			{
				int loggerUid = m_data->m_stateLoggersUniqueId++;
				VideoMP4Loggger* logger = new VideoMP4Loggger(loggerUid, clientCmd.m_stateLoggingArguments.m_fileName, this->m_data->m_guiHelper);
				m_data->m_stateLoggers.push_back(logger);
				serverStatusOut.m_type = CMD_STATE_LOGGING_START_COMPLETED;
				serverStatusOut.m_stateLoggingResultArgs.m_loggingUniqueId = loggerUid;
			}
		}

		if (clientCmd.m_stateLoggingArguments.m_logType == STATE_LOGGING_MINITAUR)
		{
			std::string fileName = clientCmd.m_stateLoggingArguments.m_fileName;
			//either provide the minitaur by object unique Id, or search for first multibody with 8 motors...

			if ((clientCmd.m_updateFlags & STATE_LOGGING_FILTER_OBJECT_UNIQUE_ID) && (clientCmd.m_stateLoggingArguments.m_numBodyUniqueIds > 0))
			{
				int bodyUniqueId = clientCmd.m_stateLoggingArguments.m_bodyUniqueIds[0];
				InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);
				if (body)
				{
					if (body->m_multiBody)
					{
						btAlignedObjectArray<std::string> motorNames;
						motorNames.push_back("motor_front_leftR_joint");
						motorNames.push_back("motor_front_leftL_joint");
						motorNames.push_back("motor_back_leftR_joint");
						motorNames.push_back("motor_back_leftL_joint");
						motorNames.push_back("motor_front_rightL_joint");
						motorNames.push_back("motor_front_rightR_joint");
						motorNames.push_back("motor_back_rightL_joint");
						motorNames.push_back("motor_back_rightR_joint");

						btAlignedObjectArray<int> motorIdList;
						for (int m = 0; m < motorNames.size(); m++)
						{
							for (int i = 0; i < body->m_multiBody->getNumLinks(); i++)
							{
								std::string jointName;
								if (body->m_multiBody->getLink(i).m_jointName)
								{
									jointName = body->m_multiBody->getLink(i).m_jointName;
								}
								if (motorNames[m] == jointName)
								{
									motorIdList.push_back(i);
								}
							}
						}

						if (motorIdList.size() == 8)
						{
							int loggerUid = m_data->m_stateLoggersUniqueId++;
							MinitaurStateLogger* logger = new MinitaurStateLogger(loggerUid, fileName, body->m_multiBody, motorIdList);
							m_data->m_stateLoggers.push_back(logger);
							serverStatusOut.m_type = CMD_STATE_LOGGING_START_COMPLETED;
							serverStatusOut.m_stateLoggingResultArgs.m_loggingUniqueId = loggerUid;
						}
					}
				}
			}
		}

		if (clientCmd.m_stateLoggingArguments.m_logType == STATE_LOGGING_GENERIC_ROBOT)
		{
			std::string fileName = clientCmd.m_stateLoggingArguments.m_fileName;

			int loggerUid = m_data->m_stateLoggersUniqueId++;
			int maxLogDof = 12;
			if ((clientCmd.m_updateFlags & STATE_LOGGING_MAX_LOG_DOF))
			{
				maxLogDof = clientCmd.m_stateLoggingArguments.m_maxLogDof;
			}

			int logFlags = 0;
			if (clientCmd.m_updateFlags & STATE_LOGGING_LOG_FLAGS)
			{
				logFlags = clientCmd.m_stateLoggingArguments.m_logFlags;
			}
			GenericRobotStateLogger* logger = new GenericRobotStateLogger(loggerUid, fileName, m_data->m_dynamicsWorld, maxLogDof, logFlags);

			if ((clientCmd.m_updateFlags & STATE_LOGGING_FILTER_OBJECT_UNIQUE_ID) && (clientCmd.m_stateLoggingArguments.m_numBodyUniqueIds > 0))
			{
				logger->m_filterObjectUniqueId = true;
				for (int i = 0; i < clientCmd.m_stateLoggingArguments.m_numBodyUniqueIds; ++i)
				{
					int objectUniqueId = clientCmd.m_stateLoggingArguments.m_bodyUniqueIds[i];
					logger->m_bodyIdList.push_back(objectUniqueId);
				}
			}

			m_data->m_stateLoggers.push_back(logger);
			serverStatusOut.m_type = CMD_STATE_LOGGING_START_COMPLETED;
			serverStatusOut.m_stateLoggingResultArgs.m_loggingUniqueId = loggerUid;
		}
		if (clientCmd.m_stateLoggingArguments.m_logType == STATE_LOGGING_CONTACT_POINTS)
		{
			std::string fileName = clientCmd.m_stateLoggingArguments.m_fileName;
			int loggerUid = m_data->m_stateLoggersUniqueId++;
			ContactPointsStateLogger* logger = new ContactPointsStateLogger(loggerUid, fileName, m_data->m_dynamicsWorld);
			if ((clientCmd.m_updateFlags & STATE_LOGGING_FILTER_LINK_INDEX_A) && clientCmd.m_stateLoggingArguments.m_linkIndexA >= -1)
			{
				logger->m_filterLinkA = true;
				logger->m_linkIndexA = clientCmd.m_stateLoggingArguments.m_linkIndexA;
			}
			if ((clientCmd.m_updateFlags & STATE_LOGGING_FILTER_LINK_INDEX_B) && clientCmd.m_stateLoggingArguments.m_linkIndexB >= -1)
			{
				logger->m_filterLinkB = true;
				logger->m_linkIndexB = clientCmd.m_stateLoggingArguments.m_linkIndexB;
			}
			if ((clientCmd.m_updateFlags & STATE_LOGGING_FILTER_BODY_UNIQUE_ID_A) && clientCmd.m_stateLoggingArguments.m_bodyUniqueIdA > -1)
			{
				logger->m_bodyUniqueIdA = clientCmd.m_stateLoggingArguments.m_bodyUniqueIdA;
			}
			if ((clientCmd.m_updateFlags & STATE_LOGGING_FILTER_BODY_UNIQUE_ID_B) && clientCmd.m_stateLoggingArguments.m_bodyUniqueIdB > -1)
			{
				logger->m_bodyUniqueIdB = clientCmd.m_stateLoggingArguments.m_bodyUniqueIdB;
			}
			m_data->m_stateLoggers.push_back(logger);
			serverStatusOut.m_type = CMD_STATE_LOGGING_START_COMPLETED;
			serverStatusOut.m_stateLoggingResultArgs.m_loggingUniqueId = loggerUid;
		}
		if (clientCmd.m_stateLoggingArguments.m_logType == STATE_LOGGING_VR_CONTROLLERS)
		{
			std::string fileName = clientCmd.m_stateLoggingArguments.m_fileName;
			int loggerUid = m_data->m_stateLoggersUniqueId++;
			int deviceFilterType = VR_DEVICE_CONTROLLER;
			if (clientCmd.m_updateFlags & STATE_LOGGING_FILTER_DEVICE_TYPE)
			{
				deviceFilterType = clientCmd.m_stateLoggingArguments.m_deviceFilterType;
			}
			VRControllerStateLogger* logger = new VRControllerStateLogger(loggerUid, deviceFilterType, fileName);
			m_data->m_stateLoggers.push_back(logger);
			serverStatusOut.m_type = CMD_STATE_LOGGING_START_COMPLETED;
			serverStatusOut.m_stateLoggingResultArgs.m_loggingUniqueId = loggerUid;
		}
	}
	if ((clientCmd.m_updateFlags & STATE_LOGGING_STOP_LOG) && clientCmd.m_stateLoggingArguments.m_loggingUniqueId >= 0)
	{
		if (clientCmd.m_stateLoggingArguments.m_loggingUniqueId == m_data->m_logPlaybackUid)
		{
			if (m_data->m_logPlayback)
			{
				delete m_data->m_logPlayback;
				m_data->m_logPlayback = 0;
				m_data->m_logPlaybackUid = -1;
			}
		}

		if (clientCmd.m_stateLoggingArguments.m_loggingUniqueId == m_data->m_commandLoggingUid)
		{
			if (m_data->m_commandLogger)
			{
				enableCommandLogging(false, 0);
				serverStatusOut.m_type = CMD_STATE_LOGGING_COMPLETED;
				m_data->m_commandLoggingUid = -1;
			}
		}

		if (clientCmd.m_stateLoggingArguments.m_loggingUniqueId == m_data->m_profileTimingLoggingUid)
		{
			serverStatusOut.m_type = CMD_STATE_LOGGING_COMPLETED;
			b3ChromeUtilsStopTimingsAndWriteJsonFile(m_data->m_profileTimingFileName.c_str());
			m_data->m_profileTimingLoggingUid = -1;
		}
		else
		{
			serverStatusOut.m_type = CMD_STATE_LOGGING_COMPLETED;
			for (int i = 0; i < m_data->m_stateLoggers.size(); i++)
			{
				if (m_data->m_stateLoggers[i]->m_loggingUniqueId == clientCmd.m_stateLoggingArguments.m_loggingUniqueId)
				{
					m_data->m_stateLoggers[i]->stop();
					delete m_data->m_stateLoggers[i];
					m_data->m_stateLoggers.removeAtIndex(i);
				}
			}
		}
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestCameraImageCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_REQUEST_CAMERA_IMAGE_DATA");
	int startPixelIndex = clientCmd.m_requestPixelDataArguments.m_startPixelIndex;
	int width = clientCmd.m_requestPixelDataArguments.m_pixelWidth;
	int height = clientCmd.m_requestPixelDataArguments.m_pixelHeight;
	int numPixelsCopied = 0;

	if ((clientCmd.m_requestPixelDataArguments.m_startPixelIndex == 0) &&
		(clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_SET_PIXEL_WIDTH_HEIGHT) != 0)
	{
		if (m_data->m_pluginManager.getRenderInterface())
		{
			m_data->m_pluginManager.getRenderInterface()->setWidthAndHeight(clientCmd.m_requestPixelDataArguments.m_pixelWidth,
																			clientCmd.m_requestPixelDataArguments.m_pixelHeight);
		}
	}
	int flags = 0;
	if (clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_HAS_FLAGS)
	{
		flags = clientCmd.m_requestPixelDataArguments.m_flags;
	}
	if (m_data->m_pluginManager.getRenderInterface())
	{
		m_data->m_pluginManager.getRenderInterface()->setFlags(flags);
	}

	int numTotalPixels = width * height;
	int numRemainingPixels = numTotalPixels - startPixelIndex;

	if (numRemainingPixels > 0)
	{
		int totalBytesPerPixel = 4 + 4 + 4;  //4 for rgb, 4 for depth, 4 for segmentation mask
		int maxNumPixels = bufferSizeInBytes / totalBytesPerPixel - 1;
		unsigned char* pixelRGBA = (unsigned char*)bufferServerToClient;
		int numRequestedPixels = btMin(maxNumPixels, numRemainingPixels);

		float* depthBuffer = (float*)(bufferServerToClient + numRequestedPixels * 4);
		int* segmentationMaskBuffer = (int*)(bufferServerToClient + numRequestedPixels * 8);

		serverStatusOut.m_numDataStreamBytes = numRequestedPixels * totalBytesPerPixel;
		float viewMat[16];
		float projMat[16];
		float projTextureViewMat[16];
		float projTextureProjMat[16];
		for (int i = 0; i < 16; i++)
		{
			viewMat[i] = clientCmd.m_requestPixelDataArguments.m_viewMatrix[i];
			projMat[i] = clientCmd.m_requestPixelDataArguments.m_projectionMatrix[i];
		}
		if ((clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_HAS_CAMERA_MATRICES) == 0)
		{
			b3OpenGLVisualizerCameraInfo tmpCamResult;
			bool result = this->m_data->m_guiHelper->getCameraInfo(
				&tmpCamResult.m_width,
				&tmpCamResult.m_height,
				tmpCamResult.m_viewMatrix,
				tmpCamResult.m_projectionMatrix,
				tmpCamResult.m_camUp,
				tmpCamResult.m_camForward,
				tmpCamResult.m_horizontal,
				tmpCamResult.m_vertical,
				&tmpCamResult.m_yaw,
				&tmpCamResult.m_pitch,
				&tmpCamResult.m_dist,
				tmpCamResult.m_target);
			if (result)
			{
				for (int i = 0; i < 16; i++)
				{
					viewMat[i] = tmpCamResult.m_viewMatrix[i];
					projMat[i] = tmpCamResult.m_projectionMatrix[i];
				}
			}
		}
		bool handled = false;

		if ((clientCmd.m_updateFlags & ER_BULLET_HARDWARE_OPENGL) != 0)
		{
			if ((flags & ER_USE_PROJECTIVE_TEXTURE) != 0)
			{
				this->m_data->m_guiHelper->setProjectiveTexture(true);
				if ((clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_HAS_PROJECTIVE_TEXTURE_MATRICES) != 0)
				{
					for (int i = 0; i < 16; i++)
					{
						projTextureViewMat[i] = clientCmd.m_requestPixelDataArguments.m_projectiveTextureViewMatrix[i];
						projTextureProjMat[i] = clientCmd.m_requestPixelDataArguments.m_projectiveTextureProjectionMatrix[i];
					}
				}
				else  // If no specified matrices for projective texture, then use the camera matrices.
				{
					for (int i = 0; i < 16; i++)
					{
						projTextureViewMat[i] = viewMat[i];
						projTextureProjMat[i] = projMat[i];
					}
				}
				this->m_data->m_guiHelper->setProjectiveTextureMatrices(projTextureViewMat, projTextureProjMat);
			}
			else
			{
				this->m_data->m_guiHelper->setProjectiveTexture(false);
			}

			m_data->m_guiHelper->copyCameraImageData(viewMat,
													 projMat, pixelRGBA, numRequestedPixels,
													 depthBuffer, numRequestedPixels,
													 segmentationMaskBuffer, numRequestedPixels,
													 startPixelIndex, width, height, &numPixelsCopied);

			if (numPixelsCopied > 0)
			{
				handled = true;
				m_data->m_guiHelper->debugDisplayCameraImageData(viewMat,
																 projMat, pixelRGBA, numRequestedPixels,
																 depthBuffer, numRequestedPixels,
																 0, numRequestedPixels,
																 startPixelIndex, width, height, &numPixelsCopied);
			}
		}
		if (!handled)
		{
			if (m_data->m_pluginManager.getRenderInterface())
			{
				if (clientCmd.m_requestPixelDataArguments.m_startPixelIndex == 0)
				{
					//   printf("-------------------------------\nRendering\n");

					if ((clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_SET_LIGHT_DIRECTION) != 0)
					{
						m_data->m_pluginManager.getRenderInterface()->setLightDirection(clientCmd.m_requestPixelDataArguments.m_lightDirection[0], clientCmd.m_requestPixelDataArguments.m_lightDirection[1], clientCmd.m_requestPixelDataArguments.m_lightDirection[2]);
					}

					if ((clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_SET_LIGHT_COLOR) != 0)
					{
						m_data->m_pluginManager.getRenderInterface()->setLightColor(clientCmd.m_requestPixelDataArguments.m_lightColor[0], clientCmd.m_requestPixelDataArguments.m_lightColor[1], clientCmd.m_requestPixelDataArguments.m_lightColor[2]);
					}

					if ((clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_SET_LIGHT_DISTANCE) != 0)
					{
						m_data->m_pluginManager.getRenderInterface()->setLightDistance(clientCmd.m_requestPixelDataArguments.m_lightDistance);
					}

					if ((clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_SET_SHADOW) != 0)
					{
						m_data->m_pluginManager.getRenderInterface()->setShadow((clientCmd.m_requestPixelDataArguments.m_hasShadow != 0));
					}

					if ((clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_SET_AMBIENT_COEFF) != 0)
					{
						m_data->m_pluginManager.getRenderInterface()->setLightAmbientCoeff(clientCmd.m_requestPixelDataArguments.m_lightAmbientCoeff);
					}

					if ((clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_SET_DIFFUSE_COEFF) != 0)
					{
						m_data->m_pluginManager.getRenderInterface()->setLightDiffuseCoeff(clientCmd.m_requestPixelDataArguments.m_lightDiffuseCoeff);
					}

					if ((clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_SET_SPECULAR_COEFF) != 0)
					{
						m_data->m_pluginManager.getRenderInterface()->setLightSpecularCoeff(clientCmd.m_requestPixelDataArguments.m_lightSpecularCoeff);
					}

					for (int i = 0; i < m_data->m_dynamicsWorld->getNumCollisionObjects(); i++)
					{
						const btCollisionObject* colObj = m_data->m_dynamicsWorld->getCollisionObjectArray()[i];
						m_data->m_pluginManager.getRenderInterface()->syncTransform(colObj->getBroadphaseHandle()->getUid(), colObj->getWorldTransform(), colObj->getCollisionShape()->getLocalScaling());
					}

					if ((clientCmd.m_updateFlags & REQUEST_PIXEL_ARGS_HAS_CAMERA_MATRICES) != 0)
					{
						m_data->m_pluginManager.getRenderInterface()->render(
							clientCmd.m_requestPixelDataArguments.m_viewMatrix,
							clientCmd.m_requestPixelDataArguments.m_projectionMatrix);
					}
					else
					{
						b3OpenGLVisualizerCameraInfo tmpCamResult;
						bool result = this->m_data->m_guiHelper->getCameraInfo(
							&tmpCamResult.m_width,
							&tmpCamResult.m_height,
							tmpCamResult.m_viewMatrix,
							tmpCamResult.m_projectionMatrix,
							tmpCamResult.m_camUp,
							tmpCamResult.m_camForward,
							tmpCamResult.m_horizontal,
							tmpCamResult.m_vertical,
							&tmpCamResult.m_yaw,
							&tmpCamResult.m_pitch,
							&tmpCamResult.m_dist,
							tmpCamResult.m_target);
						if (result)
						{
							m_data->m_pluginManager.getRenderInterface()->render(tmpCamResult.m_viewMatrix, tmpCamResult.m_projectionMatrix);
						}
						else
						{
							m_data->m_pluginManager.getRenderInterface()->render();
						}
					}
				}
			}

			if (m_data->m_pluginManager.getRenderInterface())
			{
				m_data->m_pluginManager.getRenderInterface()->copyCameraImageData(pixelRGBA, numRequestedPixels,
																				  depthBuffer, numRequestedPixels,
																				  segmentationMaskBuffer, numRequestedPixels,
																				  startPixelIndex, &width, &height, &numPixelsCopied);
			}

			m_data->m_guiHelper->debugDisplayCameraImageData(clientCmd.m_requestPixelDataArguments.m_viewMatrix,
															 clientCmd.m_requestPixelDataArguments.m_projectionMatrix, pixelRGBA, numRequestedPixels,
															 depthBuffer, numRequestedPixels,
															 segmentationMaskBuffer, numRequestedPixels,
															 startPixelIndex, width, height, &numPixelsCopied);
		}

		//each pixel takes 4 RGBA values and 1 float = 8 bytes
	}
	else
	{
	}

	serverStatusOut.m_type = CMD_CAMERA_IMAGE_COMPLETED;

	serverStatusOut.m_sendPixelDataArguments.m_numPixelsCopied = numPixelsCopied;
	serverStatusOut.m_sendPixelDataArguments.m_numRemainingPixels = numRemainingPixels - numPixelsCopied;
	serverStatusOut.m_sendPixelDataArguments.m_startingPixelIndex = startPixelIndex;
	serverStatusOut.m_sendPixelDataArguments.m_imageWidth = width;
	serverStatusOut.m_sendPixelDataArguments.m_imageHeight = height;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processSaveWorldCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = false;
	BT_PROFILE("CMD_SAVE_WORLD");
	serverStatusOut.m_type = CMD_SAVE_WORLD_FAILED;

	///this is a very rudimentary way to save the state of the world, for scene authoring
	///many todo's, for example save the state of motor controllers etc.

	{
		//saveWorld(clientCmd.m_sdfArguments.m_sdfFileName);
		int constraintCount = 0;
		FILE* f = fopen(clientCmd.m_sdfArguments.m_sdfFileName, "w");
		if (f)
		{
			char line[1024];
			{
				sprintf(line, "import pybullet as p\n");
				int len = strlen(line);
				fwrite(line, len, 1, f);
			}
			{
				sprintf(line, "cin = p.connect(p.SHARED_MEMORY)\n");
				int len = strlen(line);
				fwrite(line, len, 1, f);
			}
			{
				sprintf(line, "if (cin < 0):\n");
				int len = strlen(line);
				fwrite(line, len, 1, f);
			}
			{
				sprintf(line, "    cin = p.connect(p.GUI)\n");
				int len = strlen(line);
				fwrite(line, len, 1, f);
			}

			//for each objects ...
			for (int i = 0; i < m_data->m_saveWorldBodyData.size(); i++)
			{
				SaveWorldObjectData& sd = m_data->m_saveWorldBodyData[i];

				for (int i = 0; i < sd.m_bodyUniqueIds.size(); i++)
				{
					{
						int bodyUniqueId = sd.m_bodyUniqueIds[i];
						InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);
						if (body)
						{
							if (body->m_multiBody)
							{
								btMultiBody* mb = body->m_multiBody;

								btTransform comTr = mb->getBaseWorldTransform();
								btTransform tr = comTr * body->m_rootLocalInertialFrame.inverse();

								if (strstr(sd.m_fileName.c_str(), ".urdf"))
								{
									sprintf(line, "objects = [p.loadURDF(\"%s\", %f,%f,%f,%f,%f,%f,%f)]\n", sd.m_fileName.c_str(),
											tr.getOrigin()[0], tr.getOrigin()[1], tr.getOrigin()[2],
											tr.getRotation()[0], tr.getRotation()[1], tr.getRotation()[2], tr.getRotation()[3]);
									int len = strlen(line);
									fwrite(line, len, 1, f);
								}

								if (strstr(sd.m_fileName.c_str(), ".sdf") && i == 0)
								{
									sprintf(line, "objects = p.loadSDF(\"%s\")\n", sd.m_fileName.c_str());
									int len = strlen(line);
									fwrite(line, len, 1, f);
								}
								if (strstr(sd.m_fileName.c_str(), ".xml") && i == 0)
								{
									sprintf(line, "objects = p.loadMJCF(\"%s\")\n", sd.m_fileName.c_str());
									int len = strlen(line);
									fwrite(line, len, 1, f);
								}

								if (strstr(sd.m_fileName.c_str(), ".sdf") || strstr(sd.m_fileName.c_str(), ".xml") || ((strstr(sd.m_fileName.c_str(), ".urdf")) && mb->getNumLinks()))
								{
									sprintf(line, "ob = objects[%d]\n", i);
									int len = strlen(line);
									fwrite(line, len, 1, f);
								}

								if (strstr(sd.m_fileName.c_str(), ".sdf") || strstr(sd.m_fileName.c_str(), ".xml"))
								{
									sprintf(line, "p.resetBasePositionAndOrientation(ob,[%f,%f,%f],[%f,%f,%f,%f])\n",
											comTr.getOrigin()[0], comTr.getOrigin()[1], comTr.getOrigin()[2],
											comTr.getRotation()[0], comTr.getRotation()[1], comTr.getRotation()[2], comTr.getRotation()[3]);
									int len = strlen(line);
									fwrite(line, len, 1, f);
								}

								if (mb->getNumLinks())
								{
									{
										sprintf(line, "jointPositions=[");
										int len = strlen(line);
										fwrite(line, len, 1, f);
									}

									for (int i = 0; i < mb->getNumLinks(); i++)
									{
										btScalar jointPos = mb->getJointPosMultiDof(i)[0];
										if (i < mb->getNumLinks() - 1)
										{
											sprintf(line, " %f,", jointPos);
											int len = strlen(line);
											fwrite(line, len, 1, f);
										}
										else
										{
											sprintf(line, " %f ", jointPos);
											int len = strlen(line);
											fwrite(line, len, 1, f);
										}
									}

									{
										sprintf(line, "]\nfor jointIndex in range (p.getNumJoints(ob)):\n\tp.resetJointState(ob,jointIndex,jointPositions[jointIndex])\n\n");
										int len = strlen(line);
										fwrite(line, len, 1, f);
									}
								}
							}
							else
							{
								//todo: btRigidBody/btSoftBody etc case
							}
						}
					}
				}

				//for URDF, load at origin, then reposition...

				struct SaveWorldObjectData
				{
					b3AlignedObjectArray<int> m_bodyUniqueIds;
					std::string m_fileName;
				};
			}

			//user constraints
			{
				for (int i = 0; i < m_data->m_userConstraints.size(); i++)
				{
					InteralUserConstraintData* ucptr = m_data->m_userConstraints.getAtIndex(i);
					b3UserConstraint& uc = ucptr->m_userConstraintData;

					int parentBodyIndex = uc.m_parentBodyIndex;
					int parentJointIndex = uc.m_parentJointIndex;
					int childBodyIndex = uc.m_childBodyIndex;
					int childJointIndex = uc.m_childJointIndex;
					btVector3 jointAxis(uc.m_jointAxis[0], uc.m_jointAxis[1], uc.m_jointAxis[2]);
					btVector3 pivotParent(uc.m_parentFrame[0], uc.m_parentFrame[1], uc.m_parentFrame[2]);
					btVector3 pivotChild(uc.m_childFrame[0], uc.m_childFrame[1], uc.m_childFrame[2]);
					btQuaternion ornFrameParent(uc.m_parentFrame[3], uc.m_parentFrame[4], uc.m_parentFrame[5], uc.m_parentFrame[6]);
					btQuaternion ornFrameChild(uc.m_childFrame[3], uc.m_childFrame[4], uc.m_childFrame[5], uc.m_childFrame[6]);
					{
						char jointTypeStr[1024] = "FIXED";
						bool hasKnownJointType = true;

						switch (uc.m_jointType)
						{
							case eRevoluteType:
							{
								sprintf(jointTypeStr, "p.JOINT_REVOLUTE");
								break;
							}
							case ePrismaticType:
							{
								sprintf(jointTypeStr, "p.JOINT_PRISMATIC");
								break;
							}
							case eSphericalType:
							{
								sprintf(jointTypeStr, "p.JOINT_SPHERICAL");
								break;
							}
							case ePlanarType:
							{
								sprintf(jointTypeStr, "p.JOINT_PLANAR");
								break;
							}
							case eFixedType:
							{
								sprintf(jointTypeStr, "p.JOINT_FIXED");
								break;
							}
							case ePoint2PointType:
							{
								sprintf(jointTypeStr, "p.JOINT_POINT2POINT");
								break;
							}
							case eGearType:
							{
								sprintf(jointTypeStr, "p.JOINT_GEAR");
								break;
							}
							default:
							{
								hasKnownJointType = false;
								b3Warning("unknown constraint type in SAVE_WORLD");
							}
						};
						if (hasKnownJointType)
						{
							{
								sprintf(line, "cid%d = p.createConstraint(%d,%d,%d,%d,%s,[%f,%f,%f],[%f,%f,%f],[%f,%f,%f],[%f,%f,%f,%f],[%f,%f,%f,%f])\n",
										constraintCount,
										parentBodyIndex,
										parentJointIndex,
										childBodyIndex,
										childJointIndex,
										jointTypeStr,
										jointAxis[0], jointAxis[1], jointAxis[2],
										pivotParent[0], pivotParent[1], pivotParent[2],
										pivotChild[0], pivotChild[1], pivotChild[2],
										ornFrameParent[0], ornFrameParent[1], ornFrameParent[2], ornFrameParent[3],
										ornFrameChild[0], ornFrameChild[1], ornFrameChild[2], ornFrameChild[3]);
								int len = strlen(line);
								fwrite(line, len, 1, f);
							}
							{
								sprintf(line, "p.changeConstraint(cid%d,maxForce=%f)\n", constraintCount, uc.m_maxAppliedForce);
								int len = strlen(line);
								fwrite(line, len, 1, f);
								constraintCount++;
							}
						}
					}
				}
			}

			{
				btVector3 grav = this->m_data->m_dynamicsWorld->getGravity();
				sprintf(line, "p.setGravity(%f,%f,%f)\n", grav[0], grav[1], grav[2]);
				int len = strlen(line);
				fwrite(line, len, 1, f);
			}

			{
				sprintf(line, "p.stepSimulation()\np.disconnect()\n");
				int len = strlen(line);
				fwrite(line, len, 1, f);
			}
			fclose(f);
		}

		serverStatusOut.m_type = CMD_SAVE_WORLD_COMPLETED;
		hasStatus = true;
	}

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCreateCollisionShapeCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	serverStatusOut.m_type = CMD_CREATE_COLLISION_SHAPE_FAILED;

	btMultiBodyWorldImporter* worldImporter = new btMultiBodyWorldImporter(m_data->m_dynamicsWorld);

	btCollisionShape* shape = 0;
	b3AlignedObjectArray<UrdfCollision> urdfCollisionObjects;

	btCompoundShape* compound = 0;

	if (clientCmd.m_createUserShapeArgs.m_numUserShapes > 1)
	{
		compound = worldImporter->createCompoundShape();
		compound->setMargin(m_data->m_defaultCollisionMargin);
	}
	for (int i = 0; i < clientCmd.m_createUserShapeArgs.m_numUserShapes; i++)
	{
		GLInstanceGraphicsShape* glmesh = 0;
		char pathPrefix[1024] = "";
		char relativeFileName[1024] = "";
		UrdfCollision urdfColObj;

		btTransform childTransform;
		childTransform.setIdentity();
		if (clientCmd.m_createUserShapeArgs.m_shapes[i].m_hasChildTransform)
		{
			childTransform.setOrigin(btVector3(clientCmd.m_createUserShapeArgs.m_shapes[i].m_childPosition[0],
											   clientCmd.m_createUserShapeArgs.m_shapes[i].m_childPosition[1],
											   clientCmd.m_createUserShapeArgs.m_shapes[i].m_childPosition[2]));
			childTransform.setRotation(btQuaternion(
				clientCmd.m_createUserShapeArgs.m_shapes[i].m_childOrientation[0],
				clientCmd.m_createUserShapeArgs.m_shapes[i].m_childOrientation[1],
				clientCmd.m_createUserShapeArgs.m_shapes[i].m_childOrientation[2],
				clientCmd.m_createUserShapeArgs.m_shapes[i].m_childOrientation[3]));
			if (compound == 0)
			{
				compound = worldImporter->createCompoundShape();
			}
		}

		urdfColObj.m_linkLocalFrame = childTransform;
		urdfColObj.m_sourceFileLocation = "memory";
		urdfColObj.m_name = "memory";
		urdfColObj.m_geometry.m_type = URDF_GEOM_UNKNOWN;

		switch (clientCmd.m_createUserShapeArgs.m_shapes[i].m_type)
		{
			case GEOM_SPHERE:
			{
				double radius = clientCmd.m_createUserShapeArgs.m_shapes[i].m_sphereRadius;
				shape = worldImporter->createSphereShape(radius);
				shape->setMargin(m_data->m_defaultCollisionMargin);
				if (compound)
				{
					compound->addChildShape(childTransform, shape);
				}
				urdfColObj.m_geometry.m_type = URDF_GEOM_SPHERE;
				urdfColObj.m_geometry.m_sphereRadius = radius;
				break;
			}
			case GEOM_BOX:
			{
				//double halfExtents[3] = clientCmd.m_createUserShapeArgs.m_shapes[i].m_sphereRadius;
				btVector3 halfExtents(
					clientCmd.m_createUserShapeArgs.m_shapes[i].m_boxHalfExtents[0],
					clientCmd.m_createUserShapeArgs.m_shapes[i].m_boxHalfExtents[1],
					clientCmd.m_createUserShapeArgs.m_shapes[i].m_boxHalfExtents[2]);
				shape = worldImporter->createBoxShape(halfExtents);
				shape->setMargin(m_data->m_defaultCollisionMargin);
				if (compound)
				{
					compound->addChildShape(childTransform, shape);
				}
				urdfColObj.m_geometry.m_type = URDF_GEOM_BOX;
				urdfColObj.m_geometry.m_boxSize = 2. * halfExtents;
				break;
			}
			case GEOM_CAPSULE:
			{
				shape = worldImporter->createCapsuleShapeZ(clientCmd.m_createUserShapeArgs.m_shapes[i].m_capsuleRadius,
														   clientCmd.m_createUserShapeArgs.m_shapes[i].m_capsuleHeight);
				shape->setMargin(m_data->m_defaultCollisionMargin);
				if (compound)
				{
					compound->addChildShape(childTransform, shape);
				}
				urdfColObj.m_geometry.m_type = URDF_GEOM_CAPSULE;
				urdfColObj.m_geometry.m_capsuleRadius = clientCmd.m_createUserShapeArgs.m_shapes[i].m_capsuleRadius;
				urdfColObj.m_geometry.m_capsuleHeight = clientCmd.m_createUserShapeArgs.m_shapes[i].m_capsuleHeight;

				break;
			}
			case GEOM_CYLINDER:
			{
				shape = worldImporter->createCylinderShapeZ(clientCmd.m_createUserShapeArgs.m_shapes[i].m_capsuleRadius,
															0.5 * clientCmd.m_createUserShapeArgs.m_shapes[i].m_capsuleHeight);
				shape->setMargin(m_data->m_defaultCollisionMargin);
				if (compound)
				{
					compound->addChildShape(childTransform, shape);
				}
				urdfColObj.m_geometry.m_type = URDF_GEOM_CYLINDER;
				urdfColObj.m_geometry.m_capsuleRadius = clientCmd.m_createUserShapeArgs.m_shapes[i].m_capsuleRadius;
				urdfColObj.m_geometry.m_capsuleHeight = clientCmd.m_createUserShapeArgs.m_shapes[i].m_capsuleHeight;

				break;
			}
			case GEOM_PLANE:
			{
				btVector3 planeNormal(clientCmd.m_createUserShapeArgs.m_shapes[i].m_planeNormal[0],
									  clientCmd.m_createUserShapeArgs.m_shapes[i].m_planeNormal[1],
									  clientCmd.m_createUserShapeArgs.m_shapes[i].m_planeNormal[2]);

				shape = worldImporter->createPlaneShape(planeNormal, 0);
				shape->setMargin(m_data->m_defaultCollisionMargin);
				if (compound)
				{
					compound->addChildShape(childTransform, shape);
				}
				urdfColObj.m_geometry.m_type = URDF_GEOM_PLANE;
				urdfColObj.m_geometry.m_planeNormal.setValue(
					clientCmd.m_createUserShapeArgs.m_shapes[i].m_planeNormal[0],
					clientCmd.m_createUserShapeArgs.m_shapes[i].m_planeNormal[1],
					clientCmd.m_createUserShapeArgs.m_shapes[i].m_planeNormal[2]);

				break;
			}
			case GEOM_MESH:
			{
				btVector3 meshScale(clientCmd.m_createUserShapeArgs.m_shapes[i].m_meshScale[0],
									clientCmd.m_createUserShapeArgs.m_shapes[i].m_meshScale[1],
									clientCmd.m_createUserShapeArgs.m_shapes[i].m_meshScale[2]);

				const std::string& urdf_path = "";

				std::string fileName = clientCmd.m_createUserShapeArgs.m_shapes[i].m_meshFileName;
				urdfColObj.m_geometry.m_type = URDF_GEOM_MESH;
				urdfColObj.m_geometry.m_meshFileName = fileName;

				urdfColObj.m_geometry.m_meshScale = meshScale;

				pathPrefix[0] = 0;
				if (b3ResourcePath::findResourcePath(fileName.c_str(), relativeFileName, 1024))
				{
					b3FileUtils::extractPath(relativeFileName, pathPrefix, 1024);
				}

				const std::string& error_message_prefix = "";
				std::string out_found_filename;
				int out_type;

				bool foundFile = findExistingMeshFile(pathPrefix, relativeFileName, error_message_prefix, &out_found_filename, &out_type);
				if (foundFile)
				{
					urdfColObj.m_geometry.m_meshFileType = out_type;

					if (out_type == UrdfGeometry::FILE_STL)
					{
						glmesh = LoadMeshFromSTL(relativeFileName);
					}
					if (out_type == UrdfGeometry::FILE_OBJ)
					{
						//create a convex hull for each shape, and store it in a btCompoundShape

						if (clientCmd.m_createUserShapeArgs.m_shapes[i].m_collisionFlags & GEOM_FORCE_CONCAVE_TRIMESH)
						{
							glmesh = LoadMeshFromObj(relativeFileName, pathPrefix);
						}
						else
						{
							std::vector<tinyobj::shape_t> shapes;
							std::string err = tinyobj::LoadObj(shapes, out_found_filename.c_str());

							//shape = createConvexHullFromShapes(shapes, collision->m_geometry.m_meshScale);
							//static btCollisionShape* createConvexHullFromShapes(std::vector<tinyobj::shape_t>& shapes, const btVector3& geomScale)
							B3_PROFILE("createConvexHullFromShapes");
							if (compound == 0)
							{
								compound = worldImporter->createCompoundShape();
							}
							compound->setMargin(m_data->m_defaultCollisionMargin);

							for (int s = 0; s < (int)shapes.size(); s++)
							{
								btConvexHullShape* convexHull = worldImporter->createConvexHullShape();
								convexHull->setMargin(m_data->m_defaultCollisionMargin);
								tinyobj::shape_t& shape = shapes[s];
								int faceCount = shape.mesh.indices.size();

								for (int f = 0; f < faceCount; f += 3)
								{
									btVector3 pt;
									pt.setValue(shape.mesh.positions[shape.mesh.indices[f] * 3 + 0],
												shape.mesh.positions[shape.mesh.indices[f] * 3 + 1],
												shape.mesh.positions[shape.mesh.indices[f] * 3 + 2]);

									convexHull->addPoint(pt * meshScale, false);

									pt.setValue(shape.mesh.positions[shape.mesh.indices[f + 1] * 3 + 0],
												shape.mesh.positions[shape.mesh.indices[f + 1] * 3 + 1],
												shape.mesh.positions[shape.mesh.indices[f + 1] * 3 + 2]);
									convexHull->addPoint(pt * meshScale, false);

									pt.setValue(shape.mesh.positions[shape.mesh.indices[f + 2] * 3 + 0],
												shape.mesh.positions[shape.mesh.indices[f + 2] * 3 + 1],
												shape.mesh.positions[shape.mesh.indices[f + 2] * 3 + 2]);
									convexHull->addPoint(pt * meshScale, false);
								}

								convexHull->recalcLocalAabb();
								convexHull->optimizeConvexHull();
								compound->addChildShape(childTransform, convexHull);
							}
						}
					}
				}
				break;
			}
			default:
			{
			}
		}
		if (urdfColObj.m_geometry.m_type != URDF_GEOM_UNKNOWN)
		{
			urdfCollisionObjects.push_back(urdfColObj);
		}

		if (glmesh)
		{
			btVector3 meshScale(clientCmd.m_createUserShapeArgs.m_shapes[i].m_meshScale[0],
								clientCmd.m_createUserShapeArgs.m_shapes[i].m_meshScale[1],
								clientCmd.m_createUserShapeArgs.m_shapes[i].m_meshScale[2]);

			if (!glmesh || glmesh->m_numvertices <= 0)
			{
				b3Warning("%s: cannot extract mesh from '%s'\n", pathPrefix, relativeFileName);
				delete glmesh;
			}
			else
			{
				btAlignedObjectArray<btVector3> convertedVerts;
				convertedVerts.reserve(glmesh->m_numvertices);

				for (int v = 0; v < glmesh->m_numvertices; v++)
				{
					convertedVerts.push_back(btVector3(
						glmesh->m_vertices->at(v).xyzw[0] * meshScale[0],
						glmesh->m_vertices->at(v).xyzw[1] * meshScale[1],
						glmesh->m_vertices->at(v).xyzw[2] * meshScale[2]));
				}

				if (clientCmd.m_createUserShapeArgs.m_shapes[i].m_collisionFlags & GEOM_FORCE_CONCAVE_TRIMESH)
				{
					BT_PROFILE("convert trimesh");
					btTriangleMesh* meshInterface = new btTriangleMesh();
					this->m_data->m_meshInterfaces.push_back(meshInterface);
					{
						BT_PROFILE("convert vertices");

						for (int i = 0; i < glmesh->m_numIndices / 3; i++)
						{
							const btVector3& v0 = convertedVerts[glmesh->m_indices->at(i * 3)];
							const btVector3& v1 = convertedVerts[glmesh->m_indices->at(i * 3 + 1)];
							const btVector3& v2 = convertedVerts[glmesh->m_indices->at(i * 3 + 2)];
							meshInterface->addTriangle(v0, v1, v2);
						}
					}

					{
						BT_PROFILE("create btBvhTriangleMeshShape");
						btBvhTriangleMeshShape* trimesh = new btBvhTriangleMeshShape(meshInterface, true, true);
						m_data->m_collisionShapes.push_back(trimesh);

						if (clientCmd.m_createUserShapeArgs.m_shapes[i].m_collisionFlags & GEOM_CONCAVE_INTERNAL_EDGE)
						{
							btTriangleInfoMap* triangleInfoMap = new btTriangleInfoMap();
							btGenerateInternalEdgeInfo(trimesh, triangleInfoMap);
						}
						//trimesh->setLocalScaling(collision->m_geometry.m_meshScale);
						shape = trimesh;
						if (compound)
						{
							compound->addChildShape(childTransform, shape);
							shape->setMargin(m_data->m_defaultCollisionMargin);
						}
					}
					delete glmesh;
				}
				else
				{
					//convex mesh

					if (compound == 0)
					{
						compound = worldImporter->createCompoundShape();
					}
					compound->setMargin(m_data->m_defaultCollisionMargin);

					{
						btConvexHullShape* convexHull = worldImporter->createConvexHullShape();
						convexHull->setMargin(m_data->m_defaultCollisionMargin);

						for (int v = 0; v < convertedVerts.size(); v++)
						{
							btVector3 pt = convertedVerts[v];
							convexHull->addPoint(pt, false);
						}

						convexHull->recalcLocalAabb();
						convexHull->optimizeConvexHull();
						compound->addChildShape(childTransform, convexHull);
					}
				}
			}
		}
	}
	if (compound && compound->getNumChildShapes())
	{
		shape = compound;
	}

	if (shape)
	{
		int collisionShapeUid = m_data->m_userCollisionShapeHandles.allocHandle();
		InternalCollisionShapeHandle* handle = m_data->m_userCollisionShapeHandles.getHandle(collisionShapeUid);
		handle->m_collisionShape = shape;
		for (int i = 0; i < urdfCollisionObjects.size(); i++)
		{
			handle->m_urdfCollisionObjects.push_back(urdfCollisionObjects[i]);
		}
		serverStatusOut.m_createUserShapeResultArgs.m_userShapeUniqueId = collisionShapeUid;
		m_data->m_worldImporters.push_back(worldImporter);
		serverStatusOut.m_type = CMD_CREATE_COLLISION_SHAPE_COMPLETED;
	}
	else
	{
		delete worldImporter;
	}

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCreateVisualShapeCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	serverStatusOut.m_type = CMD_CREATE_VISUAL_SHAPE_FAILED;
	double globalScaling = 1.f;
	int flags = 0;
	BulletURDFImporter u2b(m_data->m_guiHelper, m_data->m_pluginManager.getRenderInterface(), globalScaling, flags);
	u2b.setEnableTinyRenderer(m_data->m_enableTinyRenderer);
	btTransform localInertiaFrame;
	localInertiaFrame.setIdentity();

	const char* pathPrefix = "";
	int visualShapeUniqueId = -1;

	UrdfVisual visualShape;
	for (int userShapeIndex = 0; userShapeIndex < clientCmd.m_createUserShapeArgs.m_numUserShapes; userShapeIndex++)
	{
		btTransform childTrans;
		childTrans.setIdentity();
		visualShape.m_geometry.m_type = (UrdfGeomTypes)clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_type;
		char relativeFileName[1024];
		char pathPrefix[1024];
		pathPrefix[0] = 0;

		const b3CreateUserShapeData& visShape = clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex];

		switch (visualShape.m_geometry.m_type)
		{
			case URDF_GEOM_CYLINDER:
			{
				visualShape.m_geometry.m_capsuleHeight = visShape.m_capsuleHeight;
				visualShape.m_geometry.m_capsuleRadius = visShape.m_capsuleRadius;
				break;
			}
			case URDF_GEOM_BOX:
			{
				visualShape.m_geometry.m_boxSize.setValue(2. * visShape.m_boxHalfExtents[0],
														  2. * visShape.m_boxHalfExtents[1],
														  2. * visShape.m_boxHalfExtents[2]);
				break;
			}
			case URDF_GEOM_SPHERE:
			{
				visualShape.m_geometry.m_sphereRadius = visShape.m_sphereRadius;
				break;
			}
			case URDF_GEOM_CAPSULE:
			{
				visualShape.m_geometry.m_hasFromTo = visShape.m_hasFromTo;
				if (visualShape.m_geometry.m_hasFromTo)
				{
					visualShape.m_geometry.m_capsuleFrom.setValue(visShape.m_capsuleFrom[0],
																  visShape.m_capsuleFrom[1],
																  visShape.m_capsuleFrom[2]);
					visualShape.m_geometry.m_capsuleTo.setValue(visShape.m_capsuleTo[0],
																visShape.m_capsuleTo[1],
																visShape.m_capsuleTo[2]);
				}
				else
				{
					visualShape.m_geometry.m_capsuleHeight = visShape.m_capsuleHeight;
					visualShape.m_geometry.m_capsuleRadius = visShape.m_capsuleRadius;
				}
				break;
			}
			case URDF_GEOM_MESH:
			{
				std::string fileName = clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_meshFileName;
				const std::string& error_message_prefix = "";
				std::string out_found_filename;
				int out_type;
				if (b3ResourcePath::findResourcePath(fileName.c_str(), relativeFileName, 1024))
				{
					b3FileUtils::extractPath(relativeFileName, pathPrefix, 1024);
				}

				bool foundFile = findExistingMeshFile(pathPrefix, relativeFileName, error_message_prefix, &out_found_filename, &out_type);
				visualShape.m_geometry.m_meshFileType = out_type;
				visualShape.m_geometry.m_meshFileName = fileName;

				visualShape.m_geometry.m_meshScale.setValue(clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_meshScale[0],
															clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_meshScale[1],
															clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_meshScale[2]);
				break;
			}

			default:
			{
			}
		};
		visualShape.m_name = "in_memory";
		visualShape.m_materialName = "";
		visualShape.m_sourceFileLocation = "in_memory_unknown_line";
		visualShape.m_linkLocalFrame.setIdentity();
		visualShape.m_geometry.m_hasLocalMaterial = false;

		bool hasRGBA = (clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_visualFlags & GEOM_VISUAL_HAS_RGBA_COLOR) != 0;
		;
		bool hasSpecular = (clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_visualFlags & GEOM_VISUAL_HAS_SPECULAR_COLOR) != 0;
		;
		visualShape.m_geometry.m_hasLocalMaterial = hasRGBA | hasSpecular;
		if (visualShape.m_geometry.m_hasLocalMaterial)
		{
			if (hasRGBA)
			{
				visualShape.m_geometry.m_localMaterial.m_matColor.m_rgbaColor.setValue(
					clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_rgbaColor[0],
					clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_rgbaColor[1],
					clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_rgbaColor[2],
					clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_rgbaColor[3]);
			}
			else
			{
			}
			if (hasSpecular)
			{
				visualShape.m_geometry.m_localMaterial.m_matColor.m_specularColor.setValue(
					clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_specularColor[0],
					clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_specularColor[1],
					clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_specularColor[2]);
			}
			else
			{
				visualShape.m_geometry.m_localMaterial.m_matColor.m_specularColor.setValue(0.4, 0.4, 0.4);
			}
		}

		if (clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_hasChildTransform != 0)
		{
			childTrans.setOrigin(btVector3(clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_childPosition[0],
										   clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_childPosition[1],
										   clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_childPosition[2]));
			childTrans.setRotation(btQuaternion(
				clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_childOrientation[0],
				clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_childOrientation[1],
				clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_childOrientation[2],
				clientCmd.m_createUserShapeArgs.m_shapes[userShapeIndex].m_childOrientation[3]));
		}

		if (visualShapeUniqueId < 0)
		{
			visualShapeUniqueId = m_data->m_userVisualShapeHandles.allocHandle();
		}
		InternalVisualShapeHandle* visualHandle = m_data->m_userVisualShapeHandles.getHandle(visualShapeUniqueId);
		visualHandle->m_OpenGLGraphicsIndex = -1;
		visualHandle->m_tinyRendererVisualShapeIndex = -1;
		//tinyrenderer doesn't separate shape versus instance, so create it when creating the multibody instance
		//store needed info for tinyrenderer

		visualShape.m_linkLocalFrame = childTrans;
		visualHandle->m_visualShapes.push_back(visualShape);
		visualHandle->m_pathPrefixes.push_back(pathPrefix[0] ? pathPrefix : "");

		serverStatusOut.m_createUserShapeResultArgs.m_userShapeUniqueId = visualShapeUniqueId;
		serverStatusOut.m_type = CMD_CREATE_VISUAL_SHAPE_COMPLETED;
	}

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCustomCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CUSTOM_COMMAND_FAILED;
	serverCmd.m_customCommandResultArgs.m_pluginUniqueId = -1;

	if (clientCmd.m_updateFlags & CMD_CUSTOM_COMMAND_LOAD_PLUGIN)
	{
		//pluginPath could be registered or load from disk
		const char* postFix = "";
		if (clientCmd.m_updateFlags & CMD_CUSTOM_COMMAND_LOAD_PLUGIN_POSTFIX)
		{
			postFix = clientCmd.m_customCommandArgs.m_postFix;
		}

		int pluginUniqueId = m_data->m_pluginManager.loadPlugin(clientCmd.m_customCommandArgs.m_pluginPath, postFix);
		if (pluginUniqueId >= 0)
		{
			serverCmd.m_customCommandResultArgs.m_pluginUniqueId = pluginUniqueId;
			serverCmd.m_type = CMD_CUSTOM_COMMAND_COMPLETED;
		}
	}
	if (clientCmd.m_updateFlags & CMD_CUSTOM_COMMAND_UNLOAD_PLUGIN)
	{
		m_data->m_pluginManager.unloadPlugin(clientCmd.m_customCommandArgs.m_pluginUniqueId);
		serverCmd.m_type = CMD_CUSTOM_COMMAND_COMPLETED;
	}
	if (clientCmd.m_updateFlags & CMD_CUSTOM_COMMAND_EXECUTE_PLUGIN_COMMAND)
	{
		int result = m_data->m_pluginManager.executePluginCommand(clientCmd.m_customCommandArgs.m_pluginUniqueId, &clientCmd.m_customCommandArgs.m_arguments);
		serverCmd.m_customCommandResultArgs.m_executeCommandResult = result;
		serverCmd.m_type = CMD_CUSTOM_COMMAND_COMPLETED;
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processUserDebugDrawCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_USER_DEBUG_DRAW");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_USER_DEBUG_DRAW_FAILED;

	int trackingVisualShapeIndex = -1;

	if (clientCmd.m_userDebugDrawArgs.m_parentObjectUniqueId >= 0)
	{
		InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(clientCmd.m_userDebugDrawArgs.m_parentObjectUniqueId);
		if (bodyHandle)
		{
			int linkIndex = -1;

			if (bodyHandle->m_multiBody)
			{
				int linkIndex = clientCmd.m_userDebugDrawArgs.m_parentLinkIndex;
				if (linkIndex == -1)
				{
					if (bodyHandle->m_multiBody->getBaseCollider())
					{
						trackingVisualShapeIndex = bodyHandle->m_multiBody->getBaseCollider()->getUserIndex();
					}
				}
				else
				{
					if (linkIndex >= 0 && linkIndex < bodyHandle->m_multiBody->getNumLinks())
					{
						if (bodyHandle->m_multiBody->getLink(linkIndex).m_collider)
						{
							trackingVisualShapeIndex = bodyHandle->m_multiBody->getLink(linkIndex).m_collider->getUserIndex();
						}
					}
				}
			}
			if (bodyHandle->m_rigidBody)
			{
				trackingVisualShapeIndex = bodyHandle->m_rigidBody->getUserIndex();
			}
		}
	}

	if (clientCmd.m_updateFlags & USER_DEBUG_ADD_PARAMETER)
	{
		int uid = m_data->m_guiHelper->addUserDebugParameter(
			clientCmd.m_userDebugDrawArgs.m_text,
			clientCmd.m_userDebugDrawArgs.m_rangeMin,
			clientCmd.m_userDebugDrawArgs.m_rangeMax,
			clientCmd.m_userDebugDrawArgs.m_startValue);
		serverCmd.m_userDebugDrawArgs.m_debugItemUniqueId = uid;
		serverCmd.m_type = CMD_USER_DEBUG_DRAW_COMPLETED;
	}
	if (clientCmd.m_updateFlags & USER_DEBUG_READ_PARAMETER)
	{
		int ok = m_data->m_guiHelper->readUserDebugParameter(
			clientCmd.m_userDebugDrawArgs.m_itemUniqueId,
			&serverCmd.m_userDebugDrawArgs.m_parameterValue);
		if (ok)
		{
			serverCmd.m_type = CMD_USER_DEBUG_DRAW_PARAMETER_COMPLETED;
		}
	}
	if ((clientCmd.m_updateFlags & USER_DEBUG_SET_CUSTOM_OBJECT_COLOR) || (clientCmd.m_updateFlags & USER_DEBUG_REMOVE_CUSTOM_OBJECT_COLOR))
	{
		int bodyUniqueId = clientCmd.m_userDebugDrawArgs.m_objectUniqueId;
		InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);
		if (body)
		{
			btCollisionObject* destColObj = 0;

			if (body->m_multiBody)
			{
				if (clientCmd.m_userDebugDrawArgs.m_linkIndex == -1)
				{
					destColObj = body->m_multiBody->getBaseCollider();
				}
				else
				{
					if (clientCmd.m_userDebugDrawArgs.m_linkIndex >= 0 && clientCmd.m_userDebugDrawArgs.m_linkIndex < body->m_multiBody->getNumLinks())
					{
						destColObj = body->m_multiBody->getLink(clientCmd.m_userDebugDrawArgs.m_linkIndex).m_collider;
					}
				}
			}
			if (body->m_rigidBody)
			{
				destColObj = body->m_rigidBody;
			}

			if (destColObj)
			{
				if (clientCmd.m_updateFlags & USER_DEBUG_REMOVE_CUSTOM_OBJECT_COLOR)
				{
					destColObj->removeCustomDebugColor();
					serverCmd.m_type = CMD_USER_DEBUG_DRAW_COMPLETED;
				}
				if (clientCmd.m_updateFlags & USER_DEBUG_SET_CUSTOM_OBJECT_COLOR)
				{
					btVector3 objectColorRGB;
					objectColorRGB.setValue(clientCmd.m_userDebugDrawArgs.m_objectDebugColorRGB[0],
											clientCmd.m_userDebugDrawArgs.m_objectDebugColorRGB[1],
											clientCmd.m_userDebugDrawArgs.m_objectDebugColorRGB[2]);
					destColObj->setCustomDebugColor(objectColorRGB);
					serverCmd.m_type = CMD_USER_DEBUG_DRAW_COMPLETED;
				}
			}
		}
	}

	if (clientCmd.m_updateFlags & USER_DEBUG_HAS_TEXT)
	{
		//addUserDebugText3D( const double orientation[4], const double	textColorRGB[3], double size, double lifeTime, int trackingObjectUniqueId, int optionFlags){return -1;}

		int optionFlags = clientCmd.m_userDebugDrawArgs.m_optionFlags;

		if ((clientCmd.m_updateFlags & USER_DEBUG_HAS_TEXT_ORIENTATION) == 0)
		{
			optionFlags |= DEB_DEBUG_TEXT_ALWAYS_FACE_CAMERA;
		}

		int replaceItemUniqueId = -1;
		if ((clientCmd.m_updateFlags & USER_DEBUG_HAS_REPLACE_ITEM_UNIQUE_ID) != 0)
		{
			replaceItemUniqueId = clientCmd.m_userDebugDrawArgs.m_replaceItemUniqueId;
		}

		int uid = m_data->m_guiHelper->addUserDebugText3D(clientCmd.m_userDebugDrawArgs.m_text,
														  clientCmd.m_userDebugDrawArgs.m_textPositionXYZ,
														  clientCmd.m_userDebugDrawArgs.m_textOrientation,
														  clientCmd.m_userDebugDrawArgs.m_textColorRGB,
														  clientCmd.m_userDebugDrawArgs.m_textSize,
														  clientCmd.m_userDebugDrawArgs.m_lifeTime,
														  trackingVisualShapeIndex,
														  optionFlags,
														  replaceItemUniqueId);

		if (uid >= 0)
		{
			serverCmd.m_userDebugDrawArgs.m_debugItemUniqueId = uid;
			serverCmd.m_type = CMD_USER_DEBUG_DRAW_COMPLETED;
		}
	}

	if (clientCmd.m_updateFlags & USER_DEBUG_HAS_LINE)
	{
		int replaceItemUid = -1;
		if (clientCmd.m_updateFlags & USER_DEBUG_HAS_REPLACE_ITEM_UNIQUE_ID)
		{
			replaceItemUid = clientCmd.m_userDebugDrawArgs.m_replaceItemUniqueId;
		}

		int uid = m_data->m_guiHelper->addUserDebugLine(
			clientCmd.m_userDebugDrawArgs.m_debugLineFromXYZ,
			clientCmd.m_userDebugDrawArgs.m_debugLineToXYZ,
			clientCmd.m_userDebugDrawArgs.m_debugLineColorRGB,
			clientCmd.m_userDebugDrawArgs.m_lineWidth,
			clientCmd.m_userDebugDrawArgs.m_lifeTime,
			trackingVisualShapeIndex,
			replaceItemUid);

		if (uid >= 0)
		{
			serverCmd.m_userDebugDrawArgs.m_debugItemUniqueId = uid;
			serverCmd.m_type = CMD_USER_DEBUG_DRAW_COMPLETED;
		}
	}

	if (clientCmd.m_updateFlags & USER_DEBUG_REMOVE_ALL)
	{
		m_data->m_guiHelper->removeAllUserDebugItems();
		serverCmd.m_type = CMD_USER_DEBUG_DRAW_COMPLETED;
	}
	if (clientCmd.m_updateFlags & USER_DEBUG_REMOVE_ONE_ITEM)
	{
		m_data->m_guiHelper->removeUserDebugItem(clientCmd.m_userDebugDrawArgs.m_itemUniqueId);
		serverCmd.m_type = CMD_USER_DEBUG_DRAW_COMPLETED;
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processSetVRCameraStateCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_SET_VR_CAMERA_STATE");

	if (clientCmd.m_updateFlags & VR_CAMERA_ROOT_POSITION)
	{
		gVRTeleportPos1[0] = clientCmd.m_vrCameraStateArguments.m_rootPosition[0];
		gVRTeleportPos1[1] = clientCmd.m_vrCameraStateArguments.m_rootPosition[1];
		gVRTeleportPos1[2] = clientCmd.m_vrCameraStateArguments.m_rootPosition[2];
	}
	if (clientCmd.m_updateFlags & VR_CAMERA_ROOT_ORIENTATION)
	{
		gVRTeleportOrn[0] = clientCmd.m_vrCameraStateArguments.m_rootOrientation[0];
		gVRTeleportOrn[1] = clientCmd.m_vrCameraStateArguments.m_rootOrientation[1];
		gVRTeleportOrn[2] = clientCmd.m_vrCameraStateArguments.m_rootOrientation[2];
		gVRTeleportOrn[3] = clientCmd.m_vrCameraStateArguments.m_rootOrientation[3];
	}

	if (clientCmd.m_updateFlags & VR_CAMERA_ROOT_TRACKING_OBJECT)
	{
		gVRTrackingObjectUniqueId = clientCmd.m_vrCameraStateArguments.m_trackingObjectUniqueId;
	}

	if (clientCmd.m_updateFlags & VR_CAMERA_FLAG)
	{
		gVRTrackingObjectFlag = clientCmd.m_vrCameraStateArguments.m_trackingObjectFlag;
	}

	serverStatusOut.m_type = CMD_CLIENT_COMMAND_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestVREventsCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	//BT_PROFILE("CMD_REQUEST_VR_EVENTS_DATA");
	serverStatusOut.m_sendVREvents.m_numVRControllerEvents = 0;

	for (int i = 0; i < MAX_VR_CONTROLLERS; i++)
	{
		b3VRControllerEvent& event = m_data->m_vrControllerEvents.m_vrEvents[i];

		if (clientCmd.m_updateFlags & event.m_deviceType)
		{
			if (event.m_numButtonEvents + event.m_numMoveEvents)
			{
				serverStatusOut.m_sendVREvents.m_controllerEvents[serverStatusOut.m_sendVREvents.m_numVRControllerEvents++] = event;
				event.m_numButtonEvents = 0;
				event.m_numMoveEvents = 0;
				for (int b = 0; b < MAX_VR_BUTTONS; b++)
				{
					event.m_buttons[b] = 0;
				}
			}
		}
	}
	serverStatusOut.m_type = CMD_REQUEST_VR_EVENTS_DATA_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestMouseEventsCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	serverStatusOut.m_sendMouseEvents.m_numMouseEvents = m_data->m_mouseEvents.size();
	if (serverStatusOut.m_sendMouseEvents.m_numMouseEvents > MAX_MOUSE_EVENTS)
	{
		serverStatusOut.m_sendMouseEvents.m_numMouseEvents = MAX_MOUSE_EVENTS;
	}
	for (int i = 0; i < serverStatusOut.m_sendMouseEvents.m_numMouseEvents; i++)
	{
		serverStatusOut.m_sendMouseEvents.m_mouseEvents[i] = m_data->m_mouseEvents[i];
	}

	m_data->m_mouseEvents.resize(0);
	serverStatusOut.m_type = CMD_REQUEST_MOUSE_EVENTS_DATA_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestKeyboardEventsCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	//BT_PROFILE("CMD_REQUEST_KEYBOARD_EVENTS_DATA");
	bool hasStatus = true;
	serverStatusOut.m_sendKeyboardEvents.m_numKeyboardEvents = m_data->m_keyboardEvents.size();
	if (serverStatusOut.m_sendKeyboardEvents.m_numKeyboardEvents > MAX_KEYBOARD_EVENTS)
	{
		serverStatusOut.m_sendKeyboardEvents.m_numKeyboardEvents = MAX_KEYBOARD_EVENTS;
	}
	for (int i = 0; i < serverStatusOut.m_sendKeyboardEvents.m_numKeyboardEvents; i++)
	{
		serverStatusOut.m_sendKeyboardEvents.m_keyboardEvents[i] = m_data->m_keyboardEvents[i];
	}

	btAlignedObjectArray<b3KeyboardEvent> events;

	//remove out-of-date events
	for (int i = 0; i < m_data->m_keyboardEvents.size(); i++)
	{
		b3KeyboardEvent event = m_data->m_keyboardEvents[i];
		if (event.m_keyState & eButtonIsDown)
		{
			event.m_keyState = eButtonIsDown;
			events.push_back(event);
		}
	}
	m_data->m_keyboardEvents.resize(events.size());
	for (int i = 0; i < events.size(); i++)
	{
		m_data->m_keyboardEvents[i] = events[i];
	}

	serverStatusOut.m_type = CMD_REQUEST_KEYBOARD_EVENTS_DATA_COMPLETED;
	return hasStatus;
}

#if __cplusplus >= 201103L
#include <atomic>

struct CastSyncInfo
{
	std::atomic<int> m_nextTaskNumber;

	CastSyncInfo() : m_nextTaskNumber(0) {}

	inline int getNextTask()
	{
		return m_nextTaskNumber++;
	}
};
#else   // __cplusplus >= 201103L
struct CastSyncInfo
{
	volatile int m_nextTaskNumber;
	btSpinMutex m_taskLock;

	CastSyncInfo() : m_nextTaskNumber(0) {}

	inline int getNextTask()
	{
		m_taskLock.lock();
		const int taskNr = m_nextTaskNumber++;
		m_taskLock.unlock();
		return taskNr;
	}
};
#endif  // __cplusplus >= 201103L

struct BatchRayCaster
{
	b3ThreadPool* m_threadPool;
	CastSyncInfo* m_syncInfo;
	const btCollisionWorld* m_world;
	const b3RayData* m_rayInputBuffer;
	b3RayHitInfo* m_hitInfoOutputBuffer;
	int m_numRays;

	BatchRayCaster(b3ThreadPool* threadPool, const btCollisionWorld* world, const b3RayData* rayInputBuffer, b3RayHitInfo* hitInfoOutputBuffer, int numRays)
		: m_threadPool(threadPool), m_world(world), m_rayInputBuffer(rayInputBuffer), m_hitInfoOutputBuffer(hitInfoOutputBuffer), m_numRays(numRays)
	{
		m_syncInfo = new CastSyncInfo;
	}

	~BatchRayCaster()
	{
		delete m_syncInfo;
	}

	void castRays(int numWorkers)
	{
#if BT_THREADSAFE
		if (numWorkers <= 1)
		{
			castSequentially();
		}
		else
		{
			{
				BT_PROFILE("BatchRayCaster_startingWorkerThreads");
				int numTasks = btMin(m_threadPool->numWorkers(), numWorkers - 1);
				for (int i = 0; i < numTasks; i++)
				{
					m_threadPool->runTask(i, BatchRayCaster::rayCastWorker, this);
				}
			}
			rayCastWorker(this);
			m_threadPool->waitForAllTasks();
		}
#else   // BT_THREADSAFE
		castSequentially();
#endif  // BT_THREADSAFE
	}

	static void rayCastWorker(void* arg)
	{
		BT_PROFILE("BatchRayCaster_raycastWorker");
		BatchRayCaster* const obj = (BatchRayCaster*)arg;
		const int numRays = obj->m_numRays;
		int taskNr;
		while (true)
		{
			{
				BT_PROFILE("CastSyncInfo_getNextTask");
				taskNr = obj->m_syncInfo->getNextTask();
			}
			if (taskNr >= numRays)
				return;
			obj->processRay(taskNr);
		}
	}

	void castSequentially()
	{
		for (int i = 0; i < m_numRays; i++)
		{
			processRay(i);
		}
	}

	void processRay(int ray)
	{
		BT_PROFILE("BatchRayCaster_processRay");
		const double* from = m_rayInputBuffer[ray].m_rayFromPosition;
		const double* to = m_rayInputBuffer[ray].m_rayToPosition;
		btVector3 rayFromWorld(from[0], from[1], from[2]);
		btVector3 rayToWorld(to[0], to[1], to[2]);

		btCollisionWorld::ClosestRayResultCallback rayResultCallback(rayFromWorld, rayToWorld);
		rayResultCallback.m_flags |= btTriangleRaycastCallback::kF_UseGjkConvexCastRaytest;

		m_world->rayTest(rayFromWorld, rayToWorld, rayResultCallback);

		b3RayHitInfo& hit = m_hitInfoOutputBuffer[ray];
		if (rayResultCallback.hasHit())
		{
			hit.m_hitFraction = rayResultCallback.m_closestHitFraction;

			int objectUniqueId = -1;
			int linkIndex = -1;

			const btRigidBody* body = btRigidBody::upcast(rayResultCallback.m_collisionObject);
			if (body)
			{
				objectUniqueId = rayResultCallback.m_collisionObject->getUserIndex2();
			}
			else
			{
				const btMultiBodyLinkCollider* mblB = btMultiBodyLinkCollider::upcast(rayResultCallback.m_collisionObject);
				if (mblB && mblB->m_multiBody)
				{
					linkIndex = mblB->m_link;
					objectUniqueId = mblB->m_multiBody->getUserIndex2();
				}
			}

			hit.m_hitObjectUniqueId = objectUniqueId;
			hit.m_hitObjectLinkIndex = linkIndex;

			hit.m_hitPositionWorld[0] = rayResultCallback.m_hitPointWorld[0];
			hit.m_hitPositionWorld[1] = rayResultCallback.m_hitPointWorld[1];
			hit.m_hitPositionWorld[2] = rayResultCallback.m_hitPointWorld[2];
			hit.m_hitNormalWorld[0] = rayResultCallback.m_hitNormalWorld[0];
			hit.m_hitNormalWorld[1] = rayResultCallback.m_hitNormalWorld[1];
			hit.m_hitNormalWorld[2] = rayResultCallback.m_hitNormalWorld[2];
		}
		else
		{
			hit.m_hitFraction = 1;
			hit.m_hitObjectUniqueId = -1;
			hit.m_hitObjectLinkIndex = -1;
			hit.m_hitPositionWorld[0] = 0;
			hit.m_hitPositionWorld[1] = 0;
			hit.m_hitPositionWorld[2] = 0;
			hit.m_hitNormalWorld[0] = 0;
			hit.m_hitNormalWorld[1] = 0;
			hit.m_hitNormalWorld[2] = 0;
		}
	}
};

void PhysicsServerCommandProcessor::createThreadPool()
{
#ifdef BT_THREADSAFE
	if (m_data->m_threadPool == 0)
	{
		m_data->m_threadPool = new b3ThreadPool("PhysicsServerCommandProcessorThreadPool");
	}
#endif  //BT_THREADSAFE
}

bool PhysicsServerCommandProcessor::processRequestRaycastIntersectionsCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_REQUEST_RAY_CAST_INTERSECTIONS");
	serverStatusOut.m_raycastHits.m_numRaycastHits = 0;

	const int numCommandRays = clientCmd.m_requestRaycastIntersections.m_numCommandRays;
	const int numStreamingRays = clientCmd.m_requestRaycastIntersections.m_numStreamingRays;
	const int totalRays = numCommandRays + numStreamingRays;
	int numThreads = clientCmd.m_requestRaycastIntersections.m_numThreads;
	if (numThreads == 0)
	{
		// When 0 is specified, Bullet can decide how many threads to use.
		// About 16 rays per thread seems to work reasonably well.
		numThreads = btMax(1, totalRays / 16);
	}
	if (numThreads > 1)
	{
		createThreadPool();
	}

	btAlignedObjectArray<b3RayData> rays;
	rays.resize(totalRays);
	if (numCommandRays)
	{
		memcpy(&rays[0], &clientCmd.m_requestRaycastIntersections.m_fromToRays[0], numCommandRays * sizeof(b3RayData));
	}
	if (numStreamingRays)
	{
		memcpy(&rays[numCommandRays], bufferServerToClient, numStreamingRays * sizeof(b3RayData));
	}

	BatchRayCaster batchRayCaster(m_data->m_threadPool, m_data->m_dynamicsWorld, &rays[0], (b3RayHitInfo*)bufferServerToClient, totalRays);
	batchRayCaster.castRays(numThreads);

	serverStatusOut.m_raycastHits.m_numRaycastHits = totalRays;
	serverStatusOut.m_type = CMD_REQUEST_RAY_CAST_INTERSECTIONS_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestDebugLinesCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_REQUEST_DEBUG_LINES");
	int curFlags = m_data->m_remoteDebugDrawer->getDebugMode();

	int debugMode = clientCmd.m_requestDebugLinesArguments.m_debugMode;  //clientCmd.btIDebugDraw::DBG_DrawWireframe|btIDebugDraw::DBG_DrawAabb;
	int startingLineIndex = clientCmd.m_requestDebugLinesArguments.m_startingLineIndex;
	if (startingLineIndex < 0)
	{
		b3Warning("startingLineIndex should be non-negative");
		startingLineIndex = 0;
	}

	if (clientCmd.m_requestDebugLinesArguments.m_startingLineIndex == 0)
	{
		m_data->m_remoteDebugDrawer->m_lines2.resize(0);
		//|btIDebugDraw::DBG_DrawAabb|
		//	btIDebugDraw::DBG_DrawConstraints |btIDebugDraw::DBG_DrawConstraintLimits ;
		m_data->m_remoteDebugDrawer->setDebugMode(debugMode);
		btIDebugDraw* oldDebugDrawer = m_data->m_dynamicsWorld->getDebugDrawer();
		m_data->m_dynamicsWorld->setDebugDrawer(m_data->m_remoteDebugDrawer);
		m_data->m_dynamicsWorld->debugDrawWorld();
		m_data->m_dynamicsWorld->setDebugDrawer(oldDebugDrawer);
		m_data->m_remoteDebugDrawer->setDebugMode(curFlags);
	}

	//9 floats per line: 3 floats for 'from', 3 floats for 'to' and 3 floats for 'color'
	int bytesPerLine = (sizeof(float) * 9);
	int maxNumLines = bufferSizeInBytes / bytesPerLine - 1;
	if (startingLineIndex > m_data->m_remoteDebugDrawer->m_lines2.size())
	{
		b3Warning("m_startingLineIndex exceeds total number of debug lines");
		startingLineIndex = m_data->m_remoteDebugDrawer->m_lines2.size();
	}

	int numLines = btMin(maxNumLines, m_data->m_remoteDebugDrawer->m_lines2.size() - startingLineIndex);

	if (numLines)
	{
		float* linesFrom = (float*)bufferServerToClient;
		float* linesTo = (float*)(bufferServerToClient + numLines * 3 * sizeof(float));
		float* linesColor = (float*)(bufferServerToClient + 2 * numLines * 3 * sizeof(float));

		for (int i = 0; i < numLines; i++)
		{
			linesFrom[i * 3] = m_data->m_remoteDebugDrawer->m_lines2[i + startingLineIndex].m_from.x();
			linesTo[i * 3] = m_data->m_remoteDebugDrawer->m_lines2[i + startingLineIndex].m_to.x();
			linesColor[i * 3] = m_data->m_remoteDebugDrawer->m_lines2[i + startingLineIndex].m_color.x();

			linesFrom[i * 3 + 1] = m_data->m_remoteDebugDrawer->m_lines2[i + startingLineIndex].m_from.y();
			linesTo[i * 3 + 1] = m_data->m_remoteDebugDrawer->m_lines2[i + startingLineIndex].m_to.y();
			linesColor[i * 3 + 1] = m_data->m_remoteDebugDrawer->m_lines2[i + startingLineIndex].m_color.y();

			linesFrom[i * 3 + 2] = m_data->m_remoteDebugDrawer->m_lines2[i + startingLineIndex].m_from.z();
			linesTo[i * 3 + 2] = m_data->m_remoteDebugDrawer->m_lines2[i + startingLineIndex].m_to.z();
			linesColor[i * 3 + 2] = m_data->m_remoteDebugDrawer->m_lines2[i + startingLineIndex].m_color.z();
		}
	}

	serverStatusOut.m_type = CMD_DEBUG_LINES_COMPLETED;
	serverStatusOut.m_numDataStreamBytes = numLines * bytesPerLine;
	serverStatusOut.m_sendDebugLinesArgs.m_numDebugLines = numLines;
	serverStatusOut.m_sendDebugLinesArgs.m_startingLineIndex = startingLineIndex;
	serverStatusOut.m_sendDebugLinesArgs.m_numRemainingDebugLines = m_data->m_remoteDebugDrawer->m_lines2.size() - (startingLineIndex + numLines);

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processSyncBodyInfoCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_SYNC_BODY_INFO");

	b3AlignedObjectArray<int> usedHandles;
	m_data->m_bodyHandles.getUsedHandles(usedHandles);
	int actualNumBodies = 0;
	for (int i = 0; i < usedHandles.size(); i++)
	{
		int usedHandle = usedHandles[i];
		InternalBodyData* body = m_data->m_bodyHandles.getHandle(usedHandle);
		if (body && (body->m_multiBody || body->m_rigidBody))
		{
			serverStatusOut.m_sdfLoadedArgs.m_bodyUniqueIds[actualNumBodies++] = usedHandle;
		}
	}
	serverStatusOut.m_sdfLoadedArgs.m_numBodies = actualNumBodies;

	int usz = m_data->m_userConstraints.size();
	serverStatusOut.m_sdfLoadedArgs.m_numUserConstraints = usz;
	for (int i = 0; i < usz; i++)
	{
		int key = m_data->m_userConstraints.getKeyAtIndex(i).getUid1();
		//						int uid = m_data->m_userConstraints.getAtIndex(i)->m_userConstraintData.m_userConstraintUniqueId;
		serverStatusOut.m_sdfLoadedArgs.m_userConstraintUniqueIds[i] = key;
	}

	serverStatusOut.m_type = CMD_SYNC_BODY_INFO_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processSyncUserDataCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_SYNC_USER_DATA");

	b3AlignedObjectArray<int> userDataHandles;
	m_data->m_userDataHandles.getUsedHandles(userDataHandles);
	if (userDataHandles.size())
	{
		memcpy(bufferServerToClient, &userDataHandles[0], sizeof(int) * userDataHandles.size());
	}
	serverStatusOut.m_syncUserDataArgs.m_numUserDataIdentifiers = userDataHandles.size();
	serverStatusOut.m_type = CMD_SYNC_USER_DATA_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestUserDataCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_REQUEST_USER_DATA");
	serverStatusOut.m_type = CMD_REQUEST_USER_DATA_FAILED;

	SharedMemoryUserData* userData = m_data->m_userDataHandles.getHandle(clientCmd.m_userDataRequestArgs.m_userDataId);
	if (!userData)
	{
		return hasStatus;
	}

	btAssert(bufferSizeInBytes >= userData->m_bytes.size());
	serverStatusOut.m_userDataResponseArgs.m_userDataId = clientCmd.m_userDataRequestArgs.m_userDataId;
	serverStatusOut.m_userDataResponseArgs.m_bodyUniqueId = userData->m_bodyUniqueId;
	serverStatusOut.m_userDataResponseArgs.m_linkIndex = userData->m_linkIndex;
	serverStatusOut.m_userDataResponseArgs.m_visualShapeIndex = userData->m_visualShapeIndex;
	serverStatusOut.m_userDataResponseArgs.m_valueType = userData->m_type;
	serverStatusOut.m_userDataResponseArgs.m_valueLength = userData->m_bytes.size();
	serverStatusOut.m_type = CMD_REQUEST_USER_DATA_COMPLETED;

	strcpy(serverStatusOut.m_userDataResponseArgs.m_key, userData->m_key.c_str());
	if (userData->m_bytes.size())
	{
		memcpy(bufferServerToClient, &userData->m_bytes[0], userData->m_bytes.size());
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processAddUserDataCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_ADD_USER_DATA");
	serverStatusOut.m_type = CMD_ADD_USER_DATA_FAILED;

	if (clientCmd.m_addUserDataRequestArgs.m_bodyUniqueId < 0 || clientCmd.m_addUserDataRequestArgs.m_bodyUniqueId >= m_data->m_bodyHandles.getNumHandles())
	{
		return hasStatus;
	}

	InternalBodyData* body = m_data->m_bodyHandles.getHandle(clientCmd.m_addUserDataRequestArgs.m_bodyUniqueId);
	if (!body)
	{
		return hasStatus;
	}

	SharedMemoryUserDataHashKey userDataIdentifier(
		clientCmd.m_addUserDataRequestArgs.m_key,
		clientCmd.m_addUserDataRequestArgs.m_bodyUniqueId,
		clientCmd.m_addUserDataRequestArgs.m_linkIndex,
		clientCmd.m_addUserDataRequestArgs.m_visualShapeIndex);

	int* userDataHandlePtr = m_data->m_userDataHandleLookup.find(userDataIdentifier);
	int userDataHandle = userDataHandlePtr ? *userDataHandlePtr : m_data->m_userDataHandles.allocHandle();

	SharedMemoryUserData* userData = m_data->m_userDataHandles.getHandle(userDataHandle);
	if (!userData)
	{
		return hasStatus;
	}

	if (!userDataHandlePtr)
	{
		userData->m_key = clientCmd.m_addUserDataRequestArgs.m_key;
		userData->m_bodyUniqueId = clientCmd.m_addUserDataRequestArgs.m_bodyUniqueId;
		userData->m_linkIndex = clientCmd.m_addUserDataRequestArgs.m_linkIndex;
		userData->m_visualShapeIndex = clientCmd.m_addUserDataRequestArgs.m_visualShapeIndex;
		m_data->m_userDataHandleLookup.insert(userDataIdentifier, userDataHandle);
		body->m_userDataHandles.push_back(userDataHandle);
	}
	userData->replaceValue(bufferServerToClient,
						   clientCmd.m_addUserDataRequestArgs.m_valueLength,
						   clientCmd.m_addUserDataRequestArgs.m_valueType);

	serverStatusOut.m_type = CMD_ADD_USER_DATA_COMPLETED;
	serverStatusOut.m_userDataResponseArgs.m_userDataId = userDataHandle;
	serverStatusOut.m_userDataResponseArgs.m_bodyUniqueId = clientCmd.m_addUserDataRequestArgs.m_bodyUniqueId;
	serverStatusOut.m_userDataResponseArgs.m_linkIndex = clientCmd.m_addUserDataRequestArgs.m_linkIndex;
	serverStatusOut.m_userDataResponseArgs.m_visualShapeIndex = clientCmd.m_addUserDataRequestArgs.m_visualShapeIndex;
	serverStatusOut.m_userDataResponseArgs.m_valueLength = clientCmd.m_addUserDataRequestArgs.m_valueLength;
	serverStatusOut.m_userDataResponseArgs.m_valueType = clientCmd.m_addUserDataRequestArgs.m_valueType;
	strcpy(serverStatusOut.m_userDataResponseArgs.m_key, clientCmd.m_addUserDataRequestArgs.m_key);

	b3Notification notification;
	notification.m_notificationType = USER_DATA_ADDED;
	notification.m_userDataArgs.m_userDataId = userDataHandle;
	m_data->m_pluginManager.addNotification(notification);

	// Keep bufferServerToClient as-is.
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCollisionFilterCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	serverStatusOut.m_type = CMD_CLIENT_COMMAND_COMPLETED;
	b3PluginCollisionInterface* collisionInterface = m_data->m_pluginManager.getCollisionInterface();
	if (collisionInterface)
	{
		if (clientCmd.m_updateFlags & B3_COLLISION_FILTER_PAIR)
		{
			collisionInterface->setBroadphaseCollisionFilter(clientCmd.m_collisionFilterArgs.m_bodyUniqueIdA,
															 clientCmd.m_collisionFilterArgs.m_bodyUniqueIdB,
															 clientCmd.m_collisionFilterArgs.m_linkIndexA,
															 clientCmd.m_collisionFilterArgs.m_linkIndexB,
															 clientCmd.m_collisionFilterArgs.m_enableCollision);

			btAlignedObjectArray<InternalBodyData*> bodies;

			//now also 'refresh' the broadphase collision pairs involved
			if (clientCmd.m_collisionFilterArgs.m_bodyUniqueIdA >= 0)
			{
				bodies.push_back(m_data->m_bodyHandles.getHandle(clientCmd.m_collisionFilterArgs.m_bodyUniqueIdA));
			}
			if (clientCmd.m_collisionFilterArgs.m_bodyUniqueIdB >= 0)
			{
				bodies.push_back(m_data->m_bodyHandles.getHandle(clientCmd.m_collisionFilterArgs.m_bodyUniqueIdB));
			}
			for (int i = 0; i < bodies.size(); i++)
			{
				InternalBodyData* body = bodies[i];
				if (body)
				{
					if (body->m_multiBody)
					{
						if (body->m_multiBody->getBaseCollider())
						{
							m_data->m_dynamicsWorld->refreshBroadphaseProxy(body->m_multiBody->getBaseCollider());
						}
						for (int i = 0; i < body->m_multiBody->getNumLinks(); i++)
						{
							if (body->m_multiBody->getLinkCollider(i))
							{
								m_data->m_dynamicsWorld->refreshBroadphaseProxy(body->m_multiBody->getLinkCollider(i));
							}
						}
					}
					else
					{
						//btRigidBody case
						if (body->m_rigidBody)
						{
							m_data->m_dynamicsWorld->refreshBroadphaseProxy(body->m_rigidBody);
						}
					}
				}
			}
		}
		if (clientCmd.m_updateFlags & B3_COLLISION_FILTER_GROUP_MASK)
		{
			InternalBodyData* body = m_data->m_bodyHandles.getHandle(clientCmd.m_collisionFilterArgs.m_bodyUniqueIdA);
			btCollisionObject* colObj = 0;
			if (body->m_multiBody)
			{
				if (clientCmd.m_collisionFilterArgs.m_linkIndexA)
				{
					colObj = body->m_multiBody->getBaseCollider();
				}
				else
				{
					if (clientCmd.m_collisionFilterArgs.m_linkIndexA >= 0 && clientCmd.m_collisionFilterArgs.m_linkIndexA < body->m_multiBody->getNumLinks())
					{
						colObj = body->m_multiBody->getLinkCollider(clientCmd.m_collisionFilterArgs.m_linkIndexA);
					}
				}
			}
			else
			{
				if (body->m_rigidBody)
				{
					colObj = body->m_rigidBody;
				}
			}
			if (colObj)
			{
				colObj->getBroadphaseHandle()->m_collisionFilterGroup = clientCmd.m_collisionFilterArgs.m_collisionFilterGroup;
				colObj->getBroadphaseHandle()->m_collisionFilterMask = clientCmd.m_collisionFilterArgs.m_collisionFilterMask;
				m_data->m_dynamicsWorld->refreshBroadphaseProxy(colObj);
			}
		}
	}
	return true;
}

bool PhysicsServerCommandProcessor::processRemoveUserDataCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_REMOVE_USER_DATA");
	serverStatusOut.m_type = CMD_REMOVE_USER_DATA_FAILED;

	SharedMemoryUserData* userData = m_data->m_userDataHandles.getHandle(clientCmd.m_removeUserDataRequestArgs.m_userDataId);
	if (!userData)
	{
		return hasStatus;
	}

	InternalBodyData* body = m_data->m_bodyHandles.getHandle(userData->m_bodyUniqueId);
	if (!body)
	{
		return hasStatus;
	}
	body->m_userDataHandles.remove(clientCmd.m_removeUserDataRequestArgs.m_userDataId);

	m_data->m_userDataHandleLookup.remove(SharedMemoryUserDataHashKey(userData));
	m_data->m_userDataHandles.freeHandle(clientCmd.m_removeUserDataRequestArgs.m_userDataId);

	serverStatusOut.m_removeUserDataResponseArgs = clientCmd.m_removeUserDataRequestArgs;
	serverStatusOut.m_type = CMD_REMOVE_USER_DATA_COMPLETED;

	b3Notification notification;
	notification.m_notificationType = USER_DATA_REMOVED;
	notification.m_userDataArgs.m_userDataId = clientCmd.m_removeUserDataRequestArgs.m_userDataId;
	m_data->m_pluginManager.addNotification(notification);

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processSendDesiredStateCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_SEND_DESIRED_STATE");
	if (m_data->m_verboseOutput)
	{
		b3Printf("Processed CMD_SEND_DESIRED_STATE");
	}

	int bodyUniqueId = clientCmd.m_sendDesiredStateCommandArgument.m_bodyUniqueId;
	InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);

	if (body && body->m_multiBody)
	{
		btMultiBody* mb = body->m_multiBody;
		btAssert(mb);

		switch (clientCmd.m_sendDesiredStateCommandArgument.m_controlMode)
		{
			case CONTROL_MODE_PD:
			{
				if (m_data->m_verboseOutput)
				{
					b3Printf("Using CONTROL_MODE_PD");
				}

				b3PluginArguments args;
				args.m_ints[1] = bodyUniqueId;
				//find the joint motors and apply the desired velocity and maximum force/torque
				{
					int velIndex = 6;  //skip the 3 linear + 3 angular degree of freedom velocity entries of the base
					int posIndex = 7;  //skip 3 positional and 4 orientation (quaternion) positional degrees of freedom of the base
					for (int link = 0; link < mb->getNumLinks(); link++)
					{
						if (supportsJointMotor(mb, link))
						{
							bool hasDesiredPosOrVel = false;
							btScalar desiredVelocity = 0.f;
							if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[velIndex] & SIM_DESIRED_STATE_HAS_QDOT) != 0)
							{
								hasDesiredPosOrVel = true;
								desiredVelocity = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateQdot[velIndex];
								args.m_floats[2] = 0.1;  // kd
							}
							btScalar desiredPosition = 0.f;
							if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[posIndex] & SIM_DESIRED_STATE_HAS_Q) != 0)
							{
								hasDesiredPosOrVel = true;
								desiredPosition = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateQ[posIndex];
								args.m_floats[3] = 0.1;  // kp
							}

							if (hasDesiredPosOrVel)
							{
								if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[velIndex] & SIM_DESIRED_STATE_HAS_KP) != 0)
								{
									args.m_floats[3] = clientCmd.m_sendDesiredStateCommandArgument.m_Kp[velIndex];
								}
								if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[velIndex] & SIM_DESIRED_STATE_HAS_KD) != 0)
								{
									args.m_floats[2] = clientCmd.m_sendDesiredStateCommandArgument.m_Kd[velIndex];
								}

								args.m_floats[1] = desiredVelocity;
								//clamp position
								if (mb->getLink(link).m_jointLowerLimit <= mb->getLink(link).m_jointUpperLimit)
								{
									btClamp(desiredPosition, mb->getLink(link).m_jointLowerLimit, mb->getLink(link).m_jointUpperLimit);
								}
								args.m_floats[0] = desiredPosition;

								btScalar maxImp = 1000000.f;
								if ((clientCmd.m_updateFlags & SIM_DESIRED_STATE_HAS_MAX_FORCE) != 0)
									maxImp = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateForceTorque[velIndex];
								args.m_floats[4] = maxImp;

								args.m_ints[2] = link;
								args.m_numInts = 3;
								args.m_numFloats = 5;

								args.m_ints[0] = eSetPDControl;
								if (args.m_floats[4] < B3_EPSILON)
								{
									args.m_ints[0] = eRemovePDControl;
								}
								m_data->m_pluginManager.executePluginCommand(m_data->m_pdControlPlugin, &args);
							}
						}
						velIndex += mb->getLink(link).m_dofCount;
						posIndex += mb->getLink(link).m_posVarCount;
					}
				}
				break;
			}
			case CONTROL_MODE_TORQUE:
			{
				if (m_data->m_verboseOutput)
				{
					b3Printf("Using CONTROL_MODE_TORQUE");
				}
				//  mb->clearForcesAndTorques();
				int torqueIndex = 6;
				if ((clientCmd.m_updateFlags & SIM_DESIRED_STATE_HAS_MAX_FORCE) != 0)
				{
					for (int link = 0; link < mb->getNumLinks(); link++)
					{
						for (int dof = 0; dof < mb->getLink(link).m_dofCount; dof++)
						{
							double torque = 0.f;
							if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[torqueIndex] & SIM_DESIRED_STATE_HAS_MAX_FORCE) != 0)
							{
								torque = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateForceTorque[torqueIndex];
								mb->addJointTorqueMultiDof(link, dof, torque);
							}
							torqueIndex++;
						}
					}
				}
				break;
			}
			case CONTROL_MODE_VELOCITY:
			{
				if (m_data->m_verboseOutput)
				{
					b3Printf("Using CONTROL_MODE_VELOCITY");
				}

				int numMotors = 0;
				//find the joint motors and apply the desired velocity and maximum force/torque
				{
					int dofIndex = 6;  //skip the 3 linear + 3 angular degree of freedom entries of the base
					for (int link = 0; link < mb->getNumLinks(); link++)
					{
						if (supportsJointMotor(mb, link))
						{
							btMultiBodyJointMotor* motor = (btMultiBodyJointMotor*)mb->getLink(link).m_userPtr;

							if (motor)
							{
								btScalar desiredVelocity = 0.f;
								bool hasDesiredVelocity = false;

								if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[dofIndex] & SIM_DESIRED_STATE_HAS_QDOT) != 0)
								{
									desiredVelocity = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateQdot[dofIndex];
									btScalar kd = 0.1f;
									if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[dofIndex] & SIM_DESIRED_STATE_HAS_KD) != 0)
									{
										kd = clientCmd.m_sendDesiredStateCommandArgument.m_Kd[dofIndex];
									}

									motor->setVelocityTarget(desiredVelocity, kd);

									btScalar kp = 0.f;
									motor->setPositionTarget(0, kp);
									hasDesiredVelocity = true;
								}
								if (hasDesiredVelocity)
								{
									//disable velocity clamp in velocity mode
									motor->setRhsClamp(SIMD_INFINITY);

									btScalar maxImp = 1000000.f * m_data->m_physicsDeltaTime;
									if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[dofIndex] & SIM_DESIRED_STATE_HAS_MAX_FORCE) != 0)
									{
										maxImp = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateForceTorque[dofIndex] * m_data->m_physicsDeltaTime;
									}
									motor->setMaxAppliedImpulse(maxImp);
								}
								numMotors++;
							}
						}
						dofIndex += mb->getLink(link).m_dofCount;
					}
				}
				break;
			}

			case CONTROL_MODE_POSITION_VELOCITY_PD:
			{
				if (m_data->m_verboseOutput)
				{
					b3Printf("Using CONTROL_MODE_POSITION_VELOCITY_PD");
				}
				//compute the force base on PD control

				int numMotors = 0;
				//find the joint motors and apply the desired velocity and maximum force/torque
				{
					int velIndex = 6;  //skip the 3 linear + 3 angular degree of freedom velocity entries of the base
					int posIndex = 7;  //skip 3 positional and 4 orientation (quaternion) positional degrees of freedom of the base
					for (int link = 0; link < mb->getNumLinks(); link++)
					{
						if (supportsJointMotor(mb, link))
						{
							btMultiBodyJointMotor* motor = (btMultiBodyJointMotor*)mb->getLink(link).m_userPtr;

							if (motor)
							{
								if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[velIndex] & SIM_DESIRED_STATE_HAS_RHS_CLAMP) != 0)
								{
									motor->setRhsClamp(clientCmd.m_sendDesiredStateCommandArgument.m_rhsClamp[velIndex]);
								}

								bool hasDesiredPosOrVel = false;
								btScalar kp = 0.f;
								btScalar kd = 0.f;
								btScalar desiredVelocity = 0.f;
								if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[velIndex] & SIM_DESIRED_STATE_HAS_QDOT) != 0)
								{
									hasDesiredPosOrVel = true;
									desiredVelocity = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateQdot[velIndex];
									kd = 0.1;
								}
								btScalar desiredPosition = 0.f;
								if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[posIndex] & SIM_DESIRED_STATE_HAS_Q) != 0)
								{
									hasDesiredPosOrVel = true;
									desiredPosition = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateQ[posIndex];
									kp = 0.1;
								}

								if (hasDesiredPosOrVel)
								{
									if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[velIndex] & SIM_DESIRED_STATE_HAS_KP) != 0)
									{
										kp = clientCmd.m_sendDesiredStateCommandArgument.m_Kp[velIndex];
									}

									if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[velIndex] & SIM_DESIRED_STATE_HAS_KD) != 0)
									{
										kd = clientCmd.m_sendDesiredStateCommandArgument.m_Kd[velIndex];
									}

									motor->setVelocityTarget(desiredVelocity, kd);
									//todo: instead of clamping, combine the motor and limit
									//and combine handling of limit force and motor force.

									//clamp position
									if (mb->getLink(link).m_jointLowerLimit <= mb->getLink(link).m_jointUpperLimit)
									{
										btClamp(desiredPosition, mb->getLink(link).m_jointLowerLimit, mb->getLink(link).m_jointUpperLimit);
									}
									motor->setPositionTarget(desiredPosition, kp);

									btScalar maxImp = 1000000.f * m_data->m_physicsDeltaTime;

									if ((clientCmd.m_updateFlags & SIM_DESIRED_STATE_HAS_MAX_FORCE) != 0)
										maxImp = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateForceTorque[velIndex] * m_data->m_physicsDeltaTime;

									motor->setMaxAppliedImpulse(maxImp);
								}
								numMotors++;
							}
						}
						velIndex += mb->getLink(link).m_dofCount;
						posIndex += mb->getLink(link).m_posVarCount;
					}
				}

				break;
			}
			default:
			{
				b3Warning("m_controlMode not implemented yet");
				break;
			}
		}
	}
	else
	{
		//support for non-btMultiBody, such as btRigidBody

		if (body && body->m_rigidBody)
		{
			btRigidBody* rb = body->m_rigidBody;
			btAssert(rb);

			//switch (clientCmd.m_sendDesiredStateCommandArgument.m_controlMode)
			{
				//case CONTROL_MODE_TORQUE:
				{
					if (m_data->m_verboseOutput)
					{
						b3Printf("Using CONTROL_MODE_TORQUE");
					}
					//  mb->clearForcesAndTorques();
					///see addJointInfoFromConstraint
					int velIndex = 6;
					int posIndex = 7;
					//if ((clientCmd.m_updateFlags&SIM_DESIRED_STATE_HAS_MAX_FORCE)!=0)
					{
						for (int link = 0; link < body->m_rigidBodyJoints.size(); link++)
						{
							btGeneric6DofSpring2Constraint* con = body->m_rigidBodyJoints[link];

							btVector3 linearLowerLimit;
							btVector3 linearUpperLimit;
							btVector3 angularLowerLimit;
							btVector3 angularUpperLimit;

							//for (int dof=0;dof<mb->getLink(link).m_dofCount;dof++)
							{
								{
									int torqueIndex = velIndex;
									double torque = 100;
									bool hasDesiredTorque = false;
									if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[velIndex] & SIM_DESIRED_STATE_HAS_MAX_FORCE) != 0)
									{
										torque = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateForceTorque[velIndex];
										hasDesiredTorque = true;
									}

									bool hasDesiredPosOrVel = false;
									btScalar qdotTarget = 0.f;
									if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[velIndex] & SIM_DESIRED_STATE_HAS_QDOT) != 0)
									{
										hasDesiredPosOrVel = true;
										qdotTarget = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateQdot[velIndex];
									}
									btScalar qTarget = 0.f;
									if ((clientCmd.m_sendDesiredStateCommandArgument.m_hasDesiredStateFlags[posIndex] & SIM_DESIRED_STATE_HAS_Q) != 0)
									{
										hasDesiredPosOrVel = true;
										qTarget = clientCmd.m_sendDesiredStateCommandArgument.m_desiredStateQ[posIndex];
									}

									con->getLinearLowerLimit(linearLowerLimit);
									con->getLinearUpperLimit(linearUpperLimit);
									con->getAngularLowerLimit(angularLowerLimit);
									con->getAngularUpperLimit(angularUpperLimit);

									if (linearLowerLimit.isZero() && linearUpperLimit.isZero() && angularLowerLimit.isZero() && angularUpperLimit.isZero())
									{
										//fixed, don't do anything
									}
									else
									{
										con->calculateTransforms();

										if (linearLowerLimit.isZero() && linearUpperLimit.isZero())
										{
											//eRevoluteType;
											btVector3 limitRange = angularLowerLimit.absolute() + angularUpperLimit.absolute();
											int limitAxis = limitRange.maxAxis();
											const btTransform& transA = con->getCalculatedTransformA();
											const btTransform& transB = con->getCalculatedTransformB();
											btVector3 axisA = transA.getBasis().getColumn(limitAxis);
											btVector3 axisB = transB.getBasis().getColumn(limitAxis);

											switch (clientCmd.m_sendDesiredStateCommandArgument.m_controlMode)
											{
												case CONTROL_MODE_TORQUE:
												{
													if (hasDesiredTorque)
													{
														con->getRigidBodyA().applyTorque(torque * axisA);
														con->getRigidBodyB().applyTorque(-torque * axisB);
													}
													break;
												}
												case CONTROL_MODE_VELOCITY:
												{
													if (hasDesiredPosOrVel)
													{
														con->enableMotor(3 + limitAxis, true);
														con->setTargetVelocity(3 + limitAxis, qdotTarget);
														con->setMaxMotorForce(3 + limitAxis, torque);
													}
													break;
												}
												case CONTROL_MODE_POSITION_VELOCITY_PD:
												{
													if (hasDesiredPosOrVel)
													{
														con->setServo(3 + limitAxis, true);
														con->setServoTarget(3 + limitAxis, -qTarget);
														//next one is the maximum velocity to reach target position.
														//the maximum velocity is limited by maxMotorForce
														con->setTargetVelocity(3 + limitAxis, 100);
														con->setMaxMotorForce(3 + limitAxis, torque);
														con->enableMotor(3 + limitAxis, true);
													}
													break;
												}
												default:
												{
												}
											};
										}
										else
										{
											//ePrismaticType; @todo
											btVector3 limitRange = linearLowerLimit.absolute() + linearUpperLimit.absolute();
											int limitAxis = limitRange.maxAxis();

											const btTransform& transA = con->getCalculatedTransformA();
											const btTransform& transB = con->getCalculatedTransformB();
											btVector3 axisA = transA.getBasis().getColumn(limitAxis);
											btVector3 axisB = transB.getBasis().getColumn(limitAxis);

											switch (clientCmd.m_sendDesiredStateCommandArgument.m_controlMode)
											{
												case CONTROL_MODE_TORQUE:
												{
													con->getRigidBodyA().applyForce(-torque * axisA, btVector3(0, 0, 0));
													con->getRigidBodyB().applyForce(torque * axisB, btVector3(0, 0, 0));
													break;
												}
												case CONTROL_MODE_VELOCITY:
												{
													con->enableMotor(limitAxis, true);
													con->setTargetVelocity(limitAxis, -qdotTarget);
													con->setMaxMotorForce(limitAxis, torque);
													break;
												}
												case CONTROL_MODE_POSITION_VELOCITY_PD:
												{
													con->setServo(limitAxis, true);
													con->setServoTarget(limitAxis, qTarget);
													//next one is the maximum velocity to reach target position.
													//the maximum velocity is limited by maxMotorForce
													con->setTargetVelocity(limitAxis, 100);
													con->setMaxMotorForce(limitAxis, torque);
													con->enableMotor(limitAxis, true);
													break;
												}
												default:
												{
												}
											};
										}
									}
								}  //fi
								///see addJointInfoFromConstraint
								velIndex++;  //info.m_uIndex
								posIndex++;  //info.m_qIndex
							}
						}
					}  //fi
					//break;
				}
			}
		}  //if (body && body->m_rigidBody)
	}

	serverStatusOut.m_type = CMD_DESIRED_STATE_RECEIVED_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestActualStateCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	serverStatusOut.m_type = CMD_ACTUAL_STATE_UPDATE_FAILED;

	BT_PROFILE("CMD_REQUEST_ACTUAL_STATE");
	if (m_data->m_verboseOutput)
	{
		b3Printf("Sending the actual state (Q,U)");
	}
	int bodyUniqueId = clientCmd.m_requestActualStateInformationCommandArgument.m_bodyUniqueId;
	InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);

	if (body && body->m_multiBody)
	{
		btMultiBody* mb = body->m_multiBody;
		SharedMemoryStatus& serverCmd = serverStatusOut;
		serverStatusOut.m_type = CMD_ACTUAL_STATE_UPDATE_COMPLETED;

		serverCmd.m_sendActualStateArgs.m_bodyUniqueId = bodyUniqueId;
		serverCmd.m_sendActualStateArgs.m_numLinks = body->m_multiBody->getNumLinks();

		int totalDegreeOfFreedomQ = 0;
		int totalDegreeOfFreedomU = 0;

		if (mb->getNumLinks() >= MAX_DEGREE_OF_FREEDOM)
		{
			serverStatusOut.m_type = CMD_ACTUAL_STATE_UPDATE_FAILED;
			hasStatus = true;
			return hasStatus;
		}

		//always add the base, even for static (non-moving objects)
		//so that we can easily move the 'fixed' base when needed
		//do we don't use this conditional "if (!mb->hasFixedBase())"
		{
			btTransform tr;
			tr.setOrigin(mb->getBasePos());
			tr.setRotation(mb->getWorldToBaseRot().inverse());

			serverCmd.m_sendActualStateArgs.m_rootLocalInertialFrame[0] =
				body->m_rootLocalInertialFrame.getOrigin()[0];
			serverCmd.m_sendActualStateArgs.m_rootLocalInertialFrame[1] =
				body->m_rootLocalInertialFrame.getOrigin()[1];
			serverCmd.m_sendActualStateArgs.m_rootLocalInertialFrame[2] =
				body->m_rootLocalInertialFrame.getOrigin()[2];

			serverCmd.m_sendActualStateArgs.m_rootLocalInertialFrame[3] =
				body->m_rootLocalInertialFrame.getRotation()[0];
			serverCmd.m_sendActualStateArgs.m_rootLocalInertialFrame[4] =
				body->m_rootLocalInertialFrame.getRotation()[1];
			serverCmd.m_sendActualStateArgs.m_rootLocalInertialFrame[5] =
				body->m_rootLocalInertialFrame.getRotation()[2];
			serverCmd.m_sendActualStateArgs.m_rootLocalInertialFrame[6] =
				body->m_rootLocalInertialFrame.getRotation()[3];

			//base position in world space, carthesian
			serverCmd.m_sendActualStateArgs.m_actualStateQ[0] = tr.getOrigin()[0];
			serverCmd.m_sendActualStateArgs.m_actualStateQ[1] = tr.getOrigin()[1];
			serverCmd.m_sendActualStateArgs.m_actualStateQ[2] = tr.getOrigin()[2];

			//base orientation, quaternion x,y,z,w, in world space, carthesian
			serverCmd.m_sendActualStateArgs.m_actualStateQ[3] = tr.getRotation()[0];
			serverCmd.m_sendActualStateArgs.m_actualStateQ[4] = tr.getRotation()[1];
			serverCmd.m_sendActualStateArgs.m_actualStateQ[5] = tr.getRotation()[2];
			serverCmd.m_sendActualStateArgs.m_actualStateQ[6] = tr.getRotation()[3];
			totalDegreeOfFreedomQ += 7;  //pos + quaternion

			//base linear velocity (in world space, carthesian)
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[0] = mb->getBaseVel()[0];
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[1] = mb->getBaseVel()[1];
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[2] = mb->getBaseVel()[2];

			//base angular velocity (in world space, carthesian)
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[3] = mb->getBaseOmega()[0];
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[4] = mb->getBaseOmega()[1];
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[5] = mb->getBaseOmega()[2];
			totalDegreeOfFreedomU += 6;  //3 linear and 3 angular DOF
		}

		btAlignedObjectArray<btVector3> omega;
		btAlignedObjectArray<btVector3> linVel;

		bool computeForwardKinematics = ((clientCmd.m_updateFlags & ACTUAL_STATE_COMPUTE_FORWARD_KINEMATICS) != 0);
		if (computeForwardKinematics)
		{
			B3_PROFILE("compForwardKinematics");
			btAlignedObjectArray<btQuaternion> world_to_local;
			btAlignedObjectArray<btVector3> local_origin;
			world_to_local.resize(mb->getNumLinks() + 1);
			local_origin.resize(mb->getNumLinks() + 1);
			mb->forwardKinematics(world_to_local, local_origin);
		}

		bool computeLinkVelocities = ((clientCmd.m_updateFlags & ACTUAL_STATE_COMPUTE_LINKVELOCITY) != 0);
		if (computeLinkVelocities)
		{
			omega.resize(mb->getNumLinks() + 1);
			linVel.resize(mb->getNumLinks() + 1);
			{
				B3_PROFILE("compTreeLinkVelocities");
				mb->compTreeLinkVelocities(&omega[0], &linVel[0]);
			}
		}
		for (int l = 0; l < mb->getNumLinks(); l++)
		{
			for (int d = 0; d < mb->getLink(l).m_posVarCount; d++)
			{
				serverCmd.m_sendActualStateArgs.m_actualStateQ[totalDegreeOfFreedomQ++] = mb->getJointPosMultiDof(l)[d];
			}
			for (int d = 0; d < mb->getLink(l).m_dofCount; d++)
			{
				serverCmd.m_sendActualStateArgs.m_actualStateQdot[totalDegreeOfFreedomU++] = mb->getJointVelMultiDof(l)[d];
			}

			if (0 == mb->getLink(l).m_jointFeedback)
			{
				for (int d = 0; d < 6; d++)
				{
					serverCmd.m_sendActualStateArgs.m_jointReactionForces[l * 6 + d] = 0;
				}
			}
			else
			{
				btVector3 sensedForce = mb->getLink(l).m_jointFeedback->m_reactionForces.getLinear();
				btVector3 sensedTorque = mb->getLink(l).m_jointFeedback->m_reactionForces.getAngular();

				serverCmd.m_sendActualStateArgs.m_jointReactionForces[l * 6 + 0] = sensedForce[0];
				serverCmd.m_sendActualStateArgs.m_jointReactionForces[l * 6 + 1] = sensedForce[1];
				serverCmd.m_sendActualStateArgs.m_jointReactionForces[l * 6 + 2] = sensedForce[2];

				serverCmd.m_sendActualStateArgs.m_jointReactionForces[l * 6 + 3] = sensedTorque[0];
				serverCmd.m_sendActualStateArgs.m_jointReactionForces[l * 6 + 4] = sensedTorque[1];
				serverCmd.m_sendActualStateArgs.m_jointReactionForces[l * 6 + 5] = sensedTorque[2];
			}

			serverCmd.m_sendActualStateArgs.m_jointMotorForce[l] = 0;

			if (supportsJointMotor(mb, l))
			{
				btMultiBodyJointMotor* motor = (btMultiBodyJointMotor*)body->m_multiBody->getLink(l).m_userPtr;

				if (motor && m_data->m_physicsDeltaTime > btScalar(0))
				{
					btScalar force = motor->getAppliedImpulse(0) / m_data->m_physicsDeltaTime;
					serverCmd.m_sendActualStateArgs.m_jointMotorForce[l] =
						force;
					//if (force>0)
					//{
					//   b3Printf("force = %f\n", force);
					//}
				}
			}
			btVector3 linkLocalInertialOrigin = body->m_linkLocalInertialFrames[l].getOrigin();
			btQuaternion linkLocalInertialRotation = body->m_linkLocalInertialFrames[l].getRotation();

			btVector3 linkCOMOrigin = mb->getLink(l).m_cachedWorldTransform.getOrigin();
			btQuaternion linkCOMRotation = mb->getLink(l).m_cachedWorldTransform.getRotation();

			serverCmd.m_sendActualStateArgs.m_linkState[l * 7 + 0] = linkCOMOrigin.getX();
			serverCmd.m_sendActualStateArgs.m_linkState[l * 7 + 1] = linkCOMOrigin.getY();
			serverCmd.m_sendActualStateArgs.m_linkState[l * 7 + 2] = linkCOMOrigin.getZ();
			serverCmd.m_sendActualStateArgs.m_linkState[l * 7 + 3] = linkCOMRotation.x();
			serverCmd.m_sendActualStateArgs.m_linkState[l * 7 + 4] = linkCOMRotation.y();
			serverCmd.m_sendActualStateArgs.m_linkState[l * 7 + 5] = linkCOMRotation.z();
			serverCmd.m_sendActualStateArgs.m_linkState[l * 7 + 6] = linkCOMRotation.w();

			btVector3 worldLinVel(0, 0, 0);
			btVector3 worldAngVel(0, 0, 0);

			if (computeLinkVelocities)
			{
				const btMatrix3x3& linkRotMat = mb->getLink(l).m_cachedWorldTransform.getBasis();
				worldLinVel = linkRotMat * linVel[l + 1];
				worldAngVel = linkRotMat * omega[l + 1];
			}

			serverCmd.m_sendActualStateArgs.m_linkWorldVelocities[l * 6 + 0] = worldLinVel[0];
			serverCmd.m_sendActualStateArgs.m_linkWorldVelocities[l * 6 + 1] = worldLinVel[1];
			serverCmd.m_sendActualStateArgs.m_linkWorldVelocities[l * 6 + 2] = worldLinVel[2];
			serverCmd.m_sendActualStateArgs.m_linkWorldVelocities[l * 6 + 3] = worldAngVel[0];
			serverCmd.m_sendActualStateArgs.m_linkWorldVelocities[l * 6 + 4] = worldAngVel[1];
			serverCmd.m_sendActualStateArgs.m_linkWorldVelocities[l * 6 + 5] = worldAngVel[2];

			serverCmd.m_sendActualStateArgs.m_linkLocalInertialFrames[l * 7 + 0] = linkLocalInertialOrigin.getX();
			serverCmd.m_sendActualStateArgs.m_linkLocalInertialFrames[l * 7 + 1] = linkLocalInertialOrigin.getY();
			serverCmd.m_sendActualStateArgs.m_linkLocalInertialFrames[l * 7 + 2] = linkLocalInertialOrigin.getZ();

			serverCmd.m_sendActualStateArgs.m_linkLocalInertialFrames[l * 7 + 3] = linkLocalInertialRotation.x();
			serverCmd.m_sendActualStateArgs.m_linkLocalInertialFrames[l * 7 + 4] = linkLocalInertialRotation.y();
			serverCmd.m_sendActualStateArgs.m_linkLocalInertialFrames[l * 7 + 5] = linkLocalInertialRotation.z();
			serverCmd.m_sendActualStateArgs.m_linkLocalInertialFrames[l * 7 + 6] = linkLocalInertialRotation.w();
		}

		serverCmd.m_sendActualStateArgs.m_numDegreeOfFreedomQ = totalDegreeOfFreedomQ;
		serverCmd.m_sendActualStateArgs.m_numDegreeOfFreedomU = totalDegreeOfFreedomU;

		hasStatus = true;
	}
	else
	{
		if (body && body->m_rigidBody)
		{
			btRigidBody* rb = body->m_rigidBody;
			SharedMemoryStatus& serverCmd = serverStatusOut;
			serverCmd.m_type = CMD_ACTUAL_STATE_UPDATE_COMPLETED;

			serverCmd.m_sendActualStateArgs.m_bodyUniqueId = bodyUniqueId;
			serverCmd.m_sendActualStateArgs.m_numLinks = 0;

			int totalDegreeOfFreedomQ = 0;
			int totalDegreeOfFreedomU = 0;

			btTransform tr = rb->getWorldTransform();
			//base position in world space, carthesian
			serverCmd.m_sendActualStateArgs.m_actualStateQ[0] = tr.getOrigin()[0];
			serverCmd.m_sendActualStateArgs.m_actualStateQ[1] = tr.getOrigin()[1];
			serverCmd.m_sendActualStateArgs.m_actualStateQ[2] = tr.getOrigin()[2];

			//base orientation, quaternion x,y,z,w, in world space, carthesian
			serverCmd.m_sendActualStateArgs.m_actualStateQ[3] = tr.getRotation()[0];
			serverCmd.m_sendActualStateArgs.m_actualStateQ[4] = tr.getRotation()[1];
			serverCmd.m_sendActualStateArgs.m_actualStateQ[5] = tr.getRotation()[2];
			serverCmd.m_sendActualStateArgs.m_actualStateQ[6] = tr.getRotation()[3];
			totalDegreeOfFreedomQ += 7;  //pos + quaternion

			//base linear velocity (in world space, carthesian)
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[0] = rb->getLinearVelocity()[0];
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[1] = rb->getLinearVelocity()[1];
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[2] = rb->getLinearVelocity()[2];

			//base angular velocity (in world space, carthesian)
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[3] = rb->getAngularVelocity()[0];
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[4] = rb->getAngularVelocity()[1];
			serverCmd.m_sendActualStateArgs.m_actualStateQdot[5] = rb->getAngularVelocity()[2];
			totalDegreeOfFreedomU += 6;  //3 linear and 3 angular DOF

			serverCmd.m_sendActualStateArgs.m_numDegreeOfFreedomQ = totalDegreeOfFreedomQ;
			serverCmd.m_sendActualStateArgs.m_numDegreeOfFreedomU = totalDegreeOfFreedomU;

			hasStatus = true;
		}
		else
		{
			//b3Warning("Request state but no multibody or rigid body available");
			SharedMemoryStatus& serverCmd = serverStatusOut;
			serverCmd.m_type = CMD_ACTUAL_STATE_UPDATE_FAILED;
			hasStatus = true;
		}
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestContactpointInformationCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_REQUEST_CONTACT_POINT_INFORMATION");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_sendContactPointArgs.m_numContactPointsCopied = 0;

	//make a snapshot of the contact manifolds into individual contact points
	if (clientCmd.m_requestContactPointArguments.m_startingContactPointIndex == 0)
	{
		m_data->m_cachedContactPoints.resize(0);

		int mode = CONTACT_QUERY_MODE_REPORT_EXISTING_CONTACT_POINTS;

		if (clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_QUERY_MODE)
		{
			mode = clientCmd.m_requestContactPointArguments.m_mode;
		}

		switch (mode)
		{
			case CONTACT_QUERY_MODE_REPORT_EXISTING_CONTACT_POINTS:
			{
				int numContactManifolds = m_data->m_dynamicsWorld->getDispatcher()->getNumManifolds();
				m_data->m_cachedContactPoints.reserve(numContactManifolds * 4);
				for (int i = 0; i < numContactManifolds; i++)
				{
					const btPersistentManifold* manifold = m_data->m_dynamicsWorld->getDispatcher()->getInternalManifoldPointer()[i];
					int linkIndexA = -1;
					int linkIndexB = -1;

					int objectIndexB = -1;
					const btRigidBody* bodyB = btRigidBody::upcast(manifold->getBody1());
					if (bodyB)
					{
						objectIndexB = bodyB->getUserIndex2();
					}
					const btMultiBodyLinkCollider* mblB = btMultiBodyLinkCollider::upcast(manifold->getBody1());
					if (mblB && mblB->m_multiBody)
					{
						linkIndexB = mblB->m_link;
						objectIndexB = mblB->m_multiBody->getUserIndex2();
					}

					int objectIndexA = -1;
					const btRigidBody* bodyA = btRigidBody::upcast(manifold->getBody0());
					if (bodyA)
					{
						objectIndexA = bodyA->getUserIndex2();
					}
					const btMultiBodyLinkCollider* mblA = btMultiBodyLinkCollider::upcast(manifold->getBody0());
					if (mblA && mblA->m_multiBody)
					{
						linkIndexA = mblA->m_link;
						objectIndexA = mblA->m_multiBody->getUserIndex2();
					}
					btAssert(bodyA || mblA);

					//apply the filter, if the user provides it
					bool swap = false;
					if (clientCmd.m_requestContactPointArguments.m_objectAIndexFilter >= 0)
					{
						if (clientCmd.m_requestContactPointArguments.m_objectAIndexFilter == objectIndexA)
						{
							swap = false;
						}
						else if (clientCmd.m_requestContactPointArguments.m_objectAIndexFilter == objectIndexB)
						{
							swap = true;
						}
						else
						{
							continue;
						}
					}

					if (swap)
					{
						std::swap(objectIndexA, objectIndexB);
						std::swap(linkIndexA, linkIndexB);
						std::swap(bodyA, bodyB);
					}

					//apply the second object filter, if the user provides it
					if (clientCmd.m_requestContactPointArguments.m_objectBIndexFilter >= 0)
					{
						if (clientCmd.m_requestContactPointArguments.m_objectBIndexFilter != objectIndexB)
						{
							continue;
						}
					}

					if (
						(clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_LINK_INDEX_A_FILTER) &&
						clientCmd.m_requestContactPointArguments.m_linkIndexAIndexFilter != linkIndexA)
					{
						continue;
					}

					if (
						(clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_LINK_INDEX_B_FILTER) &&
						clientCmd.m_requestContactPointArguments.m_linkIndexBIndexFilter != linkIndexB)
					{
						continue;
					}

					for (int p = 0; p < manifold->getNumContacts(); p++)
					{
						b3ContactPointData pt;
						pt.m_bodyUniqueIdA = objectIndexA;
						pt.m_bodyUniqueIdB = objectIndexB;
						const btManifoldPoint& srcPt = manifold->getContactPoint(p);
						pt.m_contactDistance = srcPt.getDistance();
						pt.m_contactFlags = 0;
						pt.m_linkIndexA = linkIndexA;
						pt.m_linkIndexB = linkIndexB;
						for (int j = 0; j < 3; j++)
						{
							pt.m_contactNormalOnBInWS[j] = srcPt.m_normalWorldOnB[j];
							pt.m_positionOnAInWS[j] = srcPt.getPositionWorldOnA()[j];
							pt.m_positionOnBInWS[j] = srcPt.getPositionWorldOnB()[j];
						}
						pt.m_normalForce = srcPt.getAppliedImpulse() / m_data->m_physicsDeltaTime;
						//                                    pt.m_linearFrictionForce = srcPt.m_appliedImpulseLateral1;
						m_data->m_cachedContactPoints.push_back(pt);
					}
				}
				break;
			}

			case CONTACT_QUERY_MODE_COMPUTE_CLOSEST_POINTS:
			{
				//todo(erwincoumans) compute closest points between all, and vs all, pair
				btScalar closestDistanceThreshold = 0.f;

				if (clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_CLOSEST_DISTANCE_THRESHOLD)
				{
					closestDistanceThreshold = clientCmd.m_requestContactPointArguments.m_closestDistanceThreshold;
				}

				int bodyUniqueIdA = clientCmd.m_requestContactPointArguments.m_objectAIndexFilter;
				int bodyUniqueIdB = clientCmd.m_requestContactPointArguments.m_objectBIndexFilter;

				bool hasLinkIndexAFilter = (0 != (clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_LINK_INDEX_A_FILTER));
				bool hasLinkIndexBFilter = (0 != (clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_LINK_INDEX_B_FILTER));

				int linkIndexA = clientCmd.m_requestContactPointArguments.m_linkIndexAIndexFilter;
				int linkIndexB = clientCmd.m_requestContactPointArguments.m_linkIndexBIndexFilter;

				btAlignedObjectArray<btCollisionObject*> setA;
				btAlignedObjectArray<btCollisionObject*> setB;
				btAlignedObjectArray<int> setALinkIndex;
				btAlignedObjectArray<int> setBLinkIndex;

				btCollisionObject colObA;
				btCollisionObject colObB;

				int collisionShapeA = (clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_COLLISION_SHAPE_A) ? clientCmd.m_requestContactPointArguments.m_collisionShapeA : -1;
				int collisionShapeB = (clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_COLLISION_SHAPE_B) ? clientCmd.m_requestContactPointArguments.m_collisionShapeB : -1;

				if (collisionShapeA >= 0)
				{
					btVector3 posA(0, 0, 0);
					if (clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_COLLISION_SHAPE_POSITION_A)
					{
						posA.setValue(clientCmd.m_requestContactPointArguments.m_collisionShapePositionA[0],
									  clientCmd.m_requestContactPointArguments.m_collisionShapePositionA[1],
									  clientCmd.m_requestContactPointArguments.m_collisionShapePositionA[2]);
					}
					btQuaternion ornA(0, 0, 0, 1);
					if (clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_COLLISION_SHAPE_ORIENTATION_A)
					{
						ornA.setValue(clientCmd.m_requestContactPointArguments.m_collisionShapeOrientationA[0],
									  clientCmd.m_requestContactPointArguments.m_collisionShapeOrientationA[1],
									  clientCmd.m_requestContactPointArguments.m_collisionShapeOrientationA[2],
									  clientCmd.m_requestContactPointArguments.m_collisionShapeOrientationA[3]);
					}
					InternalCollisionShapeHandle* handle = m_data->m_userCollisionShapeHandles.getHandle(collisionShapeA);

					if (handle && handle->m_collisionShape)
					{
						colObA.setCollisionShape(handle->m_collisionShape);
						btTransform tr;
						tr.setIdentity();
						tr.setOrigin(posA);
						tr.setRotation(ornA);
						colObA.setWorldTransform(tr);
						setA.push_back(&colObA);
						setALinkIndex.push_back(-2);
					}
					else
					{
						b3Warning("collisionShapeA provided is not valid.");
					}
				}
				if (collisionShapeB >= 0)
				{
					btVector3 posB(0, 0, 0);
					if (clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_COLLISION_SHAPE_POSITION_B)
					{
						posB.setValue(clientCmd.m_requestContactPointArguments.m_collisionShapePositionB[0],
									  clientCmd.m_requestContactPointArguments.m_collisionShapePositionB[1],
									  clientCmd.m_requestContactPointArguments.m_collisionShapePositionB[2]);
					}
					btQuaternion ornB(0, 0, 0, 1);
					if (clientCmd.m_updateFlags & CMD_REQUEST_CONTACT_POINT_HAS_COLLISION_SHAPE_ORIENTATION_B)
					{
						ornB.setValue(clientCmd.m_requestContactPointArguments.m_collisionShapeOrientationB[0],
									  clientCmd.m_requestContactPointArguments.m_collisionShapeOrientationB[1],
									  clientCmd.m_requestContactPointArguments.m_collisionShapeOrientationB[2],
									  clientCmd.m_requestContactPointArguments.m_collisionShapeOrientationB[3]);
					}

					InternalCollisionShapeHandle* handle = m_data->m_userCollisionShapeHandles.getHandle(collisionShapeB);
					if (handle && handle->m_collisionShape)
					{
						colObB.setCollisionShape(handle->m_collisionShape);
						btTransform tr;
						tr.setIdentity();
						tr.setOrigin(posB);
						tr.setRotation(ornB);
						colObB.setWorldTransform(tr);
						setB.push_back(&colObB);
						setBLinkIndex.push_back(-2);
					}
					else
					{
						b3Warning("collisionShapeB provided is not valid.");
					}
				}

				if (bodyUniqueIdA >= 0)
				{
					InternalBodyData* bodyA = m_data->m_bodyHandles.getHandle(bodyUniqueIdA);
					if (bodyA)
					{
						if (bodyA->m_multiBody)
						{
							if (bodyA->m_multiBody->getBaseCollider())
							{
								if (!hasLinkIndexAFilter || (linkIndexA == -1))
								{
									setA.push_back(bodyA->m_multiBody->getBaseCollider());
									setALinkIndex.push_back(-1);
								}
							}
							for (int i = 0; i < bodyA->m_multiBody->getNumLinks(); i++)
							{
								if (bodyA->m_multiBody->getLink(i).m_collider)
								{
									if (!hasLinkIndexAFilter || (linkIndexA == i))
									{
										setA.push_back(bodyA->m_multiBody->getLink(i).m_collider);
										setALinkIndex.push_back(i);
									}
								}
							}
						}
						if (bodyA->m_rigidBody)
						{
							setA.push_back(bodyA->m_rigidBody);
							setALinkIndex.push_back(-1);
						}
					}
				}
				if (bodyUniqueIdB >= 0)
				{
					InternalBodyData* bodyB = m_data->m_bodyHandles.getHandle(bodyUniqueIdB);
					if (bodyB)
					{
						if (bodyB->m_multiBody)
						{
							if (bodyB->m_multiBody->getBaseCollider())
							{
								if (!hasLinkIndexBFilter || (linkIndexB == -1))
								{
									setB.push_back(bodyB->m_multiBody->getBaseCollider());
									setBLinkIndex.push_back(-1);
								}
							}
							for (int i = 0; i < bodyB->m_multiBody->getNumLinks(); i++)
							{
								if (bodyB->m_multiBody->getLink(i).m_collider)
								{
									if (!hasLinkIndexBFilter || (linkIndexB == i))
									{
										setB.push_back(bodyB->m_multiBody->getLink(i).m_collider);
										setBLinkIndex.push_back(i);
									}
								}
							}
						}
						if (bodyB->m_rigidBody)
						{
							setB.push_back(bodyB->m_rigidBody);
							setBLinkIndex.push_back(-1);
						}
					}
				}

				{
					///ContactResultCallback is used to report contact points
					struct MyContactResultCallback : public btCollisionWorld::ContactResultCallback
					{
						int m_bodyUniqueIdA;
						int m_bodyUniqueIdB;
						int m_linkIndexA;
						int m_linkIndexB;
						btScalar m_deltaTime;

						btAlignedObjectArray<b3ContactPointData>& m_cachedContactPoints;

						MyContactResultCallback(btAlignedObjectArray<b3ContactPointData>& pointCache)
							: m_cachedContactPoints(pointCache)
						{
						}

						virtual ~MyContactResultCallback()
						{
						}

						virtual bool needsCollision(btBroadphaseProxy* proxy0) const
						{
							//bool collides = (proxy0->m_collisionFilterGroup & m_collisionFilterMask) != 0;
							//collides = collides && (m_collisionFilterGroup & proxy0->m_collisionFilterMask);
							//return collides;
							return true;
						}

						virtual btScalar addSingleResult(btManifoldPoint& cp, const btCollisionObjectWrapper* colObj0Wrap, int partId0, int index0, const btCollisionObjectWrapper* colObj1Wrap, int partId1, int index1)
						{
							if (cp.m_distance1 <= m_closestDistanceThreshold)
							{
								b3ContactPointData pt;
								pt.m_bodyUniqueIdA = m_bodyUniqueIdA;
								pt.m_bodyUniqueIdB = m_bodyUniqueIdB;
								const btManifoldPoint& srcPt = cp;
								pt.m_contactDistance = srcPt.getDistance();
								pt.m_contactFlags = 0;
								pt.m_linkIndexA = m_linkIndexA;
								pt.m_linkIndexB = m_linkIndexB;
								for (int j = 0; j < 3; j++)
								{
									pt.m_contactNormalOnBInWS[j] = srcPt.m_normalWorldOnB[j];
									pt.m_positionOnAInWS[j] = srcPt.getPositionWorldOnA()[j];
									pt.m_positionOnBInWS[j] = srcPt.getPositionWorldOnB()[j];
								}
								pt.m_normalForce = srcPt.getAppliedImpulse() / m_deltaTime;
								//                                    pt.m_linearFrictionForce = srcPt.m_appliedImpulseLateral1;
								m_cachedContactPoints.push_back(pt);
							}
							return 1;
						}
					};

					MyContactResultCallback cb(m_data->m_cachedContactPoints);

					cb.m_bodyUniqueIdA = bodyUniqueIdA;
					cb.m_bodyUniqueIdB = bodyUniqueIdB;
					cb.m_deltaTime = m_data->m_physicsDeltaTime;

					for (int i = 0; i < setA.size(); i++)
					{
						cb.m_linkIndexA = setALinkIndex[i];
						for (int j = 0; j < setB.size(); j++)
						{
							cb.m_linkIndexB = setBLinkIndex[j];
							cb.m_closestDistanceThreshold = closestDistanceThreshold;
							this->m_data->m_dynamicsWorld->contactPairTest(setA[i], setB[j], cb);
						}
					}
				}

				break;
			}
			default:
			{
				b3Warning("Unknown contact query mode: %d", mode);
			}
		}
	}

	int numContactPoints = m_data->m_cachedContactPoints.size();

	//b3ContactPoint
	//struct b3ContactPointDynamics

	int totalBytesPerContact = sizeof(b3ContactPointData);
	int contactPointStorage = bufferSizeInBytes / totalBytesPerContact - 1;

	b3ContactPointData* contactData = (b3ContactPointData*)bufferServerToClient;

	int startContactPointIndex = clientCmd.m_requestContactPointArguments.m_startingContactPointIndex;
	int numContactPointBatch = btMin(numContactPoints, contactPointStorage);

	int endContactPointIndex = startContactPointIndex + numContactPointBatch;

	for (int i = startContactPointIndex; i < endContactPointIndex; i++)
	{
		const b3ContactPointData& srcPt = m_data->m_cachedContactPoints[i];
		b3ContactPointData& destPt = contactData[serverCmd.m_sendContactPointArgs.m_numContactPointsCopied];
		destPt = srcPt;
		serverCmd.m_sendContactPointArgs.m_numContactPointsCopied++;
	}

	serverCmd.m_sendContactPointArgs.m_startingContactPointIndex = clientCmd.m_requestContactPointArguments.m_startingContactPointIndex;
	serverCmd.m_sendContactPointArgs.m_numRemainingContactPoints = numContactPoints - clientCmd.m_requestContactPointArguments.m_startingContactPointIndex - serverCmd.m_sendContactPointArgs.m_numContactPointsCopied;
	serverCmd.m_numDataStreamBytes = totalBytesPerContact * serverCmd.m_sendContactPointArgs.m_numContactPointsCopied;
	serverCmd.m_type = CMD_CONTACT_POINT_INFORMATION_COMPLETED;  //CMD_CONTACT_POINT_INFORMATION_FAILED,

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestBodyInfoCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_REQUEST_BODY_INFO");

	const SdfRequestInfoArgs& sdfInfoArgs = clientCmd.m_sdfRequestInfoArgs;
	//stream info into memory
	int streamSizeInBytes = createBodyInfoStream(sdfInfoArgs.m_bodyUniqueId, bufferServerToClient, bufferSizeInBytes);

	serverStatusOut.m_type = CMD_BODY_INFO_COMPLETED;
	serverStatusOut.m_dataStreamArguments.m_bodyUniqueId = sdfInfoArgs.m_bodyUniqueId;
	serverStatusOut.m_dataStreamArguments.m_bodyName[0] = 0;

	InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(sdfInfoArgs.m_bodyUniqueId);
	if (bodyHandle)
	{
		strcpy(serverStatusOut.m_dataStreamArguments.m_bodyName, bodyHandle->m_bodyName.c_str());
	}

	serverStatusOut.m_numDataStreamBytes = streamSizeInBytes;

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processLoadSDFCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_LOAD_SDF");

	const SdfArgs& sdfArgs = clientCmd.m_sdfArguments;
	if (m_data->m_verboseOutput)
	{
		b3Printf("Processed CMD_LOAD_SDF:%s", sdfArgs.m_sdfFileName);
	}
	bool useMultiBody = (clientCmd.m_updateFlags & URDF_ARGS_USE_MULTIBODY) ? (sdfArgs.m_useMultiBody != 0) : true;

	int flags = CUF_USE_SDF;  //CUF_USE_URDF_INERTIA
	btScalar globalScaling = 1.f;
	if (clientCmd.m_updateFlags & URDF_ARGS_USE_GLOBAL_SCALING)
	{
		globalScaling = sdfArgs.m_globalScaling;
	}
	bool completedOk = loadSdf(sdfArgs.m_sdfFileName, bufferServerToClient, bufferSizeInBytes, useMultiBody, flags, globalScaling);
	if (completedOk)
	{
		m_data->m_guiHelper->autogenerateGraphicsObjects(this->m_data->m_dynamicsWorld);

		//serverStatusOut.m_type = CMD_SDF_LOADING_FAILED;
		serverStatusOut.m_sdfLoadedArgs.m_numBodies = m_data->m_sdfRecentLoadedBodies.size();
		serverStatusOut.m_sdfLoadedArgs.m_numUserConstraints = 0;
		int maxBodies = btMin(MAX_SDF_BODIES, serverStatusOut.m_sdfLoadedArgs.m_numBodies);
		for (int i = 0; i < maxBodies; i++)
		{
			serverStatusOut.m_sdfLoadedArgs.m_bodyUniqueIds[i] = m_data->m_sdfRecentLoadedBodies[i];
		}

		serverStatusOut.m_type = CMD_SDF_LOADING_COMPLETED;
	}
	else
	{
		serverStatusOut.m_type = CMD_SDF_LOADING_FAILED;
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCreateMultiBodyCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	serverStatusOut.m_type = CMD_CREATE_MULTI_BODY_FAILED;
	if (clientCmd.m_createMultiBodyArgs.m_baseLinkIndex >= 0)
	{
		m_data->m_sdfRecentLoadedBodies.clear();

		ProgrammaticUrdfInterface u2b(clientCmd.m_createMultiBodyArgs, m_data);

		bool useMultiBody = true;
		if (clientCmd.m_updateFlags & MULT_BODY_USE_MAXIMAL_COORDINATES)
		{
			useMultiBody = false;
		}

		int flags = 0;

		if (clientCmd.m_updateFlags & MULT_BODY_HAS_FLAGS)
		{
			flags = clientCmd.m_createMultiBodyArgs.m_flags;
		}

		bool ok = processImportedObjects("memory", bufferServerToClient, bufferSizeInBytes, useMultiBody, flags, u2b);

		if (ok)
		{
			int bodyUniqueId = -1;

			if (m_data->m_sdfRecentLoadedBodies.size() == 1)
			{
				bodyUniqueId = m_data->m_sdfRecentLoadedBodies[0];
			}
			m_data->m_sdfRecentLoadedBodies.clear();
			if (bodyUniqueId >= 0)
			{
				m_data->m_guiHelper->autogenerateGraphicsObjects(this->m_data->m_dynamicsWorld);
				serverStatusOut.m_type = CMD_CREATE_MULTI_BODY_COMPLETED;

				int streamSizeInBytes = createBodyInfoStream(bodyUniqueId, bufferServerToClient, bufferSizeInBytes);
				serverStatusOut.m_numDataStreamBytes = streamSizeInBytes;

				serverStatusOut.m_dataStreamArguments.m_bodyUniqueId = bodyUniqueId;
				InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);
				strcpy(serverStatusOut.m_dataStreamArguments.m_bodyName, body->m_bodyName.c_str());
			}
		}

		//ConvertURDF2Bullet(u2b,creation, rootTrans,m_data->m_dynamicsWorld,useMultiBody,u2b.getPathPrefix(),flags);
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processLoadURDFCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	serverStatusOut.m_type = CMD_URDF_LOADING_FAILED;

	BT_PROFILE("CMD_LOAD_URDF");
	const UrdfArgs& urdfArgs = clientCmd.m_urdfArguments;
	if (m_data->m_verboseOutput)
	{
		b3Printf("Processed CMD_LOAD_URDF:%s", urdfArgs.m_urdfFileName);
	}
	btAssert((clientCmd.m_updateFlags & URDF_ARGS_FILE_NAME) != 0);
	btAssert(urdfArgs.m_urdfFileName);
	btVector3 initialPos(0, 0, 0);
	btQuaternion initialOrn(0, 0, 0, 1);
	if (clientCmd.m_updateFlags & URDF_ARGS_INITIAL_POSITION)
	{
		initialPos[0] = urdfArgs.m_initialPosition[0];
		initialPos[1] = urdfArgs.m_initialPosition[1];
		initialPos[2] = urdfArgs.m_initialPosition[2];
	}
	int urdfFlags = 0;
	if (clientCmd.m_updateFlags & URDF_ARGS_HAS_CUSTOM_URDF_FLAGS)
	{
		urdfFlags = urdfArgs.m_urdfFlags;
	}
	if (clientCmd.m_updateFlags & URDF_ARGS_INITIAL_ORIENTATION)
	{
		initialOrn[0] = urdfArgs.m_initialOrientation[0];
		initialOrn[1] = urdfArgs.m_initialOrientation[1];
		initialOrn[2] = urdfArgs.m_initialOrientation[2];
		initialOrn[3] = urdfArgs.m_initialOrientation[3];
	}
	bool useMultiBody = (clientCmd.m_updateFlags & URDF_ARGS_USE_MULTIBODY) ? (urdfArgs.m_useMultiBody != 0) : true;
	bool useFixedBase = (clientCmd.m_updateFlags & URDF_ARGS_USE_FIXED_BASE) ? (urdfArgs.m_useFixedBase != 0) : false;
	int bodyUniqueId;
	btScalar globalScaling = 1.f;
	if (clientCmd.m_updateFlags & URDF_ARGS_USE_GLOBAL_SCALING)
	{
		globalScaling = urdfArgs.m_globalScaling;
	}
	//load the actual URDF and send a report: completed or failed
	bool completedOk = loadUrdf(urdfArgs.m_urdfFileName,
								initialPos, initialOrn,
								useMultiBody, useFixedBase, &bodyUniqueId, bufferServerToClient, bufferSizeInBytes, urdfFlags, globalScaling);

	if (completedOk && bodyUniqueId >= 0)
	{
		m_data->m_guiHelper->autogenerateGraphicsObjects(this->m_data->m_dynamicsWorld);

		serverStatusOut.m_type = CMD_URDF_LOADING_COMPLETED;

		int streamSizeInBytes = createBodyInfoStream(bodyUniqueId, bufferServerToClient, bufferSizeInBytes);
		serverStatusOut.m_numDataStreamBytes = streamSizeInBytes;

#ifdef ENABLE_LINK_MAPPER
		if (m_data->m_urdfLinkNameMapper.size())
		{
			serverStatusOut.m_numDataStreamBytes = m_data->m_urdfLinkNameMapper.at(m_data->m_urdfLinkNameMapper.size() - 1)->m_memSerializer->getCurrentBufferSize();
		}
#endif
		serverStatusOut.m_dataStreamArguments.m_bodyUniqueId = bodyUniqueId;
		InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);
		strcpy(serverStatusOut.m_dataStreamArguments.m_bodyName, body->m_bodyName.c_str());
	}

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processLoadSoftBodyCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	serverStatusOut.m_type = CMD_LOAD_SOFT_BODY_FAILED;
	bool hasStatus = true;
#ifndef SKIP_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD
	double scale = 0.1;
	double mass = 0.1;
	double collisionMargin = 0.02;
	const LoadSoftBodyArgs& loadSoftBodyArgs = clientCmd.m_loadSoftBodyArguments;
	if (m_data->m_verboseOutput)
	{
		b3Printf("Processed CMD_LOAD_SOFT_BODY:%s", loadSoftBodyArgs.m_fileName);
	}
	btAssert((clientCmd.m_updateFlags & LOAD_SOFT_BODY_FILE_NAME) != 0);
	btAssert(loadSoftBodyArgs.m_fileName);

	if (clientCmd.m_updateFlags & LOAD_SOFT_BODY_UPDATE_SCALE)
	{
		scale = clientCmd.m_loadSoftBodyArguments.m_scale;
	}
	if (clientCmd.m_updateFlags & LOAD_SOFT_BODY_UPDATE_MASS)
	{
		mass = clientCmd.m_loadSoftBodyArguments.m_mass;
	}
	if (clientCmd.m_updateFlags & LOAD_SOFT_BODY_UPDATE_COLLISION_MARGIN)
	{
		collisionMargin = clientCmd.m_loadSoftBodyArguments.m_collisionMargin;
	}

	{
		char relativeFileName[1024];
		char pathPrefix[1024];
		pathPrefix[0] = 0;
		if (b3ResourcePath::findResourcePath(loadSoftBodyArgs.m_fileName, relativeFileName, 1024))
		{
			b3FileUtils::extractPath(relativeFileName, pathPrefix, 1024);
		}
		const std::string& error_message_prefix = "";
		std::string out_found_filename;
		int out_type;
		bool foundFile = findExistingMeshFile(pathPrefix, relativeFileName, error_message_prefix, &out_found_filename, &out_type);
		std::vector<tinyobj::shape_t> shapes;
		std::string err = tinyobj::LoadObj(shapes, out_found_filename.c_str());
		if (shapes.size() > 0)
		{
			const tinyobj::shape_t& shape = shapes[0];
			btAlignedObjectArray<btScalar> vertices;
			btAlignedObjectArray<int> indices;
			for (int i = 0; i < shape.mesh.positions.size(); i++)
			{
				vertices.push_back(shape.mesh.positions[i]);
			}
			for (int i = 0; i < shape.mesh.indices.size(); i++)
			{
				indices.push_back(shape.mesh.indices[i]);
			}
			int numTris = indices.size() / 3;
			if (numTris > 0)
			{
				btSoftBody* psb = btSoftBodyHelpers::CreateFromTriMesh(m_data->m_dynamicsWorld->getWorldInfo(), &vertices[0], &indices[0], numTris);
				btSoftBody::Material* pm = psb->appendMaterial();
				pm->m_kLST = 0.5;
				pm->m_flags -= btSoftBody::fMaterial::DebugDraw;
				psb->generateBendingConstraints(2, pm);
				psb->m_cfg.piterations = 20;
				psb->m_cfg.kDF = 0.5;
				psb->randomizeConstraints();
				psb->rotate(btQuaternion(0.70711, 0, 0, 0.70711));
				psb->translate(btVector3(-0.05, 0, 1.0));
				psb->scale(btVector3(scale, scale, scale));

				psb->setTotalMass(mass, true);
				psb->getCollisionShape()->setMargin(collisionMargin);
				psb->getCollisionShape()->setUserPointer(psb);
				m_data->m_dynamicsWorld->addSoftBody(psb);
				m_data->m_guiHelper->createCollisionShapeGraphicsObject(psb->getCollisionShape());
				m_data->m_guiHelper->autogenerateGraphicsObjects(this->m_data->m_dynamicsWorld);
				int bodyUniqueId = m_data->m_bodyHandles.allocHandle();
				InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(bodyUniqueId);
				bodyHandle->m_softBody = psb;
				serverStatusOut.m_loadSoftBodyResultArguments.m_objectUniqueId = bodyUniqueId;
				serverStatusOut.m_type = CMD_LOAD_SOFT_BODY_COMPLETED;

				b3Notification notification;
				notification.m_notificationType = BODY_ADDED;
				notification.m_bodyArgs.m_bodyUniqueId = bodyUniqueId;
				m_data->m_pluginManager.addNotification(notification);
			}
		}
	}

#endif
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCreateSensorCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_CREATE_SENSOR");

	if (m_data->m_verboseOutput)
	{
		b3Printf("Processed CMD_CREATE_SENSOR");
	}
	int bodyUniqueId = clientCmd.m_createSensorArguments.m_bodyUniqueId;
	InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);
	if (body && body->m_multiBody)
	{
		btMultiBody* mb = body->m_multiBody;
		btAssert(mb);
		for (int i = 0; i < clientCmd.m_createSensorArguments.m_numJointSensorChanges; i++)
		{
			int jointIndex = clientCmd.m_createSensorArguments.m_jointIndex[i];
			if (clientCmd.m_createSensorArguments.m_enableJointForceSensor[i])
			{
				if (mb->getLink(jointIndex).m_jointFeedback)
				{
					b3Warning("CMD_CREATE_SENSOR: sensor for joint [%d] already enabled", jointIndex);
				}
				else
				{
					btMultiBodyJointFeedback* fb = new btMultiBodyJointFeedback();
					fb->m_reactionForces.setZero();
					mb->getLink(jointIndex).m_jointFeedback = fb;
					m_data->m_multiBodyJointFeedbacks.push_back(fb);
				};
			}
			else
			{
				if (mb->getLink(jointIndex).m_jointFeedback)
				{
					m_data->m_multiBodyJointFeedbacks.remove(mb->getLink(jointIndex).m_jointFeedback);
					delete mb->getLink(jointIndex).m_jointFeedback;
					mb->getLink(jointIndex).m_jointFeedback = 0;
				}
				else
				{
					b3Warning("CMD_CREATE_SENSOR: cannot perform sensor removal request, no sensor on joint [%d]", jointIndex);
				};
			}
		}
	}
	else
	{
		b3Warning("No btMultiBody in the world. btRigidBody/btTypedConstraint sensor not hooked up yet");
	}

#if 0
    //todo(erwincoumans) here is some sample code to hook up a force/torque sensor for btTypedConstraint/btRigidBody
    /*
        for (int i=0;i<m_data->m_dynamicsWorld->getNumConstraints();i++)
        {
        btTypedConstraint* c = m_data->m_dynamicsWorld->getConstraint(i);
        btJointFeedback* fb = new btJointFeedback();
        m_data->m_jointFeedbacks.push_back(fb);
        c->setJointFeedback(fb);


        }
        */
#endif

	serverStatusOut.m_type = CMD_CLIENT_COMMAND_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processProfileTimingCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	B3_PROFILE("custom");  //clientCmd.m_profile.m_name);
	{
		B3_PROFILE("event");  //clientCmd.m_profile.m_name);
		char** eventNamePtr = m_data->m_profileEvents[clientCmd.m_profile.m_name];
		char* eventName = 0;
		if (eventNamePtr)
		{
			B3_PROFILE("reuse");
			eventName = *eventNamePtr;
		}
		else
		{
			B3_PROFILE("alloc");
			int len = strlen(clientCmd.m_profile.m_name);
			eventName = new char[len + 1];
			strcpy(eventName, clientCmd.m_profile.m_name);
			eventName[len] = 0;
			m_data->m_profileEvents.insert(eventName, eventName);
		}

		{
			{
				B3_PROFILE("with");  //clientCmd.m_profile.m_name);
				{
					B3_PROFILE("some");  //clientCmd.m_profile.m_name);
					{
						B3_PROFILE("deep");  //clientCmd.m_profile.m_name);
						{
							B3_PROFILE("level");  //clientCmd.m_profile.m_name);
							{
								B3_PROFILE(eventName);
								b3Clock::usleep(clientCmd.m_profile.m_durationInMicroSeconds);
							}
						}
					}
				}
			}
		}
	}

	serverStatusOut.m_type = CMD_CLIENT_COMMAND_COMPLETED;
	hasStatus = true;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestCollisionInfoCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverStatusOut.m_type = CMD_REQUEST_COLLISION_INFO_FAILED;
	hasStatus = true;
	int bodyUniqueId = clientCmd.m_requestCollisionInfoArgs.m_bodyUniqueId;
	InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);

	if (body && body->m_multiBody)
	{
		btMultiBody* mb = body->m_multiBody;
		serverStatusOut.m_type = CMD_REQUEST_COLLISION_INFO_COMPLETED;
		serverCmd.m_sendCollisionInfoArgs.m_numLinks = body->m_multiBody->getNumLinks();
		serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[0] = 0;
		serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[1] = 0;
		serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[2] = 0;

		serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[0] = -1;
		serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[1] = -1;
		serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[2] = -1;

		if (body->m_multiBody->getBaseCollider())
		{
			btTransform tr;
			tr.setOrigin(mb->getBasePos());
			tr.setRotation(mb->getWorldToBaseRot().inverse());

			btVector3 aabbMin, aabbMax;
			body->m_multiBody->getBaseCollider()->getCollisionShape()->getAabb(tr, aabbMin, aabbMax);
			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[0] = aabbMin[0];
			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[1] = aabbMin[1];
			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[2] = aabbMin[2];

			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[0] = aabbMax[0];
			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[1] = aabbMax[1];
			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[2] = aabbMax[2];
		}
		for (int l = 0; l < mb->getNumLinks(); l++)
		{
			serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMin[3 * l + 0] = 0;
			serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMin[3 * l + 1] = 0;
			serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMin[3 * l + 2] = 0;

			serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMax[3 * l + 0] = -1;
			serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMax[3 * l + 1] = -1;
			serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMax[3 * l + 2] = -1;

			if (body->m_multiBody->getLink(l).m_collider)
			{
				btVector3 aabbMin, aabbMax;
				body->m_multiBody->getLinkCollider(l)->getCollisionShape()->getAabb(mb->getLink(l).m_cachedWorldTransform, aabbMin, aabbMax);

				serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMin[3 * l + 0] = aabbMin[0];
				serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMin[3 * l + 1] = aabbMin[1];
				serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMin[3 * l + 2] = aabbMin[2];
				serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMax[3 * l + 0] = aabbMax[0];
				serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMax[3 * l + 1] = aabbMax[1];
				serverCmd.m_sendCollisionInfoArgs.m_linkWorldAABBsMax[3 * l + 2] = aabbMax[2];
			}
		}
	}
	else
	{
		if (body && body->m_rigidBody)
		{
			btRigidBody* rb = body->m_rigidBody;
			SharedMemoryStatus& serverCmd = serverStatusOut;
			serverStatusOut.m_type = CMD_REQUEST_COLLISION_INFO_COMPLETED;
			serverCmd.m_sendCollisionInfoArgs.m_numLinks = 0;
			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[0] = 0;
			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[1] = 0;
			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[2] = 0;

			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[0] = -1;
			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[1] = -1;
			serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[2] = -1;
			if (rb->getCollisionShape())
			{
				btTransform tr = rb->getWorldTransform();

				btVector3 aabbMin, aabbMax;
				rb->getCollisionShape()->getAabb(tr, aabbMin, aabbMax);
				serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[0] = aabbMin[0];
				serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[1] = aabbMin[1];
				serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMin[2] = aabbMin[2];

				serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[0] = aabbMax[0];
				serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[1] = aabbMax[1];
				serverCmd.m_sendCollisionInfoArgs.m_rootWorldAABBMax[2] = aabbMax[2];
			}
		}
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processForwardDynamicsCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_STEP_FORWARD_SIMULATION");

	if (m_data->m_verboseOutput)
	{
		b3Printf("Step simulation request");
		b3Printf("CMD_STEP_FORWARD_SIMULATION clientCmd = %d\n", clientCmd.m_sequenceNumber);
	}
	///todo(erwincoumans) move this damping inside Bullet
	for (int i = 0; i < m_data->m_dynamicsWorld->getNumMultibodies(); i++)
	{
		btMultiBody* mb = m_data->m_dynamicsWorld->getMultiBody(i);
		for (int l = 0; l < mb->getNumLinks(); l++)
		{
			for (int d = 0; d < mb->getLink(l).m_dofCount; d++)
			{
				double damping_coefficient = mb->getLink(l).m_jointDamping;
				double damping = -damping_coefficient * mb->getJointVelMultiDof(l)[d];
				mb->addJointTorqueMultiDof(l, d, damping);
			}
		}
	}

	btScalar deltaTimeScaled = m_data->m_physicsDeltaTime * simTimeScalingFactor;

	int numSteps = 0;
	if (m_data->m_numSimulationSubSteps > 0)
	{
		numSteps = m_data->m_dynamicsWorld->stepSimulation(deltaTimeScaled, m_data->m_numSimulationSubSteps, m_data->m_physicsDeltaTime / m_data->m_numSimulationSubSteps);
	}
	else
	{
		numSteps = m_data->m_dynamicsWorld->stepSimulation(deltaTimeScaled, 0);
	}

	if (numSteps > 0)
	{
		addTransformChangedNotifications();
	}

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_STEP_FORWARD_SIMULATION_COMPLETED;

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestInternalDataCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_REQUEST_INTERNAL_DATA");

	//todo: also check version etc?

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_REQUEST_INTERNAL_DATA_FAILED;

	int sz = btDefaultSerializer::getMemoryDnaSizeInBytes();
	const char* memDna = btDefaultSerializer::getMemoryDna();
	if (sz < bufferSizeInBytes)
	{
		for (int i = 0; i < sz; i++)
		{
			bufferServerToClient[i] = memDna[i];
		}
		serverCmd.m_type = CMD_REQUEST_INTERNAL_DATA_COMPLETED;
		serverCmd.m_numDataStreamBytes = sz;
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processChangeDynamicsInfoCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_CHANGE_DYNAMICS_INFO");

	int bodyUniqueId = clientCmd.m_changeDynamicsInfoArgs.m_bodyUniqueId;
	int linkIndex = clientCmd.m_changeDynamicsInfoArgs.m_linkIndex;
	double mass = clientCmd.m_changeDynamicsInfoArgs.m_mass;
	double lateralFriction = clientCmd.m_changeDynamicsInfoArgs.m_lateralFriction;
	double spinningFriction = clientCmd.m_changeDynamicsInfoArgs.m_spinningFriction;
	double rollingFriction = clientCmd.m_changeDynamicsInfoArgs.m_rollingFriction;
	double restitution = clientCmd.m_changeDynamicsInfoArgs.m_restitution;
	btVector3 newLocalInertiaDiagonal(clientCmd.m_changeDynamicsInfoArgs.m_localInertiaDiagonal[0],
									  clientCmd.m_changeDynamicsInfoArgs.m_localInertiaDiagonal[1],
									  clientCmd.m_changeDynamicsInfoArgs.m_localInertiaDiagonal[2]);

	btAssert(bodyUniqueId >= 0);

	InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);

	if (body && body->m_multiBody)
	{
		btMultiBody* mb = body->m_multiBody;

		if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_ACTIVATION_STATE)
		{
			if (clientCmd.m_changeDynamicsInfoArgs.m_activationState & eActivationStateWakeUp)
			{
				mb->wakeUp();
			}
			if (clientCmd.m_changeDynamicsInfoArgs.m_activationState & eActivationStateSleep)
			{
				mb->goToSleep();
			}
			if (clientCmd.m_changeDynamicsInfoArgs.m_activationState & eActivationStateEnableSleeping)
			{
				mb->setCanSleep(true);
			}
			if (clientCmd.m_changeDynamicsInfoArgs.m_activationState & eActivationStateDisableSleeping)
			{
				mb->setCanSleep(false);
			}
		}

		if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_LINEAR_DAMPING)
		{
			mb->setLinearDamping(clientCmd.m_changeDynamicsInfoArgs.m_linearDamping);
		}
		if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_ANGULAR_DAMPING)
		{
			mb->setAngularDamping(clientCmd.m_changeDynamicsInfoArgs.m_angularDamping);
		}

		if (linkIndex == -1)
		{
			if (mb->getBaseCollider())
			{
				if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_RESTITUTION)
				{
					mb->getBaseCollider()->setRestitution(restitution);
				}

				if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_CONTACT_STIFFNESS_AND_DAMPING)
				{
					mb->getBaseCollider()->setContactStiffnessAndDamping(clientCmd.m_changeDynamicsInfoArgs.m_contactStiffness, clientCmd.m_changeDynamicsInfoArgs.m_contactDamping);
				}
				if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_LATERAL_FRICTION)
				{
					mb->getBaseCollider()->setFriction(lateralFriction);
				}
				if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_SPINNING_FRICTION)
				{
					mb->getBaseCollider()->setSpinningFriction(spinningFriction);
				}
				if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_ROLLING_FRICTION)
				{
					mb->getBaseCollider()->setRollingFriction(rollingFriction);
				}

				if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_FRICTION_ANCHOR)
				{
					if (clientCmd.m_changeDynamicsInfoArgs.m_frictionAnchor)
					{
						mb->getBaseCollider()->setCollisionFlags(mb->getBaseCollider()->getCollisionFlags() | btCollisionObject::CF_HAS_FRICTION_ANCHOR);
					}
					else
					{
						mb->getBaseCollider()->setCollisionFlags(mb->getBaseCollider()->getCollisionFlags() & ~btCollisionObject::CF_HAS_FRICTION_ANCHOR);
					}
				}
			}
			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_MASS)
			{
				mb->setBaseMass(mass);
				if (mb->getBaseCollider() && mb->getBaseCollider()->getCollisionShape())
				{
					btVector3 localInertia;
					mb->getBaseCollider()->getCollisionShape()->calculateLocalInertia(mass, localInertia);
					mb->setBaseInertia(localInertia);
				}
			}
			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_LOCAL_INERTIA_DIAGONAL)
			{
				mb->setBaseInertia(newLocalInertiaDiagonal);
			}
		}
		else
		{
			if (linkIndex >= 0 && linkIndex < mb->getNumLinks())
			{
				if (mb->getLinkCollider(linkIndex))
				{
					if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_RESTITUTION)
					{
						mb->getLinkCollider(linkIndex)->setRestitution(restitution);
					}
					if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_SPINNING_FRICTION)
					{
						mb->getLinkCollider(linkIndex)->setSpinningFriction(spinningFriction);
					}
					if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_ROLLING_FRICTION)
					{
						mb->getLinkCollider(linkIndex)->setRollingFriction(rollingFriction);
					}

					if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_FRICTION_ANCHOR)
					{
						if (clientCmd.m_changeDynamicsInfoArgs.m_frictionAnchor)
						{
							mb->getLinkCollider(linkIndex)->setCollisionFlags(mb->getLinkCollider(linkIndex)->getCollisionFlags() | btCollisionObject::CF_HAS_FRICTION_ANCHOR);
						}
						else
						{
							mb->getLinkCollider(linkIndex)->setCollisionFlags(mb->getLinkCollider(linkIndex)->getCollisionFlags() & ~btCollisionObject::CF_HAS_FRICTION_ANCHOR);
						}
					}

					if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_LATERAL_FRICTION)
					{
						mb->getLinkCollider(linkIndex)->setFriction(lateralFriction);
					}

					if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_CONTACT_STIFFNESS_AND_DAMPING)
					{
						mb->getLinkCollider(linkIndex)->setContactStiffnessAndDamping(clientCmd.m_changeDynamicsInfoArgs.m_contactStiffness, clientCmd.m_changeDynamicsInfoArgs.m_contactDamping);
					}
				}
				if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_MASS)
				{
					mb->getLink(linkIndex).m_mass = mass;
					if (mb->getLinkCollider(linkIndex) && mb->getLinkCollider(linkIndex)->getCollisionShape())
					{
						btVector3 localInertia;
						mb->getLinkCollider(linkIndex)->getCollisionShape()->calculateLocalInertia(mass, localInertia);
						mb->getLink(linkIndex).m_inertiaLocal = localInertia;
					}
				}
				if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_LOCAL_INERTIA_DIAGONAL)
				{
					mb->getLink(linkIndex).m_inertiaLocal = newLocalInertiaDiagonal;
				}
			}
		}
	}
	else
	{
		if (body && body->m_rigidBody)
		{
			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_ACTIVATION_STATE)
			{
				if (clientCmd.m_changeDynamicsInfoArgs.m_activationState & eActivationStateEnableSleeping)
				{
					body->m_rigidBody->forceActivationState(ACTIVE_TAG);
				}
				if (clientCmd.m_changeDynamicsInfoArgs.m_activationState & eActivationStateDisableSleeping)
				{
					body->m_rigidBody->forceActivationState(DISABLE_DEACTIVATION);
				}
				if (clientCmd.m_changeDynamicsInfoArgs.m_activationState & eActivationStateWakeUp)
				{
					body->m_rigidBody->forceActivationState(ACTIVE_TAG);
				}
				if (clientCmd.m_changeDynamicsInfoArgs.m_activationState & eActivationStateSleep)
				{
					body->m_rigidBody->forceActivationState(ISLAND_SLEEPING);
				}
			}

			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_LINEAR_DAMPING)
			{
				btScalar angDamping = body->m_rigidBody->getAngularDamping();
				body->m_rigidBody->setDamping(clientCmd.m_changeDynamicsInfoArgs.m_linearDamping, angDamping);
			}
			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_ANGULAR_DAMPING)
			{
				btScalar linDamping = body->m_rigidBody->getLinearDamping();
				body->m_rigidBody->setDamping(linDamping, clientCmd.m_changeDynamicsInfoArgs.m_angularDamping);
			}

			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_CONTACT_STIFFNESS_AND_DAMPING)
			{
				body->m_rigidBody->setContactStiffnessAndDamping(clientCmd.m_changeDynamicsInfoArgs.m_contactStiffness, clientCmd.m_changeDynamicsInfoArgs.m_contactDamping);
			}
			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_RESTITUTION)
			{
				body->m_rigidBody->setRestitution(restitution);
			}
			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_LATERAL_FRICTION)
			{
				body->m_rigidBody->setFriction(lateralFriction);
			}
			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_SPINNING_FRICTION)
			{
				body->m_rigidBody->setSpinningFriction(spinningFriction);
			}
			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_ROLLING_FRICTION)
			{
				body->m_rigidBody->setRollingFriction(rollingFriction);
			}

			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_FRICTION_ANCHOR)
			{
				if (clientCmd.m_changeDynamicsInfoArgs.m_frictionAnchor)
				{
					body->m_rigidBody->setCollisionFlags(body->m_rigidBody->getCollisionFlags() | btCollisionObject::CF_HAS_FRICTION_ANCHOR);
				}
				else
				{
					body->m_rigidBody->setCollisionFlags(body->m_rigidBody->getCollisionFlags() & ~btCollisionObject::CF_HAS_FRICTION_ANCHOR);
				}
			}

			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_MASS)
			{
				btVector3 localInertia;
				if (body->m_rigidBody->getCollisionShape())
				{
					body->m_rigidBody->getCollisionShape()->calculateLocalInertia(mass, localInertia);
				}
				body->m_rigidBody->setMassProps(mass, localInertia);
			}
			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_LOCAL_INERTIA_DIAGONAL)
			{
				btScalar orgMass = body->m_rigidBody->getInvMass();
				if (orgMass > 0)
				{
					body->m_rigidBody->setMassProps(mass, newLocalInertiaDiagonal);
				}
			}

			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_CONTACT_PROCESSING_THRESHOLD)
			{
				body->m_rigidBody->setContactProcessingThreshold(clientCmd.m_changeDynamicsInfoArgs.m_contactProcessingThreshold);
			}

			if (clientCmd.m_updateFlags & CHANGE_DYNAMICS_INFO_SET_CCD_SWEPT_SPHERE_RADIUS)
			{
				body->m_rigidBody->setCcdSweptSphereRadius(clientCmd.m_changeDynamicsInfoArgs.m_ccdSweptSphereRadius);
				//for a given sphere radius, use a motion threshold of half the radius, before the ccd algorithm is enabled
				body->m_rigidBody->setCcdMotionThreshold(clientCmd.m_changeDynamicsInfoArgs.m_ccdSweptSphereRadius / 2.);
			}
		}
	}

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CLIENT_COMMAND_COMPLETED;

	b3Notification notification;
	notification.m_notificationType = LINK_DYNAMICS_CHANGED;
	notification.m_linkArgs.m_bodyUniqueId = bodyUniqueId;
	notification.m_linkArgs.m_linkIndex = linkIndex;
	m_data->m_pluginManager.addNotification(notification);

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processSetAdditionalSearchPathCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_SET_ADDITIONAL_SEARCH_PATH");
	b3ResourcePath::setAdditionalSearchPath(clientCmd.m_searchPathArgs.m_path);
	serverStatusOut.m_type = CMD_CLIENT_COMMAND_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processGetDynamicsInfoCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_GET_DYNAMICS_INFO_FAILED;

	int bodyUniqueId = clientCmd.m_getDynamicsInfoArgs.m_bodyUniqueId;
	int linkIndex = clientCmd.m_getDynamicsInfoArgs.m_linkIndex;
	InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);
	if (body && body->m_multiBody)
	{
		SharedMemoryStatus& serverCmd = serverStatusOut;
		serverCmd.m_type = CMD_GET_DYNAMICS_INFO_COMPLETED;

		btMultiBody* mb = body->m_multiBody;
		if (linkIndex == -1)
		{
			serverCmd.m_dynamicsInfo.m_mass = mb->getBaseMass();
			if (mb->getBaseCollider())
			{
				serverCmd.m_dynamicsInfo.m_activationState = mb->getBaseCollider()->getActivationState();
				serverCmd.m_dynamicsInfo.m_contactProcessingThreshold = mb->getBaseCollider()->getContactProcessingThreshold();
				serverCmd.m_dynamicsInfo.m_ccdSweptSphereRadius = mb->getBaseCollider()->getCcdSweptSphereRadius();
				serverCmd.m_dynamicsInfo.m_frictionAnchor = mb->getBaseCollider()->getCollisionFlags() & btCollisionObject::CF_HAS_FRICTION_ANCHOR;
			}
			else
			{
				serverCmd.m_dynamicsInfo.m_activationState = 0;
				serverCmd.m_dynamicsInfo.m_contactProcessingThreshold = 0;
				serverCmd.m_dynamicsInfo.m_ccdSweptSphereRadius = 0;
				serverCmd.m_dynamicsInfo.m_frictionAnchor = 0;
			}
			serverCmd.m_dynamicsInfo.m_localInertialDiagonal[0] = mb->getBaseInertia()[0];
			serverCmd.m_dynamicsInfo.m_localInertialDiagonal[1] = mb->getBaseInertia()[1];
			serverCmd.m_dynamicsInfo.m_localInertialDiagonal[2] = mb->getBaseInertia()[2];
			serverCmd.m_dynamicsInfo.m_lateralFrictionCoeff = mb->getBaseCollider()->getFriction();

			serverCmd.m_dynamicsInfo.m_localInertialFrame[0] = body->m_rootLocalInertialFrame.getOrigin()[0];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[1] = body->m_rootLocalInertialFrame.getOrigin()[1];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[2] = body->m_rootLocalInertialFrame.getOrigin()[2];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[3] = body->m_rootLocalInertialFrame.getRotation()[0];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[4] = body->m_rootLocalInertialFrame.getRotation()[1];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[5] = body->m_rootLocalInertialFrame.getRotation()[2];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[6] = body->m_rootLocalInertialFrame.getRotation()[3];

			serverCmd.m_dynamicsInfo.m_angularDamping = body->m_multiBody->getAngularDamping();
			serverCmd.m_dynamicsInfo.m_linearDamping = body->m_multiBody->getLinearDamping();

			serverCmd.m_dynamicsInfo.m_restitution = mb->getBaseCollider()->getRestitution();
			serverCmd.m_dynamicsInfo.m_rollingFrictionCoeff = mb->getBaseCollider()->getRollingFriction();
			serverCmd.m_dynamicsInfo.m_spinningFrictionCoeff = mb->getBaseCollider()->getSpinningFriction();

			if (mb->getBaseCollider()->getCollisionFlags() & btCollisionObject::CF_HAS_CONTACT_STIFFNESS_DAMPING)
			{
				serverCmd.m_dynamicsInfo.m_contactStiffness = mb->getBaseCollider()->getContactStiffness();
				serverCmd.m_dynamicsInfo.m_contactDamping = mb->getBaseCollider()->getContactDamping();
			}
			else
			{
				serverCmd.m_dynamicsInfo.m_contactStiffness = -1;
				serverCmd.m_dynamicsInfo.m_contactDamping = -1;
			}
		}
		else
		{
			serverCmd.m_dynamicsInfo.m_mass = mb->getLinkMass(linkIndex);

			if (mb->getLinkCollider(linkIndex))
			{
				serverCmd.m_dynamicsInfo.m_activationState = mb->getLinkCollider(linkIndex)->getActivationState();
				serverCmd.m_dynamicsInfo.m_contactProcessingThreshold = mb->getLinkCollider(linkIndex)->getContactProcessingThreshold();
				serverCmd.m_dynamicsInfo.m_ccdSweptSphereRadius = mb->getLinkCollider(linkIndex)->getCcdSweptSphereRadius();
				serverCmd.m_dynamicsInfo.m_frictionAnchor = mb->getLinkCollider(linkIndex)->getCollisionFlags() & btCollisionObject::CF_HAS_FRICTION_ANCHOR;
			}
			else
			{
				serverCmd.m_dynamicsInfo.m_activationState = 0;
				serverCmd.m_dynamicsInfo.m_contactProcessingThreshold = 0;
				serverCmd.m_dynamicsInfo.m_ccdSweptSphereRadius = 0;
				serverCmd.m_dynamicsInfo.m_frictionAnchor = 0;
			}

			serverCmd.m_dynamicsInfo.m_localInertialDiagonal[0] = mb->getLinkInertia(linkIndex)[0];
			serverCmd.m_dynamicsInfo.m_localInertialDiagonal[1] = mb->getLinkInertia(linkIndex)[1];
			serverCmd.m_dynamicsInfo.m_localInertialDiagonal[2] = mb->getLinkInertia(linkIndex)[2];

			serverCmd.m_dynamicsInfo.m_localInertialFrame[0] = body->m_linkLocalInertialFrames[linkIndex].getOrigin()[0];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[1] = body->m_linkLocalInertialFrames[linkIndex].getOrigin()[1];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[2] = body->m_linkLocalInertialFrames[linkIndex].getOrigin()[2];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[3] = body->m_linkLocalInertialFrames[linkIndex].getRotation()[0];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[4] = body->m_linkLocalInertialFrames[linkIndex].getRotation()[1];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[5] = body->m_linkLocalInertialFrames[linkIndex].getRotation()[2];
			serverCmd.m_dynamicsInfo.m_localInertialFrame[6] = body->m_linkLocalInertialFrames[linkIndex].getRotation()[3];

			serverCmd.m_dynamicsInfo.m_angularDamping = body->m_multiBody->getAngularDamping();
			serverCmd.m_dynamicsInfo.m_linearDamping = body->m_multiBody->getLinearDamping();

			if (mb->getLinkCollider(linkIndex))
			{
				serverCmd.m_dynamicsInfo.m_lateralFrictionCoeff = mb->getLinkCollider(linkIndex)->getFriction();
				serverCmd.m_dynamicsInfo.m_restitution = mb->getLinkCollider(linkIndex)->getRestitution();
				serverCmd.m_dynamicsInfo.m_rollingFrictionCoeff = mb->getLinkCollider(linkIndex)->getRollingFriction();
				serverCmd.m_dynamicsInfo.m_spinningFrictionCoeff = mb->getLinkCollider(linkIndex)->getSpinningFriction();

				if (mb->getLinkCollider(linkIndex)->getCollisionFlags() & btCollisionObject::CF_HAS_CONTACT_STIFFNESS_DAMPING)
				{
					serverCmd.m_dynamicsInfo.m_contactStiffness = mb->getLinkCollider(linkIndex)->getContactStiffness();
					serverCmd.m_dynamicsInfo.m_contactDamping = mb->getLinkCollider(linkIndex)->getContactDamping();
				}
				else
				{
					serverCmd.m_dynamicsInfo.m_contactStiffness = -1;
					serverCmd.m_dynamicsInfo.m_contactDamping = -1;
				}
			}
			else
			{
				b3Warning("The dynamic info requested is not available");
				serverCmd.m_type = CMD_GET_DYNAMICS_INFO_FAILED;
			}
		}
		hasStatus = true;
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestPhysicsSimulationParametersCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_REQUEST_PHYSICS_SIMULATION_PARAMETERS_COMPLETED;

	serverCmd.m_simulationParameterResultArgs.m_allowedCcdPenetration = m_data->m_dynamicsWorld->getDispatchInfo().m_allowedCcdPenetration;
	serverCmd.m_simulationParameterResultArgs.m_collisionFilterMode = m_data->m_broadphaseCollisionFilterCallback->m_filterMode;
	serverCmd.m_simulationParameterResultArgs.m_deltaTime = m_data->m_physicsDeltaTime;
	serverCmd.m_simulationParameterResultArgs.m_contactBreakingThreshold = gContactBreakingThreshold;
	serverCmd.m_simulationParameterResultArgs.m_contactSlop = m_data->m_dynamicsWorld->getSolverInfo().m_linearSlop;
	serverCmd.m_simulationParameterResultArgs.m_enableSAT = m_data->m_dynamicsWorld->getDispatchInfo().m_enableSatConvex;

	serverCmd.m_simulationParameterResultArgs.m_defaultGlobalCFM = m_data->m_dynamicsWorld->getSolverInfo().m_globalCfm;
	serverCmd.m_simulationParameterResultArgs.m_defaultContactERP = m_data->m_dynamicsWorld->getSolverInfo().m_erp2;
	serverCmd.m_simulationParameterResultArgs.m_defaultNonContactERP = m_data->m_dynamicsWorld->getSolverInfo().m_erp;
	serverCmd.m_simulationParameterResultArgs.m_deltaTime = m_data->m_physicsDeltaTime;
	serverCmd.m_simulationParameterResultArgs.m_deterministicOverlappingPairs = m_data->m_dynamicsWorld->getDispatchInfo().m_deterministicOverlappingPairs;
	serverCmd.m_simulationParameterResultArgs.m_enableConeFriction = (m_data->m_dynamicsWorld->getSolverInfo().m_solverMode & SOLVER_DISABLE_IMPLICIT_CONE_FRICTION) ? 0 : 1;
	serverCmd.m_simulationParameterResultArgs.m_enableFileCaching = b3IsFileCachingEnabled();
	serverCmd.m_simulationParameterResultArgs.m_frictionCFM = m_data->m_dynamicsWorld->getSolverInfo().m_frictionCFM;
	serverCmd.m_simulationParameterResultArgs.m_frictionERP = m_data->m_dynamicsWorld->getSolverInfo().m_frictionERP;
	btVector3 grav = m_data->m_dynamicsWorld->getGravity();
	serverCmd.m_simulationParameterResultArgs.m_gravityAcceleration[0] = grav[0];
	serverCmd.m_simulationParameterResultArgs.m_gravityAcceleration[1] = grav[1];
	serverCmd.m_simulationParameterResultArgs.m_gravityAcceleration[2] = grav[2];
	serverCmd.m_simulationParameterResultArgs.m_internalSimFlags = gInternalSimFlags;
	serverCmd.m_simulationParameterResultArgs.m_jointFeedbackMode = 0;
	if (gJointFeedbackInWorldSpace)
	{
		serverCmd.m_simulationParameterResultArgs.m_jointFeedbackMode |= JOINT_FEEDBACK_IN_WORLD_SPACE;
	}
	if (gJointFeedbackInJointFrame)
	{
		serverCmd.m_simulationParameterResultArgs.m_jointFeedbackMode |= JOINT_FEEDBACK_IN_JOINT_FRAME;
	}

	serverCmd.m_simulationParameterResultArgs.m_numSimulationSubSteps = m_data->m_numSimulationSubSteps;
	serverCmd.m_simulationParameterResultArgs.m_numSolverIterations = m_data->m_dynamicsWorld->getSolverInfo().m_numIterations;
	serverCmd.m_simulationParameterResultArgs.m_restitutionVelocityThreshold = m_data->m_dynamicsWorld->getSolverInfo().m_restitutionVelocityThreshold;

	serverCmd.m_simulationParameterResultArgs.m_solverResidualThreshold = m_data->m_dynamicsWorld->getSolverInfo().m_leastSquaresResidualThreshold;
	serverCmd.m_simulationParameterResultArgs.m_splitImpulsePenetrationThreshold = m_data->m_dynamicsWorld->getSolverInfo().m_splitImpulsePenetrationThreshold;
	serverCmd.m_simulationParameterResultArgs.m_useRealTimeSimulation = m_data->m_useRealTimeSimulation;
	serverCmd.m_simulationParameterResultArgs.m_useSplitImpulse = m_data->m_dynamicsWorld->getSolverInfo().m_splitImpulse;

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processSendPhysicsParametersCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_SEND_PHYSICS_SIMULATION_PARAMETERS");

	if (clientCmd.m_updateFlags & SIM_PARAM_ENABLE_CONE_FRICTION)
	{
		if (clientCmd.m_physSimParamArgs.m_enableConeFriction)
		{
			m_data->m_dynamicsWorld->getSolverInfo().m_solverMode &= ~SOLVER_DISABLE_IMPLICIT_CONE_FRICTION;
		}
		else
		{
			m_data->m_dynamicsWorld->getSolverInfo().m_solverMode |= SOLVER_DISABLE_IMPLICIT_CONE_FRICTION;
		}
	}
	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_DETERMINISTIC_OVERLAPPING_PAIRS)
	{
		m_data->m_dynamicsWorld->getDispatchInfo().m_deterministicOverlappingPairs = (clientCmd.m_physSimParamArgs.m_deterministicOverlappingPairs != 0);
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_CCD_ALLOWED_PENETRATION)
	{
		m_data->m_dynamicsWorld->getDispatchInfo().m_allowedCcdPenetration = clientCmd.m_physSimParamArgs.m_allowedCcdPenetration;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_JOINT_FEEDBACK_MODE)
	{
		gJointFeedbackInWorldSpace = (clientCmd.m_physSimParamArgs.m_jointFeedbackMode & JOINT_FEEDBACK_IN_WORLD_SPACE) != 0;
		gJointFeedbackInJointFrame = (clientCmd.m_physSimParamArgs.m_jointFeedbackMode & JOINT_FEEDBACK_IN_JOINT_FRAME) != 0;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_DELTA_TIME)
	{
		m_data->m_physicsDeltaTime = clientCmd.m_physSimParamArgs.m_deltaTime;
	}
	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_REAL_TIME_SIMULATION)
	{
		m_data->m_useRealTimeSimulation = (clientCmd.m_physSimParamArgs.m_useRealTimeSimulation != 0);
	}

	//see
	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_INTERNAL_SIMULATION_FLAGS)
	{
		//these flags are for internal/temporary/easter-egg/experimental demo purposes, use at own risk
		gInternalSimFlags = clientCmd.m_physSimParamArgs.m_internalSimFlags;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_GRAVITY)
	{
		btVector3 grav(clientCmd.m_physSimParamArgs.m_gravityAcceleration[0],
					   clientCmd.m_physSimParamArgs.m_gravityAcceleration[1],
					   clientCmd.m_physSimParamArgs.m_gravityAcceleration[2]);
		this->m_data->m_dynamicsWorld->setGravity(grav);
#ifndef SKIP_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD
		m_data->m_dynamicsWorld->getWorldInfo().m_gravity = grav;

#endif
		if (m_data->m_verboseOutput)
		{
			b3Printf("Updated Gravity: %f,%f,%f", grav[0], grav[1], grav[2]);
		}
	}
	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_NUM_SOLVER_ITERATIONS)
	{
		m_data->m_dynamicsWorld->getSolverInfo().m_numIterations = clientCmd.m_physSimParamArgs.m_numSolverIterations;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_SOLVER_RESIDULAL_THRESHOLD)
	{
		m_data->m_dynamicsWorld->getSolverInfo().m_leastSquaresResidualThreshold = clientCmd.m_physSimParamArgs.m_solverResidualThreshold;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_CONTACT_BREAKING_THRESHOLD)
	{
		gContactBreakingThreshold = clientCmd.m_physSimParamArgs.m_contactBreakingThreshold;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_CONTACT_SLOP)
	{
		m_data->m_dynamicsWorld->getSolverInfo().m_linearSlop = clientCmd.m_physSimParamArgs.m_contactSlop;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_ENABLE_SAT)
	{
		m_data->m_dynamicsWorld->getDispatchInfo().m_enableSatConvex = clientCmd.m_physSimParamArgs.m_enableSAT != 0;
	}
	if (clientCmd.m_updateFlags & SIM_PARAM_CONSTRAINT_SOLVER_TYPE)
	{
		//check if the current type is different from requested one
		if (m_data->m_constraintSolverType != clientCmd.m_physSimParamArgs.m_constraintSolverType)
		{
			m_data->m_constraintSolverType = clientCmd.m_physSimParamArgs.m_constraintSolverType;

			btConstraintSolver* oldSolver = m_data->m_dynamicsWorld->getConstraintSolver();

			btMultiBodyConstraintSolver* newSolver = 0;

			switch (clientCmd.m_physSimParamArgs.m_constraintSolverType)
			{
				case eConstraintSolverLCP_SI:
				{
					newSolver = new btMultiBodyConstraintSolver;
					b3Printf("PyBullet: Constraint Solver: btMultiBodyConstraintSolver\n");
					break;
				}
				case eConstraintSolverLCP_PGS:
				{
					btSolveProjectedGaussSeidel* mlcp = new btSolveProjectedGaussSeidel();
					newSolver = new btMultiBodyMLCPConstraintSolver(mlcp);
					b3Printf("PyBullet: Constraint Solver: MLCP + PGS\n");
					break;
				}
				case eConstraintSolverLCP_DANTZIG:
				{
					btDantzigSolver* mlcp = new btDantzigSolver();
					newSolver = new btMultiBodyMLCPConstraintSolver(mlcp);
					b3Printf("PyBullet: Constraint Solver: MLCP + Dantzig\n");
					break;
				}
				case eConstraintSolverLCP_BLOCK_PGS:
				{
					break;
				}
				default:
				{
				}
			};

			if (newSolver)
			{
				delete oldSolver;

				m_data->m_dynamicsWorld->setMultiBodyConstraintSolver(newSolver);
				m_data->m_solver = newSolver;
				printf("switched solver\n");
			}
		}
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_COLLISION_FILTER_MODE)
	{
		m_data->m_broadphaseCollisionFilterCallback->m_filterMode = clientCmd.m_physSimParamArgs.m_collisionFilterMode;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_USE_SPLIT_IMPULSE)
	{
		m_data->m_dynamicsWorld->getSolverInfo().m_splitImpulse = clientCmd.m_physSimParamArgs.m_useSplitImpulse;
	}
	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_SPLIT_IMPULSE_PENETRATION_THRESHOLD)
	{
		m_data->m_dynamicsWorld->getSolverInfo().m_splitImpulsePenetrationThreshold = clientCmd.m_physSimParamArgs.m_splitImpulsePenetrationThreshold;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_NUM_SIMULATION_SUB_STEPS)
	{
		m_data->m_numSimulationSubSteps = clientCmd.m_physSimParamArgs.m_numSimulationSubSteps;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_DEFAULT_CONTACT_ERP)
	{
		m_data->m_dynamicsWorld->getSolverInfo().m_erp2 = clientCmd.m_physSimParamArgs.m_defaultContactERP;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_DEFAULT_NON_CONTACT_ERP)
	{
		m_data->m_dynamicsWorld->getSolverInfo().m_erp = clientCmd.m_physSimParamArgs.m_defaultNonContactERP;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_DEFAULT_FRICTION_ERP)
	{
		m_data->m_dynamicsWorld->getSolverInfo().m_frictionERP = clientCmd.m_physSimParamArgs.m_frictionERP;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_DEFAULT_GLOBAL_CFM)
	{
		m_data->m_dynamicsWorld->getSolverInfo().m_globalCfm = clientCmd.m_physSimParamArgs.m_defaultGlobalCFM;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_DEFAULT_FRICTION_CFM)
	{
		m_data->m_dynamicsWorld->getSolverInfo().m_frictionCFM = clientCmd.m_physSimParamArgs.m_frictionCFM;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_UPDATE_RESTITUTION_VELOCITY_THRESHOLD)
	{
		m_data->m_dynamicsWorld->getSolverInfo().m_restitutionVelocityThreshold = clientCmd.m_physSimParamArgs.m_restitutionVelocityThreshold;
	}

	if (clientCmd.m_updateFlags & SIM_PARAM_ENABLE_FILE_CACHING)
	{
		b3EnableFileCaching(clientCmd.m_physSimParamArgs.m_enableFileCaching);
	}

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CLIENT_COMMAND_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processInitPoseCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_INIT_POSE");

	if (m_data->m_verboseOutput)
	{
		b3Printf("Server Init Pose not implemented yet");
	}
	int bodyUniqueId = clientCmd.m_initPoseArgs.m_bodyUniqueId;
	InternalBodyData* body = m_data->m_bodyHandles.getHandle(bodyUniqueId);

	btVector3 baseLinVel(0, 0, 0);
	btVector3 baseAngVel(0, 0, 0);

	if (clientCmd.m_updateFlags & INIT_POSE_HAS_BASE_LINEAR_VELOCITY)
	{
		baseLinVel.setValue(clientCmd.m_initPoseArgs.m_initialStateQdot[0],
							clientCmd.m_initPoseArgs.m_initialStateQdot[1],
							clientCmd.m_initPoseArgs.m_initialStateQdot[2]);
	}
	if (clientCmd.m_updateFlags & INIT_POSE_HAS_BASE_ANGULAR_VELOCITY)
	{
		baseAngVel.setValue(clientCmd.m_initPoseArgs.m_initialStateQdot[3],
							clientCmd.m_initPoseArgs.m_initialStateQdot[4],
							clientCmd.m_initPoseArgs.m_initialStateQdot[5]);
	}
	btVector3 basePos(0, 0, 0);
	if (clientCmd.m_updateFlags & INIT_POSE_HAS_INITIAL_POSITION)
	{
		basePos = btVector3(
			clientCmd.m_initPoseArgs.m_initialStateQ[0],
			clientCmd.m_initPoseArgs.m_initialStateQ[1],
			clientCmd.m_initPoseArgs.m_initialStateQ[2]);
	}
	btQuaternion baseOrn(0, 0, 0, 1);
	if (clientCmd.m_updateFlags & INIT_POSE_HAS_INITIAL_ORIENTATION)
	{
		baseOrn.setValue(clientCmd.m_initPoseArgs.m_initialStateQ[3],
						 clientCmd.m_initPoseArgs.m_initialStateQ[4],
						 clientCmd.m_initPoseArgs.m_initialStateQ[5],
						 clientCmd.m_initPoseArgs.m_initialStateQ[6]);
	}
	if (body && body->m_multiBody)
	{
		btMultiBody* mb = body->m_multiBody;

		if (clientCmd.m_updateFlags & INIT_POSE_HAS_BASE_LINEAR_VELOCITY)
		{
			mb->setBaseVel(baseLinVel);
		}

		if (clientCmd.m_updateFlags & INIT_POSE_HAS_BASE_ANGULAR_VELOCITY)
		{
			mb->setBaseOmega(baseAngVel);
		}

		if (clientCmd.m_updateFlags & INIT_POSE_HAS_INITIAL_POSITION)
		{
			btVector3 zero(0, 0, 0);
			btAssert(clientCmd.m_initPoseArgs.m_hasInitialStateQ[0] &&
					 clientCmd.m_initPoseArgs.m_hasInitialStateQ[1] &&
					 clientCmd.m_initPoseArgs.m_hasInitialStateQ[2]);

			mb->setBaseVel(baseLinVel);
			mb->setBasePos(basePos);
		}
		if (clientCmd.m_updateFlags & INIT_POSE_HAS_INITIAL_ORIENTATION)
		{
			btAssert(clientCmd.m_initPoseArgs.m_hasInitialStateQ[3] &&
					 clientCmd.m_initPoseArgs.m_hasInitialStateQ[4] &&
					 clientCmd.m_initPoseArgs.m_hasInitialStateQ[5] &&
					 clientCmd.m_initPoseArgs.m_hasInitialStateQ[6]);

			mb->setBaseOmega(baseAngVel);
			btQuaternion invOrn(baseOrn);

			mb->setWorldToBaseRot(invOrn.inverse());
		}
		if (clientCmd.m_updateFlags & INIT_POSE_HAS_JOINT_STATE)
		{
			int uDofIndex = 6;
			int posVarCountIndex = 7;
			for (int i = 0; i < mb->getNumLinks(); i++)
			{
				if ((clientCmd.m_initPoseArgs.m_hasInitialStateQ[posVarCountIndex]) && (mb->getLink(i).m_dofCount == 1))
				{
					mb->setJointPos(i, clientCmd.m_initPoseArgs.m_initialStateQ[posVarCountIndex]);
					mb->setJointVel(i, 0);  //backwards compatibility
				}
				if ((clientCmd.m_initPoseArgs.m_hasInitialStateQdot[uDofIndex]) && (mb->getLink(i).m_dofCount == 1))
				{
					btScalar vel = clientCmd.m_initPoseArgs.m_initialStateQdot[uDofIndex];
					mb->setJointVel(i, vel);
				}

				posVarCountIndex += mb->getLink(i).m_posVarCount;
				uDofIndex += mb->getLink(i).m_dofCount;
			}
		}

		btAlignedObjectArray<btQuaternion> scratch_q;
		btAlignedObjectArray<btVector3> scratch_m;

		mb->forwardKinematics(scratch_q, scratch_m);
		int nLinks = mb->getNumLinks();
		scratch_q.resize(nLinks + 1);
		scratch_m.resize(nLinks + 1);

		mb->updateCollisionObjectWorldTransforms(scratch_q, scratch_m);
	}

	if (body && body->m_rigidBody)
	{
		if (clientCmd.m_updateFlags & INIT_POSE_HAS_BASE_LINEAR_VELOCITY)
		{
			body->m_rigidBody->setLinearVelocity(baseLinVel);
		}
		if (clientCmd.m_updateFlags & INIT_POSE_HAS_BASE_ANGULAR_VELOCITY)
		{
			body->m_rigidBody->setAngularVelocity(baseAngVel);
		}

		if (clientCmd.m_updateFlags & INIT_POSE_HAS_INITIAL_POSITION)
		{
			body->m_rigidBody->getWorldTransform().setOrigin(basePos);
			body->m_rigidBody->setLinearVelocity(baseLinVel);
		}

		if (clientCmd.m_updateFlags & INIT_POSE_HAS_INITIAL_ORIENTATION)
		{
			body->m_rigidBody->getWorldTransform().setRotation(baseOrn);
			body->m_rigidBody->setAngularVelocity(baseAngVel);
		}
	}

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CLIENT_COMMAND_COMPLETED;

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processResetSimulationCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_RESET_SIMULATION");
	m_data->m_guiHelper->setVisualizerFlag(COV_ENABLE_SYNC_RENDERING_INTERNAL, 0);
	resetSimulation();
	m_data->m_guiHelper->setVisualizerFlag(COV_ENABLE_SYNC_RENDERING_INTERNAL, 1);

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_RESET_SIMULATION_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCreateRigidBodyCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_RIGID_BODY_CREATION_COMPLETED;

	BT_PROFILE("CMD_CREATE_RIGID_BODY");

	btVector3 halfExtents(1, 1, 1);
	if (clientCmd.m_updateFlags & BOX_SHAPE_HAS_HALF_EXTENTS)
	{
		halfExtents = btVector3(
			clientCmd.m_createBoxShapeArguments.m_halfExtentsX,
			clientCmd.m_createBoxShapeArguments.m_halfExtentsY,
			clientCmd.m_createBoxShapeArguments.m_halfExtentsZ);
	}
	btTransform startTrans;
	startTrans.setIdentity();
	if (clientCmd.m_updateFlags & BOX_SHAPE_HAS_INITIAL_POSITION)
	{
		startTrans.setOrigin(btVector3(
			clientCmd.m_createBoxShapeArguments.m_initialPosition[0],
			clientCmd.m_createBoxShapeArguments.m_initialPosition[1],
			clientCmd.m_createBoxShapeArguments.m_initialPosition[2]));
	}

	if (clientCmd.m_updateFlags & BOX_SHAPE_HAS_INITIAL_ORIENTATION)
	{
		startTrans.setRotation(btQuaternion(
			clientCmd.m_createBoxShapeArguments.m_initialOrientation[0],
			clientCmd.m_createBoxShapeArguments.m_initialOrientation[1],
			clientCmd.m_createBoxShapeArguments.m_initialOrientation[2],
			clientCmd.m_createBoxShapeArguments.m_initialOrientation[3]));
	}

	btScalar mass = 0.f;
	if (clientCmd.m_updateFlags & BOX_SHAPE_HAS_MASS)
	{
		mass = clientCmd.m_createBoxShapeArguments.m_mass;
	}

	int shapeType = COLLISION_SHAPE_TYPE_BOX;

	if (clientCmd.m_updateFlags & BOX_SHAPE_HAS_COLLISION_SHAPE_TYPE)
	{
		shapeType = clientCmd.m_createBoxShapeArguments.m_collisionShapeType;
	}

	btMultiBodyWorldImporter* worldImporter = new btMultiBodyWorldImporter(m_data->m_dynamicsWorld);
	m_data->m_worldImporters.push_back(worldImporter);

	btCollisionShape* shape = 0;

	switch (shapeType)
	{
		case COLLISION_SHAPE_TYPE_CYLINDER_X:
		{
			btScalar radius = halfExtents[1];
			btScalar height = halfExtents[0];
			shape = worldImporter->createCylinderShapeX(radius, height);
			break;
		}
		case COLLISION_SHAPE_TYPE_CYLINDER_Y:
		{
			btScalar radius = halfExtents[0];
			btScalar height = halfExtents[1];
			shape = worldImporter->createCylinderShapeY(radius, height);
			break;
		}
		case COLLISION_SHAPE_TYPE_CYLINDER_Z:
		{
			btScalar radius = halfExtents[1];
			btScalar height = halfExtents[2];
			shape = worldImporter->createCylinderShapeZ(radius, height);
			break;
		}
		case COLLISION_SHAPE_TYPE_CAPSULE_X:
		{
			btScalar radius = halfExtents[1];
			btScalar height = halfExtents[0];
			shape = worldImporter->createCapsuleShapeX(radius, height);
			break;
		}
		case COLLISION_SHAPE_TYPE_CAPSULE_Y:
		{
			btScalar radius = halfExtents[0];
			btScalar height = halfExtents[1];
			shape = worldImporter->createCapsuleShapeY(radius, height);
			break;
		}
		case COLLISION_SHAPE_TYPE_CAPSULE_Z:
		{
			btScalar radius = halfExtents[1];
			btScalar height = halfExtents[2];
			shape = worldImporter->createCapsuleShapeZ(radius, height);
			break;
		}
		case COLLISION_SHAPE_TYPE_SPHERE:
		{
			btScalar radius = halfExtents[0];
			shape = worldImporter->createSphereShape(radius);
			break;
		}
		case COLLISION_SHAPE_TYPE_BOX:
		default:
		{
			shape = worldImporter->createBoxShape(halfExtents);
		}
	}

	bool isDynamic = (mass > 0);
	btRigidBody* rb = worldImporter->createRigidBody(isDynamic, mass, startTrans, shape, 0);
	//m_data->m_guiHelper->autogenerateGraphicsObjects(this->m_data->m_dynamicsWorld);
	btVector4 colorRGBA(1, 0, 0, 1);
	if (clientCmd.m_updateFlags & BOX_SHAPE_HAS_COLOR)
	{
		colorRGBA[0] = clientCmd.m_createBoxShapeArguments.m_colorRGBA[0];
		colorRGBA[1] = clientCmd.m_createBoxShapeArguments.m_colorRGBA[1];
		colorRGBA[2] = clientCmd.m_createBoxShapeArguments.m_colorRGBA[2];
		colorRGBA[3] = clientCmd.m_createBoxShapeArguments.m_colorRGBA[3];
	}
	m_data->m_guiHelper->createCollisionShapeGraphicsObject(rb->getCollisionShape());
	m_data->m_guiHelper->createCollisionObjectGraphicsObject(rb, colorRGBA);

	int bodyUniqueId = m_data->m_bodyHandles.allocHandle();
	InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(bodyUniqueId);
	serverCmd.m_rigidBodyCreateArgs.m_bodyUniqueId = bodyUniqueId;
	rb->setUserIndex2(bodyUniqueId);
	bodyHandle->m_rootLocalInertialFrame.setIdentity();
	bodyHandle->m_rigidBody = rb;

	b3Notification notification;
	notification.m_notificationType = BODY_ADDED;
	notification.m_bodyArgs.m_bodyUniqueId = bodyUniqueId;
	m_data->m_pluginManager.addNotification(notification);

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processPickBodyCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_PICK_BODY");

	pickBody(btVector3(clientCmd.m_pickBodyArguments.m_rayFromWorld[0],
					   clientCmd.m_pickBodyArguments.m_rayFromWorld[1],
					   clientCmd.m_pickBodyArguments.m_rayFromWorld[2]),
			 btVector3(clientCmd.m_pickBodyArguments.m_rayToWorld[0],
					   clientCmd.m_pickBodyArguments.m_rayToWorld[1],
					   clientCmd.m_pickBodyArguments.m_rayToWorld[2]));

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CLIENT_COMMAND_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processMovePickedBodyCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_MOVE_PICKED_BODY");

	movePickedBody(btVector3(clientCmd.m_pickBodyArguments.m_rayFromWorld[0],
							 clientCmd.m_pickBodyArguments.m_rayFromWorld[1],
							 clientCmd.m_pickBodyArguments.m_rayFromWorld[2]),
				   btVector3(clientCmd.m_pickBodyArguments.m_rayToWorld[0],
							 clientCmd.m_pickBodyArguments.m_rayToWorld[1],
							 clientCmd.m_pickBodyArguments.m_rayToWorld[2]));

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CLIENT_COMMAND_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRemovePickingConstraintCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_REMOVE_PICKING_CONSTRAINT_BODY");
	removePickingConstraint();

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CLIENT_COMMAND_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestAabbOverlapCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_REQUEST_AABB_OVERLAP");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	int curObjectIndex = clientCmd.m_requestOverlappingObjectsArgs.m_startingOverlappingObjectIndex;

	if (0 == curObjectIndex)
	{
		//clientCmd.m_requestContactPointArguments.m_aabbQueryMin
		btVector3 aabbMin, aabbMax;
		aabbMin.setValue(clientCmd.m_requestOverlappingObjectsArgs.m_aabbQueryMin[0],
						 clientCmd.m_requestOverlappingObjectsArgs.m_aabbQueryMin[1],
						 clientCmd.m_requestOverlappingObjectsArgs.m_aabbQueryMin[2]);
		aabbMax.setValue(clientCmd.m_requestOverlappingObjectsArgs.m_aabbQueryMax[0],
						 clientCmd.m_requestOverlappingObjectsArgs.m_aabbQueryMax[1],
						 clientCmd.m_requestOverlappingObjectsArgs.m_aabbQueryMax[2]);

		m_data->m_cachedOverlappingObjects.clear();

		m_data->m_dynamicsWorld->getBroadphase()->aabbTest(aabbMin, aabbMax, m_data->m_cachedOverlappingObjects);
	}

	int totalBytesPerObject = sizeof(b3OverlappingObject);
	int overlapCapacity = bufferSizeInBytes / totalBytesPerObject - 1;
	int numOverlap = m_data->m_cachedOverlappingObjects.m_bodyUniqueIds.size();
	int remainingObjects = numOverlap - curObjectIndex;

	int curNumObjects = btMin(overlapCapacity, remainingObjects);

	if (numOverlap < overlapCapacity)
	{
		b3OverlappingObject* overlapStorage = (b3OverlappingObject*)bufferServerToClient;
		for (int i = 0; i < m_data->m_cachedOverlappingObjects.m_bodyUniqueIds.size(); i++)
		{
			overlapStorage[i].m_objectUniqueId = m_data->m_cachedOverlappingObjects.m_bodyUniqueIds[i];
			overlapStorage[i].m_linkIndex = m_data->m_cachedOverlappingObjects.m_links[i];
		}

		serverCmd.m_type = CMD_REQUEST_AABB_OVERLAP_COMPLETED;

		//int m_startingOverlappingObjectIndex;
		//int m_numOverlappingObjectsCopied;
		//int m_numRemainingOverlappingObjects;
		serverCmd.m_sendOverlappingObjectsArgs.m_startingOverlappingObjectIndex = clientCmd.m_requestOverlappingObjectsArgs.m_startingOverlappingObjectIndex;
		serverCmd.m_sendOverlappingObjectsArgs.m_numOverlappingObjectsCopied = m_data->m_cachedOverlappingObjects.m_bodyUniqueIds.size();
		serverCmd.m_sendOverlappingObjectsArgs.m_numRemainingOverlappingObjects = remainingObjects - curNumObjects;
	}
	else
	{
		serverCmd.m_type = CMD_REQUEST_AABB_OVERLAP_FAILED;
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRequestOpenGLVisualizeCameraCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_REQUEST_OPENGL_VISUALIZER_CAMERA");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	bool result = this->m_data->m_guiHelper->getCameraInfo(
		&serverCmd.m_visualizerCameraResultArgs.m_width,
		&serverCmd.m_visualizerCameraResultArgs.m_height,
		serverCmd.m_visualizerCameraResultArgs.m_viewMatrix,
		serverCmd.m_visualizerCameraResultArgs.m_projectionMatrix,
		serverCmd.m_visualizerCameraResultArgs.m_camUp,
		serverCmd.m_visualizerCameraResultArgs.m_camForward,
		serverCmd.m_visualizerCameraResultArgs.m_horizontal,
		serverCmd.m_visualizerCameraResultArgs.m_vertical,
		&serverCmd.m_visualizerCameraResultArgs.m_yaw,
		&serverCmd.m_visualizerCameraResultArgs.m_pitch,
		&serverCmd.m_visualizerCameraResultArgs.m_dist,
		serverCmd.m_visualizerCameraResultArgs.m_target);
	serverCmd.m_type = result ? CMD_REQUEST_OPENGL_VISUALIZER_CAMERA_COMPLETED : CMD_REQUEST_OPENGL_VISUALIZER_CAMERA_FAILED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processConfigureOpenGLVisualizerCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_CONFIGURE_OPENGL_VISUALIZER");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CLIENT_COMMAND_COMPLETED;

	hasStatus = true;

	if (clientCmd.m_updateFlags & COV_SET_FLAGS)
	{
		if (clientCmd.m_configureOpenGLVisualizerArguments.m_setFlag == COV_ENABLE_TINY_RENDERER)
		{
			m_data->m_enableTinyRenderer = clientCmd.m_configureOpenGLVisualizerArguments.m_setEnabled != 0;
		}
		m_data->m_guiHelper->setVisualizerFlag(clientCmd.m_configureOpenGLVisualizerArguments.m_setFlag,
											   clientCmd.m_configureOpenGLVisualizerArguments.m_setEnabled);
	}
	if (clientCmd.m_updateFlags & COV_SET_CAMERA_VIEW_MATRIX)
	{
		m_data->m_guiHelper->resetCamera(clientCmd.m_configureOpenGLVisualizerArguments.m_cameraDistance,
										 clientCmd.m_configureOpenGLVisualizerArguments.m_cameraYaw,
										 clientCmd.m_configureOpenGLVisualizerArguments.m_cameraPitch,
										 clientCmd.m_configureOpenGLVisualizerArguments.m_cameraTargetPosition[0],
										 clientCmd.m_configureOpenGLVisualizerArguments.m_cameraTargetPosition[1],
										 clientCmd.m_configureOpenGLVisualizerArguments.m_cameraTargetPosition[2]);
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processInverseDynamicsCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_CALCULATE_INVERSE_DYNAMICS");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(clientCmd.m_calculateInverseDynamicsArguments.m_bodyUniqueId);
	if (bodyHandle && bodyHandle->m_multiBody)
	{
		serverCmd.m_type = CMD_CALCULATED_INVERSE_DYNAMICS_FAILED;

		btInverseDynamics::MultiBodyTree* tree = m_data->findOrCreateTree(bodyHandle->m_multiBody);

		if (tree)
		{
			int baseDofs = bodyHandle->m_multiBody->hasFixedBase() ? 0 : 6;
			const int num_dofs = bodyHandle->m_multiBody->getNumDofs();
			btInverseDynamics::vecx nu(num_dofs + baseDofs), qdot(num_dofs + baseDofs), q(num_dofs + baseDofs), joint_force(num_dofs + baseDofs);
			for (int i = 0; i < num_dofs; i++)
			{
				q[i + baseDofs] = clientCmd.m_calculateInverseDynamicsArguments.m_jointPositionsQ[i];
				qdot[i + baseDofs] = clientCmd.m_calculateInverseDynamicsArguments.m_jointVelocitiesQdot[i];
				nu[i + baseDofs] = clientCmd.m_calculateInverseDynamicsArguments.m_jointAccelerations[i];
			}
			// Set the gravity to correspond to the world gravity
			btInverseDynamics::vec3 id_grav(m_data->m_dynamicsWorld->getGravity());

			if (-1 != tree->setGravityInWorldFrame(id_grav) &&
				-1 != tree->calculateInverseDynamics(q, qdot, nu, &joint_force))
			{
				serverCmd.m_inverseDynamicsResultArgs.m_bodyUniqueId = clientCmd.m_calculateInverseDynamicsArguments.m_bodyUniqueId;
				serverCmd.m_inverseDynamicsResultArgs.m_dofCount = num_dofs;
				for (int i = 0; i < num_dofs; i++)
				{
					serverCmd.m_inverseDynamicsResultArgs.m_jointForces[i] = joint_force[i + baseDofs];
				}
				serverCmd.m_type = CMD_CALCULATED_INVERSE_DYNAMICS_COMPLETED;
			}
			else
			{
				serverCmd.m_type = CMD_CALCULATED_INVERSE_DYNAMICS_FAILED;
			}
		}
	}
	else
	{
		serverCmd.m_type = CMD_CALCULATED_INVERSE_DYNAMICS_FAILED;
	}

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCalculateJacobianCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_CALCULATE_JACOBIAN");

	SharedMemoryStatus& serverCmd = serverStatusOut;
	InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(clientCmd.m_calculateJacobianArguments.m_bodyUniqueId);
	if (bodyHandle && bodyHandle->m_multiBody)
	{
		serverCmd.m_type = CMD_CALCULATED_JACOBIAN_FAILED;

		btInverseDynamics::MultiBodyTree* tree = m_data->findOrCreateTree(bodyHandle->m_multiBody);

		if (tree)
		{
			int baseDofs = bodyHandle->m_multiBody->hasFixedBase() ? 0 : 6;
			const int numDofs = bodyHandle->m_multiBody->getNumDofs();
			btInverseDynamics::vecx q(numDofs + baseDofs);
			btInverseDynamics::vecx qdot(numDofs + baseDofs);
			btInverseDynamics::vecx nu(numDofs + baseDofs);
			btInverseDynamics::vecx joint_force(numDofs + baseDofs);
			for (int i = 0; i < numDofs; i++)
			{
				q[i + baseDofs] = clientCmd.m_calculateJacobianArguments.m_jointPositionsQ[i];
				qdot[i + baseDofs] = clientCmd.m_calculateJacobianArguments.m_jointVelocitiesQdot[i];
				nu[i + baseDofs] = clientCmd.m_calculateJacobianArguments.m_jointAccelerations[i];
			}
			// Set the gravity to correspond to the world gravity
			btInverseDynamics::vec3 id_grav(m_data->m_dynamicsWorld->getGravity());
			if (-1 != tree->setGravityInWorldFrame(id_grav) &&
				-1 != tree->calculateInverseDynamics(q, qdot, nu, &joint_force))
			{
				serverCmd.m_jacobianResultArgs.m_dofCount = numDofs + baseDofs;
				// Set jacobian value
				tree->calculateJacobians(q);
				btInverseDynamics::mat3x jac_t(3, numDofs + baseDofs);
				btInverseDynamics::mat3x jac_r(3, numDofs + baseDofs);

				// Note that inverse dynamics uses zero-based indexing of bodies, not starting from -1 for the base link.
				tree->getBodyJacobianTrans(clientCmd.m_calculateJacobianArguments.m_linkIndex + 1, &jac_t);
				tree->getBodyJacobianRot(clientCmd.m_calculateJacobianArguments.m_linkIndex + 1, &jac_r);
				// Update the translational jacobian based on the desired local point.
				// v_pt = v_frame + w x pt
				// v_pt = J_t * qd + (J_r * qd) x pt
				// v_pt = J_t * qd - pt x (J_r * qd)
				// v_pt = J_t * qd - pt_x * J_r * qd)
				// v_pt = (J_t - pt_x * J_r) * qd
				// J_t_new = J_t - pt_x * J_r
				btInverseDynamics::vec3 localPosition;
				for (int i = 0; i < 3; ++i)
				{
					localPosition(i) = clientCmd.m_calculateJacobianArguments.m_localPosition[i];
				}
				// Only calculate if the localPosition is non-zero.
				if (btInverseDynamics::maxAbs(localPosition) > 0.0)
				{
					// Write the localPosition into world coordinates.
					btInverseDynamics::mat33 world_rotation_body;
					tree->getBodyTransform(clientCmd.m_calculateJacobianArguments.m_linkIndex + 1, &world_rotation_body);
					localPosition = world_rotation_body * localPosition;
					// Correct the translational jacobian.
					btInverseDynamics::mat33 skewCrossProduct;
					btInverseDynamics::skew(localPosition, &skewCrossProduct);
					btInverseDynamics::mat3x jac_l(3, numDofs + baseDofs);
					btInverseDynamics::mul(skewCrossProduct, jac_r, &jac_l);
					btInverseDynamics::mat3x jac_t_new(3, numDofs + baseDofs);
					btInverseDynamics::sub(jac_t, jac_l, &jac_t_new);
					jac_t = jac_t_new;
				}
				// Fill in the result into the shared memory.
				for (int i = 0; i < 3; ++i)
				{
					for (int j = 0; j < (numDofs + baseDofs); ++j)
					{
						int element = (numDofs + baseDofs) * i + j;
						serverCmd.m_jacobianResultArgs.m_linearJacobian[element] = jac_t(i, j);
						serverCmd.m_jacobianResultArgs.m_angularJacobian[element] = jac_r(i, j);
					}
				}
				serverCmd.m_type = CMD_CALCULATED_JACOBIAN_COMPLETED;
			}
			else
			{
				serverCmd.m_type = CMD_CALCULATED_JACOBIAN_FAILED;
			}
		}
	}
	else
	{
		serverCmd.m_type = CMD_CALCULATED_JACOBIAN_FAILED;
	}

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCalculateMassMatrixCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;
	BT_PROFILE("CMD_CALCULATE_MASS_MATRIX");

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CALCULATED_MASS_MATRIX_FAILED;
	InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(clientCmd.m_calculateMassMatrixArguments.m_bodyUniqueId);
	if (bodyHandle && bodyHandle->m_multiBody)
	{
		btInverseDynamics::MultiBodyTree* tree = m_data->findOrCreateTree(bodyHandle->m_multiBody);

		if (tree)
		{
			int baseDofs = bodyHandle->m_multiBody->hasFixedBase() ? 0 : 6;
			const int numDofs = bodyHandle->m_multiBody->getNumDofs();
			const int totDofs = numDofs + baseDofs;
			btInverseDynamics::vecx q(totDofs);
			btInverseDynamics::matxx massMatrix(totDofs, totDofs);
			for (int i = 0; i < numDofs; i++)
			{
				q[i + baseDofs] = clientCmd.m_calculateMassMatrixArguments.m_jointPositionsQ[i];
			}
			if (-1 != tree->calculateMassMatrix(q, &massMatrix))
			{
				serverCmd.m_massMatrixResultArgs.m_dofCount = totDofs;
				// Fill in the result into the shared memory.
				double* sharedBuf = (double*)bufferServerToClient;
				int sizeInBytes = totDofs * totDofs * sizeof(double);
				if (sizeInBytes < bufferSizeInBytes)
				{
					for (int i = 0; i < (totDofs); ++i)
					{
						for (int j = 0; j < (totDofs); ++j)
						{
							int element = (totDofs)*i + j;

							sharedBuf[element] = massMatrix(i, j);
						}
					}
					serverCmd.m_type = CMD_CALCULATED_MASS_MATRIX_COMPLETED;
				}
			}
		}
	}
	else
	{
		serverCmd.m_type = CMD_CALCULATED_MASS_MATRIX_FAILED;
	}

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processApplyExternalForceCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_APPLY_EXTERNAL_FORCE");

	if (m_data->m_verboseOutput)
	{
		b3Printf("CMD_APPLY_EXTERNAL_FORCE clientCmd = %d\n", clientCmd.m_sequenceNumber);
	}
	for (int i = 0; i < clientCmd.m_externalForceArguments.m_numForcesAndTorques; ++i)
	{
		InternalBodyData* body = m_data->m_bodyHandles.getHandle(clientCmd.m_externalForceArguments.m_bodyUniqueIds[i]);
		bool isLinkFrame = ((clientCmd.m_externalForceArguments.m_forceFlags[i] & EF_LINK_FRAME) != 0);

		if (body && body->m_multiBody)
		{
			btMultiBody* mb = body->m_multiBody;

			if ((clientCmd.m_externalForceArguments.m_forceFlags[i] & EF_FORCE) != 0)
			{
				btVector3 tmpForce(clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 0],
								   clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 1],
								   clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 2]);
				btVector3 tmpPosition(
					clientCmd.m_externalForceArguments.m_positions[i * 3 + 0],
					clientCmd.m_externalForceArguments.m_positions[i * 3 + 1],
					clientCmd.m_externalForceArguments.m_positions[i * 3 + 2]);

				if (clientCmd.m_externalForceArguments.m_linkIds[i] == -1)
				{
					btVector3 forceWorld = isLinkFrame ? mb->getBaseWorldTransform().getBasis() * tmpForce : tmpForce;
					btVector3 relPosWorld = isLinkFrame ? mb->getBaseWorldTransform().getBasis() * tmpPosition : tmpPosition - mb->getBaseWorldTransform().getOrigin();
					mb->addBaseForce(forceWorld);
					mb->addBaseTorque(relPosWorld.cross(forceWorld));
					//b3Printf("apply base force of %f,%f,%f at %f,%f,%f\n", forceWorld[0],forceWorld[1],forceWorld[2],positionLocal[0],positionLocal[1],positionLocal[2]);
				}
				else
				{
					int link = clientCmd.m_externalForceArguments.m_linkIds[i];

					btVector3 forceWorld = isLinkFrame ? mb->getLink(link).m_cachedWorldTransform.getBasis() * tmpForce : tmpForce;
					btVector3 relPosWorld = isLinkFrame ? mb->getLink(link).m_cachedWorldTransform.getBasis() * tmpPosition : tmpPosition - mb->getBaseWorldTransform().getOrigin();
					mb->addLinkForce(link, forceWorld);
					mb->addLinkTorque(link, relPosWorld.cross(forceWorld));
					//b3Printf("apply link force of %f,%f,%f at %f,%f,%f\n", forceWorld[0],forceWorld[1],forceWorld[2], positionLocal[0],positionLocal[1],positionLocal[2]);
				}
			}
			if ((clientCmd.m_externalForceArguments.m_forceFlags[i] & EF_TORQUE) != 0)
			{
				btVector3 torqueLocal(clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 0],
									  clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 1],
									  clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 2]);

				if (clientCmd.m_externalForceArguments.m_linkIds[i] == -1)
				{
					btVector3 torqueWorld = isLinkFrame ? torqueLocal : mb->getBaseWorldTransform().getBasis() * torqueLocal;
					mb->addBaseTorque(torqueWorld);
					//b3Printf("apply base torque of %f,%f,%f\n", torqueWorld[0],torqueWorld[1],torqueWorld[2]);
				}
				else
				{
					int link = clientCmd.m_externalForceArguments.m_linkIds[i];
					btVector3 torqueWorld = mb->getLink(link).m_cachedWorldTransform.getBasis() * torqueLocal;
					mb->addLinkTorque(link, torqueWorld);
					//b3Printf("apply link torque of %f,%f,%f\n", torqueWorld[0],torqueWorld[1],torqueWorld[2]);
				}
			}
		}

		if (body && body->m_rigidBody)
		{
			btRigidBody* rb = body->m_rigidBody;
			if ((clientCmd.m_externalForceArguments.m_forceFlags[i] & EF_FORCE) != 0)
			{
				btVector3 forceLocal(clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 0],
									 clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 1],
									 clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 2]);
				btVector3 positionLocal(
					clientCmd.m_externalForceArguments.m_positions[i * 3 + 0],
					clientCmd.m_externalForceArguments.m_positions[i * 3 + 1],
					clientCmd.m_externalForceArguments.m_positions[i * 3 + 2]);

				btVector3 forceWorld = isLinkFrame ? forceLocal : rb->getWorldTransform().getBasis() * forceLocal;
				btVector3 relPosWorld = isLinkFrame ? positionLocal : rb->getWorldTransform().getBasis() * positionLocal;
				rb->applyForce(forceWorld, relPosWorld);
			}

			if ((clientCmd.m_externalForceArguments.m_forceFlags[i] & EF_TORQUE) != 0)
			{
				btVector3 torqueLocal(clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 0],
									  clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 1],
									  clientCmd.m_externalForceArguments.m_forcesAndTorques[i * 3 + 2]);

				btVector3 torqueWorld = isLinkFrame ? torqueLocal : rb->getWorldTransform().getBasis() * torqueLocal;
				rb->applyTorque(torqueWorld);
			}
		}
	}

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CLIENT_COMMAND_COMPLETED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRemoveBodyCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_REMOVE_BODY_FAILED;
	serverCmd.m_removeObjectArgs.m_numBodies = 0;
	serverCmd.m_removeObjectArgs.m_numUserConstraints = 0;

	m_data->m_guiHelper->setVisualizerFlag(COV_ENABLE_SYNC_RENDERING_INTERNAL, 0);

	for (int i = 0; i < clientCmd.m_removeObjectArgs.m_numBodies; i++)
	{
		int bodyUniqueId = clientCmd.m_removeObjectArgs.m_bodyUniqueIds[i];
		InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(bodyUniqueId);
		if (bodyHandle)
		{
			if (bodyHandle->m_multiBody)
			{
				serverCmd.m_removeObjectArgs.m_bodyUniqueIds[serverCmd.m_removeObjectArgs.m_numBodies++] = bodyUniqueId;

				if (m_data->m_pickingMultiBodyPoint2Point && m_data->m_pickingMultiBodyPoint2Point->getMultiBodyA() == bodyHandle->m_multiBody)
				{
					//memory will be deleted in the code that follows
					m_data->m_pickingMultiBodyPoint2Point = 0;
				}

				//also remove user constraints...
				for (int i = m_data->m_dynamicsWorld->getNumMultiBodyConstraints() - 1; i >= 0; i--)
				{
					btMultiBodyConstraint* mbc = m_data->m_dynamicsWorld->getMultiBodyConstraint(i);
					if ((mbc->getMultiBodyA() == bodyHandle->m_multiBody) || (mbc->getMultiBodyB() == bodyHandle->m_multiBody))
					{
						m_data->m_dynamicsWorld->removeMultiBodyConstraint(mbc);

						//also remove user constraint and submit it as removed
						for (int c = m_data->m_userConstraints.size() - 1; c >= 0; c--)
						{
							InteralUserConstraintData* userConstraintPtr = m_data->m_userConstraints.getAtIndex(c);
							int userConstraintKey = m_data->m_userConstraints.getKeyAtIndex(c).getUid1();

							if (userConstraintPtr->m_mbConstraint == mbc)
							{
								m_data->m_userConstraints.remove(userConstraintKey);
								serverCmd.m_removeObjectArgs.m_userConstraintUniqueIds[serverCmd.m_removeObjectArgs.m_numUserConstraints++] = userConstraintKey;
							}
						}

						delete mbc;
					}
				}

				if (bodyHandle->m_multiBody->getBaseCollider())
				{
					if (m_data->m_pluginManager.getRenderInterface())
					{
						m_data->m_pluginManager.getRenderInterface()->removeVisualShape(bodyHandle->m_multiBody->getBaseCollider()->getBroadphaseHandle()->getUid());
					}
					m_data->m_dynamicsWorld->removeCollisionObject(bodyHandle->m_multiBody->getBaseCollider());
					int graphicsIndex = bodyHandle->m_multiBody->getBaseCollider()->getUserIndex();
					m_data->m_guiHelper->removeGraphicsInstance(graphicsIndex);
				}
				for (int link = 0; link < bodyHandle->m_multiBody->getNumLinks(); link++)
				{
					if (bodyHandle->m_multiBody->getLink(link).m_collider)
					{
						if (m_data->m_pluginManager.getRenderInterface())
						{
							m_data->m_pluginManager.getRenderInterface()->removeVisualShape(bodyHandle->m_multiBody->getLink(link).m_collider->getBroadphaseHandle()->getUid());
						}
						m_data->m_dynamicsWorld->removeCollisionObject(bodyHandle->m_multiBody->getLink(link).m_collider);
						int graphicsIndex = bodyHandle->m_multiBody->getLink(link).m_collider->getUserIndex();
						m_data->m_guiHelper->removeGraphicsInstance(graphicsIndex);
					}
				}
				int numCollisionObjects = m_data->m_dynamicsWorld->getNumCollisionObjects();
				m_data->m_dynamicsWorld->removeMultiBody(bodyHandle->m_multiBody);
				numCollisionObjects = m_data->m_dynamicsWorld->getNumCollisionObjects();
				//todo: clear all other remaining data, release memory etc

				delete bodyHandle->m_multiBody;
				bodyHandle->m_multiBody = 0;
				serverCmd.m_type = CMD_REMOVE_BODY_COMPLETED;
			}
			if (bodyHandle->m_rigidBody)
			{
				if (m_data->m_pluginManager.getRenderInterface())
				{
					m_data->m_pluginManager.getRenderInterface()->removeVisualShape(bodyHandle->m_rigidBody->getBroadphaseHandle()->getUid());
				}
				serverCmd.m_removeObjectArgs.m_bodyUniqueIds[serverCmd.m_removeObjectArgs.m_numBodies++] = bodyUniqueId;

				if (m_data->m_pickedConstraint && m_data->m_pickedBody == bodyHandle->m_rigidBody)
				{
					m_data->m_pickedConstraint = 0;
					m_data->m_pickedBody = 0;
				}

				//todo: clear all other remaining data...
				m_data->m_dynamicsWorld->removeRigidBody(bodyHandle->m_rigidBody);
				int graphicsInstance = bodyHandle->m_rigidBody->getUserIndex2();
				m_data->m_guiHelper->removeGraphicsInstance(graphicsInstance);
				delete bodyHandle->m_rigidBody;
				bodyHandle->m_rigidBody = 0;
				serverCmd.m_type = CMD_REMOVE_BODY_COMPLETED;
			}
			for (int i = 0; i < bodyHandle->m_userDataHandles.size(); i++)
			{
				int userDataHandle = bodyHandle->m_userDataHandles[i];
				SharedMemoryUserData* userData = m_data->m_userDataHandles.getHandle(userDataHandle);
				m_data->m_userDataHandleLookup.remove(SharedMemoryUserDataHashKey(userData));
				m_data->m_userDataHandles.freeHandle(userDataHandle);
			}
			m_data->m_bodyHandles.freeHandle(bodyUniqueId);
		}
	}
	for (int i = 0; i < clientCmd.m_removeObjectArgs.m_numUserCollisionShapes; i++)
	{
		int removeCollisionShapeId = clientCmd.m_removeObjectArgs.m_userCollisionShapes[i];
		InternalCollisionShapeHandle* handle = m_data->m_userCollisionShapeHandles.getHandle(removeCollisionShapeId);
		if (handle && handle->m_collisionShape)
		{
			if (handle->m_used)
			{
				b3Warning("Don't remove collision shape: it is used.");
			}
			else
			{
				b3Warning("TODO: dealloc");
				int foundIndex = -1;

				for (int i = 0; i < m_data->m_worldImporters.size(); i++)
				{
					btMultiBodyWorldImporter* importer = m_data->m_worldImporters[i];
					for (int c = 0; c < importer->getNumCollisionShapes(); c++)
					{
						if (importer->getCollisionShapeByIndex(c) == handle->m_collisionShape)
						{
							if ((importer->getNumRigidBodies() == 0) &&
								(importer->getNumConstraints() == 0))
							{
								foundIndex = i;
								break;
							}
						}
					}
				}
				if (foundIndex >= 0)
				{
					btMultiBodyWorldImporter* importer = m_data->m_worldImporters[i];
					m_data->m_worldImporters.removeAtIndex(foundIndex);
					delete importer;
					m_data->m_userCollisionShapeHandles.freeHandle(removeCollisionShapeId);
				}
			}
		}
	}

	m_data->m_guiHelper->setVisualizerFlag(COV_ENABLE_SYNC_RENDERING_INTERNAL, 1);

	for (int i = 0; i < serverCmd.m_removeObjectArgs.m_numBodies; i++)
	{
		b3Notification notification;
		notification.m_notificationType = BODY_REMOVED;
		notification.m_bodyArgs.m_bodyUniqueId = serverCmd.m_removeObjectArgs.m_bodyUniqueIds[i];
		m_data->m_pluginManager.addNotification(notification);
	}

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCreateUserConstraintCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_USER_CONSTRAINT");

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_USER_CONSTRAINT_FAILED;
	hasStatus = true;
	if (clientCmd.m_updateFlags & USER_CONSTRAINT_REQUEST_STATE)
	{
		int constraintUid = clientCmd.m_userConstraintArguments.m_userConstraintUniqueId;
		InteralUserConstraintData* userConstraintPtr = m_data->m_userConstraints.find(constraintUid);
		if (userConstraintPtr)
		{
			serverCmd.m_userConstraintStateResultArgs.m_numDofs = 0;
			for (int i = 0; i < 6; i++)
			{
				serverCmd.m_userConstraintStateResultArgs.m_appliedConstraintForces[i] = 0;
			}
			if (userConstraintPtr->m_mbConstraint)
			{
				serverCmd.m_userConstraintStateResultArgs.m_numDofs = userConstraintPtr->m_mbConstraint->getNumRows();
				for (int i = 0; i < userConstraintPtr->m_mbConstraint->getNumRows(); i++)
				{
					serverCmd.m_userConstraintStateResultArgs.m_appliedConstraintForces[i] = userConstraintPtr->m_mbConstraint->getAppliedImpulse(i) / m_data->m_dynamicsWorld->getSolverInfo().m_timeStep;
				}
				serverCmd.m_type = CMD_USER_CONSTRAINT_REQUEST_STATE_COMPLETED;
			}
		}
	};
	if (clientCmd.m_updateFlags & USER_CONSTRAINT_REQUEST_INFO)
	{
		int userConstraintUidChange = clientCmd.m_userConstraintArguments.m_userConstraintUniqueId;
		InteralUserConstraintData* userConstraintPtr = m_data->m_userConstraints.find(userConstraintUidChange);
		if (userConstraintPtr)
		{
			serverCmd.m_userConstraintResultArgs = userConstraintPtr->m_userConstraintData;

			serverCmd.m_type = CMD_USER_CONSTRAINT_INFO_COMPLETED;
		}
	}
	if (clientCmd.m_updateFlags & USER_CONSTRAINT_ADD_CONSTRAINT)
	{
		btScalar defaultMaxForce = 500.0;
		InternalBodyData* parentBody = m_data->m_bodyHandles.getHandle(clientCmd.m_userConstraintArguments.m_parentBodyIndex);
		if (parentBody && parentBody->m_multiBody)
		{
			if ((clientCmd.m_userConstraintArguments.m_parentJointIndex >= -1) && clientCmd.m_userConstraintArguments.m_parentJointIndex < parentBody->m_multiBody->getNumLinks())
			{
				InternalBodyData* childBody = clientCmd.m_userConstraintArguments.m_childBodyIndex >= 0 ? m_data->m_bodyHandles.getHandle(clientCmd.m_userConstraintArguments.m_childBodyIndex) : 0;
				//also create a constraint with just a single multibody/rigid body without child
				//if (childBody)
				{
					btVector3 pivotInParent(clientCmd.m_userConstraintArguments.m_parentFrame[0], clientCmd.m_userConstraintArguments.m_parentFrame[1], clientCmd.m_userConstraintArguments.m_parentFrame[2]);
					btVector3 pivotInChild(clientCmd.m_userConstraintArguments.m_childFrame[0], clientCmd.m_userConstraintArguments.m_childFrame[1], clientCmd.m_userConstraintArguments.m_childFrame[2]);
					btMatrix3x3 frameInParent(btQuaternion(clientCmd.m_userConstraintArguments.m_parentFrame[3], clientCmd.m_userConstraintArguments.m_parentFrame[4], clientCmd.m_userConstraintArguments.m_parentFrame[5], clientCmd.m_userConstraintArguments.m_parentFrame[6]));
					btMatrix3x3 frameInChild(btQuaternion(clientCmd.m_userConstraintArguments.m_childFrame[3], clientCmd.m_userConstraintArguments.m_childFrame[4], clientCmd.m_userConstraintArguments.m_childFrame[5], clientCmd.m_userConstraintArguments.m_childFrame[6]));
					btVector3 jointAxis(clientCmd.m_userConstraintArguments.m_jointAxis[0], clientCmd.m_userConstraintArguments.m_jointAxis[1], clientCmd.m_userConstraintArguments.m_jointAxis[2]);

					if (clientCmd.m_userConstraintArguments.m_jointType == eGearType)
					{
						if (childBody && childBody->m_multiBody)
						{
							if ((clientCmd.m_userConstraintArguments.m_childJointIndex >= -1) && (clientCmd.m_userConstraintArguments.m_childJointIndex < childBody->m_multiBody->getNumLinks()))
							{
								btMultiBodyGearConstraint* multibodyGear = new btMultiBodyGearConstraint(parentBody->m_multiBody, clientCmd.m_userConstraintArguments.m_parentJointIndex, childBody->m_multiBody, clientCmd.m_userConstraintArguments.m_childJointIndex, pivotInParent, pivotInChild, frameInParent, frameInChild);
								multibodyGear->setMaxAppliedImpulse(defaultMaxForce);
								m_data->m_dynamicsWorld->addMultiBodyConstraint(multibodyGear);
								InteralUserConstraintData userConstraintData;
								userConstraintData.m_mbConstraint = multibodyGear;
								int uid = m_data->m_userConstraintUIDGenerator++;
								serverCmd.m_userConstraintResultArgs = clientCmd.m_userConstraintArguments;
								serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = uid;
								serverCmd.m_userConstraintResultArgs.m_maxAppliedForce = defaultMaxForce;
								userConstraintData.m_userConstraintData = serverCmd.m_userConstraintResultArgs;
								m_data->m_userConstraints.insert(uid, userConstraintData);
								serverCmd.m_type = CMD_USER_CONSTRAINT_COMPLETED;
							}
						}
					}
					else if (clientCmd.m_userConstraintArguments.m_jointType == eFixedType)
					{
						if (childBody && childBody->m_multiBody)
						{
							if ((clientCmd.m_userConstraintArguments.m_childJointIndex >= -1) && (clientCmd.m_userConstraintArguments.m_childJointIndex < childBody->m_multiBody->getNumLinks()))
							{
								btMultiBodyFixedConstraint* multibodyFixed = new btMultiBodyFixedConstraint(parentBody->m_multiBody, clientCmd.m_userConstraintArguments.m_parentJointIndex, childBody->m_multiBody, clientCmd.m_userConstraintArguments.m_childJointIndex, pivotInParent, pivotInChild, frameInParent, frameInChild);
								multibodyFixed->setMaxAppliedImpulse(defaultMaxForce);
								m_data->m_dynamicsWorld->addMultiBodyConstraint(multibodyFixed);
								InteralUserConstraintData userConstraintData;
								userConstraintData.m_mbConstraint = multibodyFixed;
								int uid = m_data->m_userConstraintUIDGenerator++;
								serverCmd.m_userConstraintResultArgs = clientCmd.m_userConstraintArguments;
								serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = uid;
								serverCmd.m_userConstraintResultArgs.m_maxAppliedForce = defaultMaxForce;
								userConstraintData.m_userConstraintData = serverCmd.m_userConstraintResultArgs;
								m_data->m_userConstraints.insert(uid, userConstraintData);
								serverCmd.m_type = CMD_USER_CONSTRAINT_COMPLETED;
							}
						}
						else
						{
							btRigidBody* rb = childBody ? childBody->m_rigidBody : 0;
							btMultiBodyFixedConstraint* rigidbodyFixed = new btMultiBodyFixedConstraint(parentBody->m_multiBody, clientCmd.m_userConstraintArguments.m_parentJointIndex, rb, pivotInParent, pivotInChild, frameInParent, frameInChild);
							rigidbodyFixed->setMaxAppliedImpulse(defaultMaxForce);
							btMultiBodyDynamicsWorld* world = (btMultiBodyDynamicsWorld*)m_data->m_dynamicsWorld;
							world->addMultiBodyConstraint(rigidbodyFixed);
							InteralUserConstraintData userConstraintData;
							userConstraintData.m_mbConstraint = rigidbodyFixed;
							int uid = m_data->m_userConstraintUIDGenerator++;
							serverCmd.m_userConstraintResultArgs = clientCmd.m_userConstraintArguments;
							serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = uid;
							serverCmd.m_userConstraintResultArgs.m_maxAppliedForce = defaultMaxForce;
							userConstraintData.m_userConstraintData = serverCmd.m_userConstraintResultArgs;
							m_data->m_userConstraints.insert(uid, userConstraintData);
							serverCmd.m_type = CMD_USER_CONSTRAINT_COMPLETED;
						}
					}
					else if (clientCmd.m_userConstraintArguments.m_jointType == ePrismaticType)
					{
						if (childBody && childBody->m_multiBody)
						{
							btMultiBodySliderConstraint* multibodySlider = new btMultiBodySliderConstraint(parentBody->m_multiBody, clientCmd.m_userConstraintArguments.m_parentJointIndex, childBody->m_multiBody, clientCmd.m_userConstraintArguments.m_childJointIndex, pivotInParent, pivotInChild, frameInParent, frameInChild, jointAxis);
							multibodySlider->setMaxAppliedImpulse(defaultMaxForce);
							m_data->m_dynamicsWorld->addMultiBodyConstraint(multibodySlider);
							InteralUserConstraintData userConstraintData;
							userConstraintData.m_mbConstraint = multibodySlider;
							int uid = m_data->m_userConstraintUIDGenerator++;
							serverCmd.m_userConstraintResultArgs = clientCmd.m_userConstraintArguments;
							serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = uid;
							serverCmd.m_userConstraintResultArgs.m_maxAppliedForce = defaultMaxForce;
							userConstraintData.m_userConstraintData = serverCmd.m_userConstraintResultArgs;
							m_data->m_userConstraints.insert(uid, userConstraintData);
							serverCmd.m_type = CMD_USER_CONSTRAINT_COMPLETED;
						}
						else
						{
							btRigidBody* rb = childBody ? childBody->m_rigidBody : 0;

							btMultiBodySliderConstraint* rigidbodySlider = new btMultiBodySliderConstraint(parentBody->m_multiBody, clientCmd.m_userConstraintArguments.m_parentJointIndex, rb, pivotInParent, pivotInChild, frameInParent, frameInChild, jointAxis);
							rigidbodySlider->setMaxAppliedImpulse(defaultMaxForce);
							btMultiBodyDynamicsWorld* world = (btMultiBodyDynamicsWorld*)m_data->m_dynamicsWorld;
							world->addMultiBodyConstraint(rigidbodySlider);
							InteralUserConstraintData userConstraintData;
							userConstraintData.m_mbConstraint = rigidbodySlider;
							int uid = m_data->m_userConstraintUIDGenerator++;
							serverCmd.m_userConstraintResultArgs = clientCmd.m_userConstraintArguments;
							serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = uid;
							serverCmd.m_userConstraintResultArgs.m_maxAppliedForce = defaultMaxForce;
							userConstraintData.m_userConstraintData = serverCmd.m_userConstraintResultArgs;
							m_data->m_userConstraints.insert(uid, userConstraintData);
							serverCmd.m_type = CMD_USER_CONSTRAINT_COMPLETED;
						}
					}
					else if (clientCmd.m_userConstraintArguments.m_jointType == ePoint2PointType)
					{
						if (childBody && childBody->m_multiBody)
						{
							btMultiBodyPoint2Point* p2p = new btMultiBodyPoint2Point(parentBody->m_multiBody, clientCmd.m_userConstraintArguments.m_parentJointIndex, childBody->m_multiBody, clientCmd.m_userConstraintArguments.m_childJointIndex, pivotInParent, pivotInChild);
							p2p->setMaxAppliedImpulse(defaultMaxForce);
							m_data->m_dynamicsWorld->addMultiBodyConstraint(p2p);
							InteralUserConstraintData userConstraintData;
							userConstraintData.m_mbConstraint = p2p;
							int uid = m_data->m_userConstraintUIDGenerator++;
							serverCmd.m_userConstraintResultArgs = clientCmd.m_userConstraintArguments;
							serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = uid;
							serverCmd.m_userConstraintResultArgs.m_maxAppliedForce = defaultMaxForce;
							userConstraintData.m_userConstraintData = serverCmd.m_userConstraintResultArgs;
							m_data->m_userConstraints.insert(uid, userConstraintData);
							serverCmd.m_type = CMD_USER_CONSTRAINT_COMPLETED;
						}
						else
						{
							btRigidBody* rb = childBody ? childBody->m_rigidBody : 0;

							btMultiBodyPoint2Point* p2p = new btMultiBodyPoint2Point(parentBody->m_multiBody, clientCmd.m_userConstraintArguments.m_parentJointIndex, rb, pivotInParent, pivotInChild);
							p2p->setMaxAppliedImpulse(defaultMaxForce);
							btMultiBodyDynamicsWorld* world = (btMultiBodyDynamicsWorld*)m_data->m_dynamicsWorld;
							world->addMultiBodyConstraint(p2p);
							InteralUserConstraintData userConstraintData;
							userConstraintData.m_mbConstraint = p2p;
							int uid = m_data->m_userConstraintUIDGenerator++;
							serverCmd.m_userConstraintResultArgs = clientCmd.m_userConstraintArguments;
							serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = uid;
							serverCmd.m_userConstraintResultArgs.m_maxAppliedForce = defaultMaxForce;
							userConstraintData.m_userConstraintData = serverCmd.m_userConstraintResultArgs;
							m_data->m_userConstraints.insert(uid, userConstraintData);
							serverCmd.m_type = CMD_USER_CONSTRAINT_COMPLETED;
						}
					}
					else
					{
						b3Warning("unknown constraint type");
					}
				}
			}
		}
		else
		{
			InternalBodyData* childBody = clientCmd.m_userConstraintArguments.m_childBodyIndex >= 0 ? m_data->m_bodyHandles.getHandle(clientCmd.m_userConstraintArguments.m_childBodyIndex) : 0;

			if (parentBody && childBody)
			{
				if (parentBody->m_rigidBody)
				{
					btRigidBody* parentRb = 0;
					if (clientCmd.m_userConstraintArguments.m_parentJointIndex == -1)
					{
						parentRb = parentBody->m_rigidBody;
					}
					else
					{
						if ((clientCmd.m_userConstraintArguments.m_parentJointIndex >= 0) &&
							(clientCmd.m_userConstraintArguments.m_parentJointIndex < parentBody->m_rigidBodyJoints.size()))
						{
							parentRb = &parentBody->m_rigidBodyJoints[clientCmd.m_userConstraintArguments.m_parentJointIndex]->getRigidBodyB();
						}
					}

					btRigidBody* childRb = 0;
					if (childBody->m_rigidBody)
					{
						if (clientCmd.m_userConstraintArguments.m_childJointIndex == -1)
						{
							childRb = childBody->m_rigidBody;
						}
						else
						{
							if ((clientCmd.m_userConstraintArguments.m_childJointIndex >= 0) && (clientCmd.m_userConstraintArguments.m_childJointIndex < childBody->m_rigidBodyJoints.size()))
							{
								childRb = &childBody->m_rigidBodyJoints[clientCmd.m_userConstraintArguments.m_childJointIndex]->getRigidBodyB();
							}
						}
					}

					switch (clientCmd.m_userConstraintArguments.m_jointType)
					{
						case eRevoluteType:
						{
							break;
						}
						case ePrismaticType:
						{
							break;
						}

						case eFixedType:
						{
							if (childRb && parentRb && (childRb != parentRb))
							{
								btVector3 pivotInParent(clientCmd.m_userConstraintArguments.m_parentFrame[0], clientCmd.m_userConstraintArguments.m_parentFrame[1], clientCmd.m_userConstraintArguments.m_parentFrame[2]);
								btVector3 pivotInChild(clientCmd.m_userConstraintArguments.m_childFrame[0], clientCmd.m_userConstraintArguments.m_childFrame[1], clientCmd.m_userConstraintArguments.m_childFrame[2]);

								btTransform offsetTrA, offsetTrB;
								offsetTrA.setIdentity();
								offsetTrA.setOrigin(pivotInParent);
								offsetTrB.setIdentity();
								offsetTrB.setOrigin(pivotInChild);

								btGeneric6DofSpring2Constraint* dof6 = new btGeneric6DofSpring2Constraint(*parentRb, *childRb, offsetTrA, offsetTrB);

								dof6->setLinearLowerLimit(btVector3(0, 0, 0));
								dof6->setLinearUpperLimit(btVector3(0, 0, 0));

								dof6->setAngularLowerLimit(btVector3(0, 0, 0));
								dof6->setAngularUpperLimit(btVector3(0, 0, 0));
								m_data->m_dynamicsWorld->addConstraint(dof6);
								InteralUserConstraintData userConstraintData;
								userConstraintData.m_rbConstraint = dof6;
								int uid = m_data->m_userConstraintUIDGenerator++;
								serverCmd.m_userConstraintResultArgs = clientCmd.m_userConstraintArguments;
								serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = uid;
								serverCmd.m_userConstraintResultArgs.m_maxAppliedForce = defaultMaxForce;
								userConstraintData.m_userConstraintData = serverCmd.m_userConstraintResultArgs;
								m_data->m_userConstraints.insert(uid, userConstraintData);
								serverCmd.m_type = CMD_USER_CONSTRAINT_COMPLETED;
							}

							break;
						}

						case ePoint2PointType:
						{
							if (childRb && parentRb && (childRb != parentRb))
							{
								btVector3 pivotInParent(clientCmd.m_userConstraintArguments.m_parentFrame[0], clientCmd.m_userConstraintArguments.m_parentFrame[1], clientCmd.m_userConstraintArguments.m_parentFrame[2]);
								btVector3 pivotInChild(clientCmd.m_userConstraintArguments.m_childFrame[0], clientCmd.m_userConstraintArguments.m_childFrame[1], clientCmd.m_userConstraintArguments.m_childFrame[2]);

								btPoint2PointConstraint* p2p = new btPoint2PointConstraint(*parentRb, *childRb, pivotInParent, pivotInChild);
								p2p->m_setting.m_impulseClamp = defaultMaxForce;
								m_data->m_dynamicsWorld->addConstraint(p2p);
								InteralUserConstraintData userConstraintData;
								userConstraintData.m_rbConstraint = p2p;
								int uid = m_data->m_userConstraintUIDGenerator++;
								serverCmd.m_userConstraintResultArgs = clientCmd.m_userConstraintArguments;
								serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = uid;
								serverCmd.m_userConstraintResultArgs.m_maxAppliedForce = defaultMaxForce;
								userConstraintData.m_userConstraintData = serverCmd.m_userConstraintResultArgs;
								m_data->m_userConstraints.insert(uid, userConstraintData);
								serverCmd.m_type = CMD_USER_CONSTRAINT_COMPLETED;
							}
							break;
						}

						case eGearType:
						{
							if (childRb && parentRb && (childRb != parentRb))
							{
								btVector3 axisA(clientCmd.m_userConstraintArguments.m_jointAxis[0],
												clientCmd.m_userConstraintArguments.m_jointAxis[1],
												clientCmd.m_userConstraintArguments.m_jointAxis[2]);
								//for now we use the same local axis for both objects
								btVector3 axisB(clientCmd.m_userConstraintArguments.m_jointAxis[0],
												clientCmd.m_userConstraintArguments.m_jointAxis[1],
												clientCmd.m_userConstraintArguments.m_jointAxis[2]);
								btScalar ratio = 1;
								btGearConstraint* gear = new btGearConstraint(*parentRb, *childRb, axisA, axisB, ratio);
								m_data->m_dynamicsWorld->addConstraint(gear, true);
								InteralUserConstraintData userConstraintData;
								userConstraintData.m_rbConstraint = gear;
								int uid = m_data->m_userConstraintUIDGenerator++;
								serverCmd.m_userConstraintResultArgs = clientCmd.m_userConstraintArguments;
								serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = uid;
								serverCmd.m_userConstraintResultArgs.m_maxAppliedForce = defaultMaxForce;
								userConstraintData.m_userConstraintData = serverCmd.m_userConstraintResultArgs;
								m_data->m_userConstraints.insert(uid, userConstraintData);
								serverCmd.m_type = CMD_USER_CONSTRAINT_COMPLETED;
							}
							break;
						}
						case eSphericalType:
						{
							b3Warning("constraint type not handled yet");
							break;
						}
						case ePlanarType:
						{
							b3Warning("constraint type not handled yet");
							break;
						}
						default:
						{
							b3Warning("unknown constraint type");
						}
					};
				}
			}
		}
	}

	if (clientCmd.m_updateFlags & USER_CONSTRAINT_CHANGE_CONSTRAINT)
	{
		serverCmd.m_type = CMD_CHANGE_USER_CONSTRAINT_FAILED;
		int userConstraintUidChange = clientCmd.m_userConstraintArguments.m_userConstraintUniqueId;
		InteralUserConstraintData* userConstraintPtr = m_data->m_userConstraints.find(userConstraintUidChange);
		if (userConstraintPtr)
		{
			if (userConstraintPtr->m_mbConstraint)
			{
				if (clientCmd.m_updateFlags & USER_CONSTRAINT_CHANGE_PIVOT_IN_B)
				{
					btVector3 pivotInB(clientCmd.m_userConstraintArguments.m_childFrame[0],
									   clientCmd.m_userConstraintArguments.m_childFrame[1],
									   clientCmd.m_userConstraintArguments.m_childFrame[2]);
					userConstraintPtr->m_userConstraintData.m_childFrame[0] = clientCmd.m_userConstraintArguments.m_childFrame[0];
					userConstraintPtr->m_userConstraintData.m_childFrame[1] = clientCmd.m_userConstraintArguments.m_childFrame[1];
					userConstraintPtr->m_userConstraintData.m_childFrame[2] = clientCmd.m_userConstraintArguments.m_childFrame[2];
					userConstraintPtr->m_mbConstraint->setPivotInB(pivotInB);
				}
				if (clientCmd.m_updateFlags & USER_CONSTRAINT_CHANGE_FRAME_ORN_IN_B)
				{
					btQuaternion childFrameOrn(clientCmd.m_userConstraintArguments.m_childFrame[3],
											   clientCmd.m_userConstraintArguments.m_childFrame[4],
											   clientCmd.m_userConstraintArguments.m_childFrame[5],
											   clientCmd.m_userConstraintArguments.m_childFrame[6]);
					userConstraintPtr->m_userConstraintData.m_childFrame[3] = clientCmd.m_userConstraintArguments.m_childFrame[3];
					userConstraintPtr->m_userConstraintData.m_childFrame[4] = clientCmd.m_userConstraintArguments.m_childFrame[4];
					userConstraintPtr->m_userConstraintData.m_childFrame[5] = clientCmd.m_userConstraintArguments.m_childFrame[5];
					userConstraintPtr->m_userConstraintData.m_childFrame[6] = clientCmd.m_userConstraintArguments.m_childFrame[6];
					btMatrix3x3 childFrameBasis(childFrameOrn);
					userConstraintPtr->m_mbConstraint->setFrameInB(childFrameBasis);
				}
				if (clientCmd.m_updateFlags & USER_CONSTRAINT_CHANGE_MAX_FORCE)
				{
					btScalar maxImp = clientCmd.m_userConstraintArguments.m_maxAppliedForce * m_data->m_physicsDeltaTime;
					userConstraintPtr->m_userConstraintData.m_maxAppliedForce = clientCmd.m_userConstraintArguments.m_maxAppliedForce;
					userConstraintPtr->m_mbConstraint->setMaxAppliedImpulse(maxImp);
				}

				if (clientCmd.m_updateFlags & USER_CONSTRAINT_CHANGE_GEAR_RATIO)
				{
					userConstraintPtr->m_mbConstraint->setGearRatio(clientCmd.m_userConstraintArguments.m_gearRatio);
					userConstraintPtr->m_userConstraintData.m_gearRatio = clientCmd.m_userConstraintArguments.m_gearRatio;
				}
				if (clientCmd.m_updateFlags & USER_CONSTRAINT_CHANGE_RELATIVE_POSITION_TARGET)
				{
					userConstraintPtr->m_mbConstraint->setRelativePositionTarget(clientCmd.m_userConstraintArguments.m_relativePositionTarget);
					userConstraintPtr->m_userConstraintData.m_relativePositionTarget = clientCmd.m_userConstraintArguments.m_relativePositionTarget;
				}
				if (clientCmd.m_updateFlags & USER_CONSTRAINT_CHANGE_ERP)
				{
					userConstraintPtr->m_mbConstraint->setErp(clientCmd.m_userConstraintArguments.m_erp);
					userConstraintPtr->m_userConstraintData.m_erp = clientCmd.m_userConstraintArguments.m_erp;
				}

				if (clientCmd.m_updateFlags & USER_CONSTRAINT_CHANGE_GEAR_AUX_LINK)
				{
					userConstraintPtr->m_mbConstraint->setGearAuxLink(clientCmd.m_userConstraintArguments.m_gearAuxLink);
					userConstraintPtr->m_userConstraintData.m_gearAuxLink = clientCmd.m_userConstraintArguments.m_gearAuxLink;
				}
			}
			if (userConstraintPtr->m_rbConstraint)
			{
				if (clientCmd.m_updateFlags & USER_CONSTRAINT_CHANGE_MAX_FORCE)
				{
					btScalar maxImp = clientCmd.m_userConstraintArguments.m_maxAppliedForce * m_data->m_physicsDeltaTime;
					userConstraintPtr->m_userConstraintData.m_maxAppliedForce = clientCmd.m_userConstraintArguments.m_maxAppliedForce;
					//userConstraintPtr->m_rbConstraint->setMaxAppliedImpulse(maxImp);
				}

				if (clientCmd.m_updateFlags & USER_CONSTRAINT_CHANGE_GEAR_RATIO)
				{
					if (userConstraintPtr->m_rbConstraint->getObjectType() == GEAR_CONSTRAINT_TYPE)
					{
						btGearConstraint* gear = (btGearConstraint*)userConstraintPtr->m_rbConstraint;
						gear->setRatio(clientCmd.m_userConstraintArguments.m_gearRatio);
					}
				}
			}
			serverCmd.m_userConstraintResultArgs = clientCmd.m_userConstraintArguments;
			serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = userConstraintUidChange;
			serverCmd.m_updateFlags = clientCmd.m_updateFlags;
			serverCmd.m_type = CMD_CHANGE_USER_CONSTRAINT_COMPLETED;
		}
	}
	if (clientCmd.m_updateFlags & USER_CONSTRAINT_REMOVE_CONSTRAINT)
	{
		serverCmd.m_type = CMD_REMOVE_USER_CONSTRAINT_FAILED;
		int userConstraintUidRemove = clientCmd.m_userConstraintArguments.m_userConstraintUniqueId;
		InteralUserConstraintData* userConstraintPtr = m_data->m_userConstraints.find(userConstraintUidRemove);
		if (userConstraintPtr)
		{
			if (userConstraintPtr->m_mbConstraint)
			{
				m_data->m_dynamicsWorld->removeMultiBodyConstraint(userConstraintPtr->m_mbConstraint);
				delete userConstraintPtr->m_mbConstraint;
				m_data->m_userConstraints.remove(userConstraintUidRemove);
			}
			if (userConstraintPtr->m_rbConstraint)
			{
				m_data->m_dynamicsWorld->removeConstraint(userConstraintPtr->m_rbConstraint);
				delete userConstraintPtr->m_rbConstraint;
				m_data->m_userConstraints.remove(userConstraintUidRemove);
			}
			serverCmd.m_userConstraintResultArgs.m_userConstraintUniqueId = userConstraintUidRemove;
			serverCmd.m_type = CMD_REMOVE_USER_CONSTRAINT_COMPLETED;
		}
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCalculateInverseKinematicsCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_CALCULATE_INVERSE_KINEMATICS");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CALCULATE_INVERSE_KINEMATICS_FAILED;

	InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(clientCmd.m_calculateInverseKinematicsArguments.m_bodyUniqueId);
	if (bodyHandle && bodyHandle->m_multiBody)
	{
		IKTrajectoryHelper** ikHelperPtrPtr = m_data->m_inverseKinematicsHelpers.find(bodyHandle->m_multiBody);
		IKTrajectoryHelper* ikHelperPtr = 0;

		if (ikHelperPtrPtr)
		{
			ikHelperPtr = *ikHelperPtrPtr;
		}
		else
		{
			IKTrajectoryHelper* tmpHelper = new IKTrajectoryHelper;
			m_data->m_inverseKinematicsHelpers.insert(bodyHandle->m_multiBody, tmpHelper);
			ikHelperPtr = tmpHelper;
		}

		int endEffectorLinkIndex = clientCmd.m_calculateInverseKinematicsArguments.m_endEffectorLinkIndex;

		btAlignedObjectArray<double> startingPositions;
		startingPositions.reserve(bodyHandle->m_multiBody->getNumLinks());

		btVector3 targetPosWorld(clientCmd.m_calculateInverseKinematicsArguments.m_targetPosition[0],
								 clientCmd.m_calculateInverseKinematicsArguments.m_targetPosition[1],
								 clientCmd.m_calculateInverseKinematicsArguments.m_targetPosition[2]);

		btQuaternion targetOrnWorld(clientCmd.m_calculateInverseKinematicsArguments.m_targetOrientation[0],
									clientCmd.m_calculateInverseKinematicsArguments.m_targetOrientation[1],
									clientCmd.m_calculateInverseKinematicsArguments.m_targetOrientation[2],
									clientCmd.m_calculateInverseKinematicsArguments.m_targetOrientation[3]);

		btTransform targetBaseCoord;
		if (clientCmd.m_updateFlags & IK_HAS_CURRENT_JOINT_POSITIONS)
		{
			targetBaseCoord.setOrigin(targetPosWorld);
			targetBaseCoord.setRotation(targetOrnWorld);
		}
		else
		{
			btTransform targetWorld;
			targetWorld.setOrigin(targetPosWorld);
			targetWorld.setRotation(targetOrnWorld);
			btTransform tr = bodyHandle->m_multiBody->getBaseWorldTransform();
			targetBaseCoord = tr.inverse() * targetWorld;
		}

		{
			int DofIndex = 0;
			for (int i = 0; i < bodyHandle->m_multiBody->getNumLinks(); ++i)
			{
				if (bodyHandle->m_multiBody->getLink(i).m_jointType >= 0 && bodyHandle->m_multiBody->getLink(i).m_jointType <= 2)
				{
					// 0, 1, 2 represent revolute, prismatic, and spherical joint types respectively. Skip the fixed joints.
					double curPos = 0;
					if (clientCmd.m_updateFlags & IK_HAS_CURRENT_JOINT_POSITIONS)
					{
						curPos = clientCmd.m_calculateInverseKinematicsArguments.m_currentPositions[DofIndex];
					}
					else
					{
						curPos = bodyHandle->m_multiBody->getJointPos(i);
					}
					startingPositions.push_back(curPos);
					DofIndex++;
				}
			}
		}

		int numIterations = 20;
		if (clientCmd.m_updateFlags & IK_HAS_MAX_ITERATIONS)
		{
			numIterations = clientCmd.m_calculateInverseKinematicsArguments.m_maxNumIterations;
		}
		double residualThreshold = 1e-4;
		if (clientCmd.m_updateFlags & IK_HAS_RESIDUAL_THRESHOLD)
		{
			residualThreshold = clientCmd.m_calculateInverseKinematicsArguments.m_residualThreshold;
		}

		btScalar currentDiff = 1e30f;
		b3AlignedObjectArray<double> jacobian_linear;
		b3AlignedObjectArray<double> jacobian_angular;
		btAlignedObjectArray<double> q_current;
		btAlignedObjectArray<double> q_new;
		btAlignedObjectArray<double> lower_limit;
		btAlignedObjectArray<double> upper_limit;
		btAlignedObjectArray<double> joint_range;
		btAlignedObjectArray<double> rest_pose;
		const int numDofs = bodyHandle->m_multiBody->getNumDofs();
		int baseDofs = bodyHandle->m_multiBody->hasFixedBase() ? 0 : 6;
		btInverseDynamics::vecx nu(numDofs + baseDofs), qdot(numDofs + baseDofs), q(numDofs + baseDofs), joint_force(numDofs + baseDofs);

		for (int i = 0; i < numIterations && currentDiff > residualThreshold; i++)
		{
			BT_PROFILE("InverseKinematics1Step");
			if (ikHelperPtr && (endEffectorLinkIndex < bodyHandle->m_multiBody->getNumLinks()))
			{
				jacobian_linear.resize(3 * numDofs);
				jacobian_angular.resize(3 * numDofs);
				int jacSize = 0;

				btInverseDynamics::MultiBodyTree* tree = m_data->findOrCreateTree(bodyHandle->m_multiBody);

				q_current.resize(numDofs);

				if (tree && ((numDofs + baseDofs) == tree->numDoFs()))
				{
					btInverseDynamics::vec3 world_origin;
					btInverseDynamics::mat33 world_rot;

					jacSize = jacobian_linear.size();
					// Set jacobian value

					int DofIndex = 0;
					for (int i = 0; i < bodyHandle->m_multiBody->getNumLinks(); ++i)
					{
						if (bodyHandle->m_multiBody->getLink(i).m_jointType >= 0 && bodyHandle->m_multiBody->getLink(i).m_jointType <= 2)
						{
							// 0, 1, 2 represent revolute, prismatic, and spherical joint types respectively. Skip the fixed joints.

							double curPos = startingPositions[DofIndex];
							q_current[DofIndex] = curPos;
							q[DofIndex + baseDofs] = curPos;
							qdot[DofIndex + baseDofs] = 0;
							nu[DofIndex + baseDofs] = 0;
							DofIndex++;
						}
					}  // Set the gravity to correspond to the world gravity
					btInverseDynamics::vec3 id_grav(m_data->m_dynamicsWorld->getGravity());

					{
						BT_PROFILE("calculateInverseDynamics");
						if (-1 != tree->setGravityInWorldFrame(id_grav) &&
							-1 != tree->calculateInverseDynamics(q, qdot, nu, &joint_force))
						{
							tree->calculateJacobians(q);
							btInverseDynamics::mat3x jac_t(3, numDofs + baseDofs);
							btInverseDynamics::mat3x jac_r(3, numDofs + baseDofs);
							// Note that inverse dynamics uses zero-based indexing of bodies, not starting from -1 for the base link.
							tree->getBodyJacobianTrans(endEffectorLinkIndex + 1, &jac_t);
							tree->getBodyJacobianRot(endEffectorLinkIndex + 1, &jac_r);

							//calculatePositionKinematics is already done inside calculateInverseDynamics
							tree->getBodyOrigin(endEffectorLinkIndex + 1, &world_origin);
							tree->getBodyTransform(endEffectorLinkIndex + 1, &world_rot);

							for (int i = 0; i < 3; ++i)
							{
								for (int j = 0; j < numDofs; ++j)
								{
									jacobian_linear[i * numDofs + j] = jac_t(i, (baseDofs + j));
									jacobian_angular[i * numDofs + j] = jac_r(i, (baseDofs + j));
								}
							}
						}
					}

					q_new.resize(numDofs);
					int ikMethod = 0;
					if ((clientCmd.m_updateFlags & IK_HAS_TARGET_ORIENTATION) && (clientCmd.m_updateFlags & IK_HAS_NULL_SPACE_VELOCITY))
					{
						//Nullspace task only works with DLS now. TODO: add nullspace task to SDLS.
						ikMethod = IK2_VEL_DLS_WITH_ORIENTATION_NULLSPACE;
					}
					else if (clientCmd.m_updateFlags & IK_HAS_TARGET_ORIENTATION)
					{
						if (clientCmd.m_updateFlags & IK_SDLS)
						{
							ikMethod = IK2_VEL_SDLS_WITH_ORIENTATION;
						}
						else
						{
							ikMethod = IK2_VEL_DLS_WITH_ORIENTATION;
						}
					}
					else if (clientCmd.m_updateFlags & IK_HAS_NULL_SPACE_VELOCITY)
					{
						//Nullspace task only works with DLS now. TODO: add nullspace task to SDLS.
						ikMethod = IK2_VEL_DLS_WITH_NULLSPACE;
					}
					else
					{
						if (clientCmd.m_updateFlags & IK_SDLS)
						{
							ikMethod = IK2_VEL_SDLS;
						}
						else
						{
							ikMethod = IK2_VEL_DLS;
							;
						}
					}

					if (clientCmd.m_updateFlags & IK_HAS_NULL_SPACE_VELOCITY)
					{
						lower_limit.resize(numDofs);
						upper_limit.resize(numDofs);
						joint_range.resize(numDofs);
						rest_pose.resize(numDofs);
						for (int i = 0; i < numDofs; ++i)
						{
							lower_limit[i] = clientCmd.m_calculateInverseKinematicsArguments.m_lowerLimit[i];
							upper_limit[i] = clientCmd.m_calculateInverseKinematicsArguments.m_upperLimit[i];
							joint_range[i] = clientCmd.m_calculateInverseKinematicsArguments.m_jointRange[i];
							rest_pose[i] = clientCmd.m_calculateInverseKinematicsArguments.m_restPose[i];
						}
						{
							BT_PROFILE("computeNullspaceVel");
							ikHelperPtr->computeNullspaceVel(numDofs, &q_current[0], &lower_limit[0], &upper_limit[0], &joint_range[0], &rest_pose[0]);
						}
					}

					//btTransform endEffectorTransformWorld = bodyHandle->m_multiBody->getLink(endEffectorLinkIndex).m_cachedWorldTransform * bodyHandle->m_linkLocalInertialFrames[endEffectorLinkIndex].inverse();

					btVector3DoubleData endEffectorWorldPosition;
					btQuaternionDoubleData endEffectorWorldOrientation;

					//get the position from the inverse dynamics (based on q) instead of endEffectorTransformWorld
					btVector3 endEffectorPosWorldOrg = world_origin;
					btQuaternion endEffectorOriWorldOrg;
					world_rot.getRotation(endEffectorOriWorldOrg);

					btTransform endEffectorBaseCoord;
					endEffectorBaseCoord.setOrigin(endEffectorPosWorldOrg);
					endEffectorBaseCoord.setRotation(endEffectorOriWorldOrg);

					//don't need the next two lines
					//btTransform linkInertiaInv = bodyHandle->m_linkLocalInertialFrames[endEffectorLinkIndex].inverse();
					//endEffectorBaseCoord = endEffectorBaseCoord * linkInertiaInv;

					//btTransform tr = bodyHandle->m_multiBody->getBaseWorldTransform();
					//endEffectorBaseCoord = tr.inverse()*endEffectorTransformWorld;
					//endEffectorBaseCoord = tr.inverse()*endEffectorTransformWorld;

					btQuaternion endEffectorOriBaseCoord = endEffectorBaseCoord.getRotation();

					//btVector4 endEffectorOri(endEffectorOriBaseCoord.x(), endEffectorOriBaseCoord.y(), endEffectorOriBaseCoord.z(), endEffectorOriBaseCoord.w());

					endEffectorBaseCoord.getOrigin().serializeDouble(endEffectorWorldPosition);
					endEffectorBaseCoord.getRotation().serializeDouble(endEffectorWorldOrientation);

					//diff
					currentDiff = (endEffectorBaseCoord.getOrigin() - targetBaseCoord.getOrigin()).length();

					btVector3DoubleData targetPosBaseCoord;
					btQuaternionDoubleData targetOrnBaseCoord;
					targetBaseCoord.getOrigin().serializeDouble(targetPosBaseCoord);
					targetBaseCoord.getRotation().serializeDouble(targetOrnBaseCoord);

					// Set joint damping coefficents. A small default
					// damping constant is added to prevent singularity
					// with pseudo inverse. The user can set joint damping
					// coefficients differently for each joint. The larger
					// the damping coefficient is, the less we rely on
					// this joint to achieve the IK target.
					btAlignedObjectArray<double> joint_damping;
					joint_damping.resize(numDofs, 0.5);
					if (clientCmd.m_updateFlags & IK_HAS_JOINT_DAMPING)
					{
						for (int i = 0; i < numDofs; ++i)
						{
							joint_damping[i] = clientCmd.m_calculateInverseKinematicsArguments.m_jointDamping[i];
						}
					}
					ikHelperPtr->setDampingCoeff(numDofs, &joint_damping[0]);

					double targetDampCoeff[6] = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0};

					{
						BT_PROFILE("computeIK");
						ikHelperPtr->computeIK(targetPosBaseCoord.m_floats, targetOrnBaseCoord.m_floats,
											   endEffectorWorldPosition.m_floats, endEffectorWorldOrientation.m_floats,
											   &q_current[0],
											   numDofs, clientCmd.m_calculateInverseKinematicsArguments.m_endEffectorLinkIndex,
											   &q_new[0], ikMethod, &jacobian_linear[0], &jacobian_angular[0], jacSize * 2, targetDampCoeff);
					}
					serverCmd.m_inverseKinematicsResultArgs.m_bodyUniqueId = clientCmd.m_calculateInverseDynamicsArguments.m_bodyUniqueId;
					for (int i = 0; i < numDofs; i++)
					{
						serverCmd.m_inverseKinematicsResultArgs.m_jointPositions[i] = q_new[i];
					}
					serverCmd.m_inverseKinematicsResultArgs.m_dofCount = numDofs;
					serverCmd.m_type = CMD_CALCULATE_INVERSE_KINEMATICS_COMPLETED;
					for (int i = 0; i < numDofs; i++)
					{
						startingPositions[i] = q_new[i];
					}
				}
			}
		}
	}
	return hasStatus;
}

//		PyModule_AddIntConstant(m, "GEOM_SPHERE", GEOM_SPHERE);
//		PyModule_AddIntConstant(m, "GEOM_BOX", GEOM_BOX);
//		PyModule_AddIntConstant(m, "GEOM_CYLINDER", GEOM_CYLINDER);
//		PyModule_AddIntConstant(m, "GEOM_MESH", GEOM_MESH);
//		PyModule_AddIntConstant(m, "GEOM_PLANE", GEOM_PLANE);
//		PyModule_AddIntConstant(m, "GEOM_CAPSULE", GEOM_CAPSULE);

int PhysicsServerCommandProcessor::extractCollisionShapes(const btCollisionShape* colShape, const btTransform& transform, b3CollisionShapeData* collisionShapeBuffer, int maxCollisionShapes)
{
	if (maxCollisionShapes <= 0)
	{
		b3Warning("No space in buffer");
		return 0;
	}

	int numConverted = 0;

	collisionShapeBuffer[0].m_localCollisionFrame[0] = transform.getOrigin()[0];
	collisionShapeBuffer[0].m_localCollisionFrame[1] = transform.getOrigin()[1];
	collisionShapeBuffer[0].m_localCollisionFrame[2] = transform.getOrigin()[2];
	collisionShapeBuffer[0].m_localCollisionFrame[3] = transform.getRotation()[0];
	collisionShapeBuffer[0].m_localCollisionFrame[4] = transform.getRotation()[1];
	collisionShapeBuffer[0].m_localCollisionFrame[5] = transform.getRotation()[2];
	collisionShapeBuffer[0].m_localCollisionFrame[6] = transform.getRotation()[3];
	collisionShapeBuffer[0].m_meshAssetFileName[0] = 0;

	switch (colShape->getShapeType())
	{
		case CONVEX_HULL_SHAPE_PROXYTYPE:
		{
			UrdfCollision* urdfCol = m_data->m_bulletCollisionShape2UrdfCollision.find(colShape);
			if (urdfCol && (urdfCol->m_geometry.m_type == URDF_GEOM_MESH))
			{
				collisionShapeBuffer[0].m_collisionGeometryType = GEOM_MESH;
				collisionShapeBuffer[0].m_dimensions[0] = urdfCol->m_geometry.m_meshScale[0];
				collisionShapeBuffer[0].m_dimensions[1] = urdfCol->m_geometry.m_meshScale[1];
				collisionShapeBuffer[0].m_dimensions[2] = urdfCol->m_geometry.m_meshScale[2];
				strcpy(collisionShapeBuffer[0].m_meshAssetFileName, urdfCol->m_geometry.m_meshFileName.c_str());
				numConverted += 1;
			}
			else
			{
				collisionShapeBuffer[0].m_collisionGeometryType = GEOM_MESH;
				sprintf(collisionShapeBuffer[0].m_meshAssetFileName, "unknown_file");
				collisionShapeBuffer[0].m_dimensions[0] = 1;
				collisionShapeBuffer[0].m_dimensions[1] = 1;
				collisionShapeBuffer[0].m_dimensions[2] = 1;
				numConverted++;
			}

			break;
		}
		case CAPSULE_SHAPE_PROXYTYPE:
		{
			btCapsuleShapeZ* capsule = (btCapsuleShapeZ*)colShape;
			collisionShapeBuffer[0].m_collisionGeometryType = GEOM_CAPSULE;
			collisionShapeBuffer[0].m_dimensions[0] = 2. * capsule->getHalfHeight();
			collisionShapeBuffer[0].m_dimensions[1] = capsule->getRadius();
			collisionShapeBuffer[0].m_dimensions[2] = 0;
			numConverted++;
			break;
		}
		case CYLINDER_SHAPE_PROXYTYPE:
		{
			btCylinderShapeZ* cyl = (btCylinderShapeZ*)colShape;
			collisionShapeBuffer[0].m_collisionGeometryType = GEOM_CYLINDER;
			collisionShapeBuffer[0].m_dimensions[0] = 2. * cyl->getHalfExtentsWithMargin().getZ();
			collisionShapeBuffer[0].m_dimensions[1] = cyl->getHalfExtentsWithMargin().getX();
			collisionShapeBuffer[0].m_dimensions[2] = 0;
			numConverted++;
			break;
		}
		case BOX_SHAPE_PROXYTYPE:
		{
			btBoxShape* box = (btBoxShape*)colShape;
			btVector3 halfExtents = box->getHalfExtentsWithMargin();
			collisionShapeBuffer[0].m_collisionGeometryType = GEOM_BOX;
			collisionShapeBuffer[0].m_dimensions[0] = 2. * halfExtents[0];
			collisionShapeBuffer[0].m_dimensions[1] = 2. * halfExtents[1];
			collisionShapeBuffer[0].m_dimensions[2] = 2. * halfExtents[2];
			numConverted++;
			break;
		}
		case SPHERE_SHAPE_PROXYTYPE:
		{
			btSphereShape* sphere = (btSphereShape*)colShape;
			collisionShapeBuffer[0].m_collisionGeometryType = GEOM_SPHERE;
			collisionShapeBuffer[0].m_dimensions[0] = sphere->getRadius();
			collisionShapeBuffer[0].m_dimensions[1] = sphere->getRadius();
			collisionShapeBuffer[0].m_dimensions[2] = sphere->getRadius();
			numConverted++;
			break;
		}
		case COMPOUND_SHAPE_PROXYTYPE:
		{
			//it could be a compound mesh from a wavefront OBJ, check it
			UrdfCollision* urdfCol = m_data->m_bulletCollisionShape2UrdfCollision.find(colShape);
			if (urdfCol && (urdfCol->m_geometry.m_type == URDF_GEOM_MESH))
			{
				collisionShapeBuffer[0].m_collisionGeometryType = GEOM_MESH;
				collisionShapeBuffer[0].m_dimensions[0] = urdfCol->m_geometry.m_meshScale[0];
				collisionShapeBuffer[0].m_dimensions[1] = urdfCol->m_geometry.m_meshScale[1];
				collisionShapeBuffer[0].m_dimensions[2] = urdfCol->m_geometry.m_meshScale[2];
				strcpy(collisionShapeBuffer[0].m_meshAssetFileName, urdfCol->m_geometry.m_meshFileName.c_str());
				numConverted += 1;
			}
			else
			{
				//recurse, accumulate childTransform
				btCompoundShape* compound = (btCompoundShape*)colShape;
				for (int i = 0; i < compound->getNumChildShapes(); i++)
				{
					btTransform childTrans = transform * compound->getChildTransform(i);
					int remain = maxCollisionShapes - numConverted;
					int converted = extractCollisionShapes(compound->getChildShape(i), childTrans, &collisionShapeBuffer[numConverted], remain);
					numConverted += converted;
				}
			}
			break;
		}
		default:
		{
			b3Warning("Unexpected collision shape type in PhysicsServerCommandProcessor::extractCollisionShapes");
		}
	};

	return numConverted;
}

bool PhysicsServerCommandProcessor::processRequestCollisionShapeInfoCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_REQUEST_COLLISION_SHAPE_INFO");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_COLLISION_SHAPE_INFO_FAILED;
	int bodyUniqueId = clientCmd.m_requestCollisionShapeDataArguments.m_bodyUniqueId;
	int linkIndex = clientCmd.m_requestCollisionShapeDataArguments.m_linkIndex;
	InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(bodyUniqueId);
	if (bodyHandle)
	{
		if (bodyHandle->m_multiBody)
		{
			b3CollisionShapeData* collisionShapeStoragePtr = (b3CollisionShapeData*)bufferServerToClient;
			int totalBytesPerObject = sizeof(b3CollisionShapeData);
			int maxNumColObjects = bufferSizeInBytes / totalBytesPerObject - 1;
			btTransform childTrans;
			childTrans.setIdentity();
			serverCmd.m_sendCollisionShapeArgs.m_bodyUniqueId = bodyUniqueId;
			serverCmd.m_sendCollisionShapeArgs.m_linkIndex = linkIndex;

			if (linkIndex == -1)
			{
				if (bodyHandle->m_multiBody->getBaseCollider())
				{
					//extract shape info from base collider
					int numConvertedCollisionShapes = extractCollisionShapes(bodyHandle->m_multiBody->getBaseCollider()->getCollisionShape(), childTrans, collisionShapeStoragePtr, maxNumColObjects);
					serverCmd.m_sendCollisionShapeArgs.m_numCollisionShapes = numConvertedCollisionShapes;
					serverCmd.m_type = CMD_COLLISION_SHAPE_INFO_COMPLETED;
				}
			}
			else
			{
				if (linkIndex >= 0 && linkIndex < bodyHandle->m_multiBody->getNumLinks() && bodyHandle->m_multiBody->getLinkCollider(linkIndex))
				{
					int numConvertedCollisionShapes = extractCollisionShapes(bodyHandle->m_multiBody->getLinkCollider(linkIndex)->getCollisionShape(), childTrans, collisionShapeStoragePtr, maxNumColObjects);
					serverCmd.m_sendCollisionShapeArgs.m_numCollisionShapes = numConvertedCollisionShapes;
					serverCmd.m_type = CMD_COLLISION_SHAPE_INFO_COMPLETED;
				}
			}
		}
	}

	return hasStatus;
}
bool PhysicsServerCommandProcessor::processRequestVisualShapeInfoCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_REQUEST_VISUAL_SHAPE_INFO");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_VISUAL_SHAPE_INFO_FAILED;
	//retrieve the visual shape information for a specific body

	if (m_data->m_pluginManager.getRenderInterface())
	{
		int totalNumVisualShapes = m_data->m_pluginManager.getRenderInterface()->getNumVisualShapes(clientCmd.m_requestVisualShapeDataArguments.m_bodyUniqueId);
		//int totalBytesPerVisualShape = sizeof (b3VisualShapeData);
		//int visualShapeStorage = bufferSizeInBytes / totalBytesPerVisualShape - 1;
		b3VisualShapeData* visualShapeStoragePtr = (b3VisualShapeData*)bufferServerToClient;

		int remain = totalNumVisualShapes - clientCmd.m_requestVisualShapeDataArguments.m_startingVisualShapeIndex;
		int shapeIndex = clientCmd.m_requestVisualShapeDataArguments.m_startingVisualShapeIndex;

		int success = m_data->m_pluginManager.getRenderInterface()->getVisualShapesData(clientCmd.m_requestVisualShapeDataArguments.m_bodyUniqueId,
																						shapeIndex,
																						visualShapeStoragePtr);
		if (success)
		{
			//find the matching texture unique ids.
			if (visualShapeStoragePtr->m_tinyRendererTextureId >= 0)
			{
				b3AlignedObjectArray<int> usedHandles;
				m_data->m_textureHandles.getUsedHandles(usedHandles);

				for (int i = 0; i < usedHandles.size(); i++)
				{
					int texHandle = usedHandles[i];
					InternalTextureHandle* texH = m_data->m_textureHandles.getHandle(texHandle);
					if (texH && (texH->m_tinyRendererTextureId == visualShapeStoragePtr->m_tinyRendererTextureId))
					{
						visualShapeStoragePtr->m_openglTextureId = texH->m_openglTextureId;
						visualShapeStoragePtr->m_textureUniqueId = texHandle;
					}
				}
			}

			serverCmd.m_sendVisualShapeArgs.m_numRemainingVisualShapes = remain - 1;
			serverCmd.m_sendVisualShapeArgs.m_numVisualShapesCopied = 1;
			serverCmd.m_sendVisualShapeArgs.m_startingVisualShapeIndex = clientCmd.m_requestVisualShapeDataArguments.m_startingVisualShapeIndex;
			serverCmd.m_sendVisualShapeArgs.m_bodyUniqueId = clientCmd.m_requestVisualShapeDataArguments.m_bodyUniqueId;
			serverCmd.m_numDataStreamBytes = sizeof(b3VisualShapeData) * serverCmd.m_sendVisualShapeArgs.m_numVisualShapesCopied;
			serverCmd.m_type = CMD_VISUAL_SHAPE_INFO_COMPLETED;
		}
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processUpdateVisualShapeCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_UPDATE_VISUAL_SHAPE");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_VISUAL_SHAPE_UPDATE_FAILED;
	InternalTextureHandle* texHandle = 0;

	if (clientCmd.m_updateFlags & CMD_UPDATE_VISUAL_SHAPE_TEXTURE)
	{
		texHandle = m_data->m_textureHandles.getHandle(clientCmd.m_updateVisualShapeDataArguments.m_textureUniqueId);

		if (clientCmd.m_updateVisualShapeDataArguments.m_textureUniqueId >= 0)
		{
			if (texHandle)
			{
				if (m_data->m_pluginManager.getRenderInterface())
				{
					m_data->m_pluginManager.getRenderInterface()->changeShapeTexture(clientCmd.m_updateVisualShapeDataArguments.m_bodyUniqueId,
																					 clientCmd.m_updateVisualShapeDataArguments.m_jointIndex,
																					 clientCmd.m_updateVisualShapeDataArguments.m_shapeIndex,
																					 texHandle->m_tinyRendererTextureId);
				}
			}
		}
	}

	{
		int bodyUniqueId = clientCmd.m_updateVisualShapeDataArguments.m_bodyUniqueId;
		int linkIndex = clientCmd.m_updateVisualShapeDataArguments.m_jointIndex;

		InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(bodyUniqueId);
		if (bodyHandle)
		{
			if (bodyHandle->m_multiBody)
			{
				if (linkIndex == -1)
				{
					if (bodyHandle->m_multiBody->getBaseCollider())
					{
						int graphicsIndex = bodyHandle->m_multiBody->getBaseCollider()->getUserIndex();
						if (clientCmd.m_updateFlags & CMD_UPDATE_VISUAL_SHAPE_TEXTURE)
						{
							if (texHandle)
							{
								int shapeIndex = m_data->m_guiHelper->getShapeIndexFromInstance(graphicsIndex);
								m_data->m_guiHelper->replaceTexture(shapeIndex, texHandle->m_openglTextureId);
							}
						}
						if (clientCmd.m_updateFlags & CMD_UPDATE_VISUAL_SHAPE_RGBA_COLOR)
						{
							if (m_data->m_pluginManager.getRenderInterface())
							{
								m_data->m_pluginManager.getRenderInterface()->changeRGBAColor(bodyUniqueId, linkIndex,
																							  clientCmd.m_updateVisualShapeDataArguments.m_shapeIndex,
																							  clientCmd.m_updateVisualShapeDataArguments.m_rgbaColor);
							}
							m_data->m_guiHelper->changeRGBAColor(graphicsIndex, clientCmd.m_updateVisualShapeDataArguments.m_rgbaColor);
						}
						if (clientCmd.m_updateFlags & CMD_UPDATE_VISUAL_SHAPE_SPECULAR_COLOR)
						{
							m_data->m_guiHelper->changeSpecularColor(graphicsIndex, clientCmd.m_updateVisualShapeDataArguments.m_specularColor);
						}
					}
				}
				else
				{
					if (linkIndex < bodyHandle->m_multiBody->getNumLinks())
					{
						if (bodyHandle->m_multiBody->getLink(linkIndex).m_collider)
						{
							int graphicsIndex = bodyHandle->m_multiBody->getLink(linkIndex).m_collider->getUserIndex();
							if (clientCmd.m_updateFlags & CMD_UPDATE_VISUAL_SHAPE_TEXTURE)
							{
								if (texHandle)
								{
									int shapeIndex = m_data->m_guiHelper->getShapeIndexFromInstance(graphicsIndex);
									m_data->m_guiHelper->replaceTexture(shapeIndex, texHandle->m_openglTextureId);
								}
							}
							if (clientCmd.m_updateFlags & CMD_UPDATE_VISUAL_SHAPE_RGBA_COLOR)
							{
								if (m_data->m_pluginManager.getRenderInterface())
								{
									m_data->m_pluginManager.getRenderInterface()->changeRGBAColor(bodyUniqueId, linkIndex,
																								  clientCmd.m_updateVisualShapeDataArguments.m_shapeIndex, clientCmd.m_updateVisualShapeDataArguments.m_rgbaColor);
								}
								m_data->m_guiHelper->changeRGBAColor(graphicsIndex, clientCmd.m_updateVisualShapeDataArguments.m_rgbaColor);
							}
							if (clientCmd.m_updateFlags & CMD_UPDATE_VISUAL_SHAPE_SPECULAR_COLOR)
							{
								m_data->m_guiHelper->changeSpecularColor(graphicsIndex, clientCmd.m_updateVisualShapeDataArguments.m_specularColor);
							}
						}
					}
				}
			}
			else
			{
				if (bodyHandle->m_rigidBody)
				{
					int graphicsIndex = bodyHandle->m_rigidBody->getUserIndex();
					if (clientCmd.m_updateFlags & CMD_UPDATE_VISUAL_SHAPE_TEXTURE)
					{
						if (texHandle)
						{
							int shapeIndex = m_data->m_guiHelper->getShapeIndexFromInstance(graphicsIndex);
							m_data->m_guiHelper->replaceTexture(shapeIndex, texHandle->m_openglTextureId);
						}
					}
					if (clientCmd.m_updateFlags & CMD_UPDATE_VISUAL_SHAPE_RGBA_COLOR)
					{
						if (m_data->m_pluginManager.getRenderInterface())
						{
							m_data->m_pluginManager.getRenderInterface()->changeRGBAColor(bodyUniqueId, linkIndex,
																						  clientCmd.m_updateVisualShapeDataArguments.m_shapeIndex, clientCmd.m_updateVisualShapeDataArguments.m_rgbaColor);
						}
						m_data->m_guiHelper->changeRGBAColor(graphicsIndex, clientCmd.m_updateVisualShapeDataArguments.m_rgbaColor);
					}
					if (clientCmd.m_updateFlags & CMD_UPDATE_VISUAL_SHAPE_SPECULAR_COLOR)
					{
						m_data->m_guiHelper->changeSpecularColor(graphicsIndex, clientCmd.m_updateVisualShapeDataArguments.m_specularColor);
					}
				}
			}
		}
	}

	serverCmd.m_type = CMD_VISUAL_SHAPE_UPDATE_COMPLETED;

	b3Notification notification;
	notification.m_notificationType = VISUAL_SHAPE_CHANGED;
	notification.m_visualShapeArgs.m_bodyUniqueId = clientCmd.m_updateVisualShapeDataArguments.m_bodyUniqueId;
	notification.m_visualShapeArgs.m_linkIndex = clientCmd.m_updateVisualShapeDataArguments.m_jointIndex;
	notification.m_visualShapeArgs.m_visualShapeIndex = clientCmd.m_updateVisualShapeDataArguments.m_shapeIndex;
	m_data->m_pluginManager.addNotification(notification);

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processChangeTextureCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_CHANGE_TEXTURE_COMMAND_FAILED;

	InternalTextureHandle* texH = m_data->m_textureHandles.getHandle(clientCmd.m_changeTextureArgs.m_textureUniqueId);
	if (texH)
	{
		int gltex = texH->m_openglTextureId;
		m_data->m_guiHelper->changeTexture(gltex,
										   (const unsigned char*)bufferServerToClient, clientCmd.m_changeTextureArgs.m_width, clientCmd.m_changeTextureArgs.m_height);

		serverCmd.m_type = CMD_CLIENT_COMMAND_COMPLETED;
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processLoadTextureCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_LOAD_TEXTURE");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_LOAD_TEXTURE_FAILED;

	char relativeFileName[1024];
	char pathPrefix[1024];

	if (b3ResourcePath::findResourcePath(clientCmd.m_loadTextureArguments.m_textureFileName, relativeFileName, 1024))
	{
		b3FileUtils::extractPath(relativeFileName, pathPrefix, 1024);

		int texHandle = m_data->m_textureHandles.allocHandle();
		InternalTextureHandle* texH = m_data->m_textureHandles.getHandle(texHandle);
		if (texH)
		{
			texH->m_tinyRendererTextureId = -1;
			texH->m_openglTextureId = -1;

			int uid = -1;
			if (m_data->m_pluginManager.getRenderInterface())
			{
				uid = m_data->m_pluginManager.getRenderInterface()->loadTextureFile(relativeFileName);
			}
			if (uid >= 0)
			{
				texH->m_tinyRendererTextureId = uid;
			}

			{
				int width, height, n;
				unsigned char* imageData = stbi_load(relativeFileName, &width, &height, &n, 3);

				if (imageData)
				{
					texH->m_openglTextureId = m_data->m_guiHelper->registerTexture(imageData, width, height);
					free(imageData);
				}
				else
				{
					b3Warning("Unsupported texture image format [%s]\n", relativeFileName);
				}
			}
			serverCmd.m_loadTextureResultArguments.m_textureUniqueId = texHandle;
			serverCmd.m_type = CMD_LOAD_TEXTURE_COMPLETED;
		}
	}
	else
	{
		serverCmd.m_type = CMD_LOAD_TEXTURE_FAILED;
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processSaveStateCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	BT_PROFILE("CMD_RESTORE_STATE");
	bool hasStatus = true;
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_SAVE_STATE_FAILED;

	btDefaultSerializer* ser = new btDefaultSerializer();
	int currentFlags = ser->getSerializationFlags();
	ser->setSerializationFlags(currentFlags | BT_SERIALIZE_CONTACT_MANIFOLDS);
	m_data->m_dynamicsWorld->serialize(ser);
	bParse::btBulletFile* bulletFile = new bParse::btBulletFile((char*)ser->getBufferPointer(), ser->getCurrentBufferSize());
	bulletFile->parse(false);
	if (bulletFile->ok())
	{
		serverCmd.m_type = CMD_SAVE_STATE_COMPLETED;
		serverCmd.m_saveStateResultArgs.m_stateId = m_data->m_savedStates.size();
		SaveStateData sd;
		sd.m_bulletFile = bulletFile;
		sd.m_serializer = ser;
		m_data->m_savedStates.push_back(sd);
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processRestoreStateCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	BT_PROFILE("CMD_RESTORE_STATE");
	bool hasStatus = true;
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_RESTORE_STATE_FAILED;

	btMultiBodyWorldImporter* importer = new btMultiBodyWorldImporter(m_data->m_dynamicsWorld);
	importer->setImporterFlags(eRESTORE_EXISTING_OBJECTS);

	bool ok = false;

	if (clientCmd.m_loadStateArguments.m_stateId >= 0)
	{
		if (clientCmd.m_loadStateArguments.m_stateId < m_data->m_savedStates.size())
		{
			bParse::btBulletFile* bulletFile = m_data->m_savedStates[clientCmd.m_loadStateArguments.m_stateId].m_bulletFile;
			ok = importer->convertAllObjects(bulletFile);
		}
	}
	else
	{
		const char* prefix[] = {"", "./", "./data/", "../data/", "../../data/", "../../../data/", "../../../../data/"};
		int numPrefixes = sizeof(prefix) / sizeof(const char*);
		char relativeFileName[1024];
		FILE* f = 0;
		bool found = false;

		for (int i = 0; !f && i < numPrefixes; i++)
		{
			sprintf(relativeFileName, "%s%s", prefix[i], clientCmd.m_fileArguments.m_fileName);
			f = fopen(relativeFileName, "rb");
			if (f)
			{
				found = true;
				break;
			}
		}
		if (f)
		{
			fclose(f);
		}

		if (found)
		{
			ok = importer->loadFile(relativeFileName);
		}
	}

	if (ok)
	{
		serverCmd.m_type = CMD_RESTORE_STATE_COMPLETED;
	}

	return hasStatus;
}

bool PhysicsServerCommandProcessor::processLoadBulletCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	BT_PROFILE("CMD_LOAD_BULLET");

	bool hasStatus = true;
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_BULLET_LOADING_FAILED;

	//btBulletWorldImporter* importer = new btBulletWorldImporter(m_data->m_dynamicsWorld);
	btMultiBodyWorldImporter* importer = new btMultiBodyWorldImporter(m_data->m_dynamicsWorld);

	const char* prefix[] = {"", "./", "./data/", "../data/", "../../data/", "../../../data/", "../../../../data/"};
	int numPrefixes = sizeof(prefix) / sizeof(const char*);
	char relativeFileName[1024];
	FILE* f = 0;
	bool found = false;

	for (int i = 0; !f && i < numPrefixes; i++)
	{
		sprintf(relativeFileName, "%s%s", prefix[i], clientCmd.m_fileArguments.m_fileName);
		f = fopen(relativeFileName, "rb");
		if (f)
		{
			found = true;
			break;
		}
	}
	if (f)
	{
		fclose(f);
	}

	if (found)
	{
		bool ok = importer->loadFile(relativeFileName);
		if (ok)
		{
			int numRb = importer->getNumRigidBodies();
			serverStatusOut.m_sdfLoadedArgs.m_numBodies = 0;
			serverStatusOut.m_sdfLoadedArgs.m_numUserConstraints = 0;

			for (int i = 0; i < numRb; i++)
			{
				btCollisionObject* colObj = importer->getRigidBodyByIndex(i);
				if (colObj)
				{
					btRigidBody* rb = btRigidBody::upcast(colObj);
					if (rb)
					{
						int bodyUniqueId = m_data->m_bodyHandles.allocHandle();
						InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(bodyUniqueId);
						colObj->setUserIndex2(bodyUniqueId);
						bodyHandle->m_rigidBody = rb;

						if (serverStatusOut.m_sdfLoadedArgs.m_numBodies < MAX_SDF_BODIES)
						{
							serverStatusOut.m_sdfLoadedArgs.m_numBodies++;
							serverStatusOut.m_sdfLoadedArgs.m_bodyUniqueIds[i] = bodyUniqueId;
						}

						b3Notification notification;
						notification.m_notificationType = BODY_ADDED;
						notification.m_bodyArgs.m_bodyUniqueId = bodyUniqueId;
						m_data->m_pluginManager.addNotification(notification);
					}
				}
			}

			serverCmd.m_type = CMD_BULLET_LOADING_COMPLETED;
			m_data->m_guiHelper->autogenerateGraphicsObjects(m_data->m_dynamicsWorld);
		}
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processLoadMJCFCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_LOAD_MJCF");
	SharedMemoryStatus& serverCmd = serverStatusOut;
	serverCmd.m_type = CMD_MJCF_LOADING_FAILED;
	const MjcfArgs& mjcfArgs = clientCmd.m_mjcfArguments;
	if (m_data->m_verboseOutput)
	{
		b3Printf("Processed CMD_LOAD_MJCF:%s", mjcfArgs.m_mjcfFileName);
	}
	bool useMultiBody = (clientCmd.m_updateFlags & URDF_ARGS_USE_MULTIBODY) ? (mjcfArgs.m_useMultiBody != 0) : true;
	int flags = CUF_USE_MJCF;
	if (clientCmd.m_updateFlags & URDF_ARGS_HAS_CUSTOM_URDF_FLAGS)
	{
		flags |= clientCmd.m_mjcfArguments.m_flags;
	}

	bool completedOk = loadMjcf(mjcfArgs.m_mjcfFileName, bufferServerToClient, bufferSizeInBytes, useMultiBody, flags);
	if (completedOk)
	{
		m_data->m_guiHelper->autogenerateGraphicsObjects(this->m_data->m_dynamicsWorld);

		serverStatusOut.m_sdfLoadedArgs.m_numBodies = m_data->m_sdfRecentLoadedBodies.size();
		serverStatusOut.m_sdfLoadedArgs.m_numUserConstraints = 0;
		int maxBodies = btMin(MAX_SDF_BODIES, serverStatusOut.m_sdfLoadedArgs.m_numBodies);
		for (int i = 0; i < maxBodies; i++)
		{
			serverStatusOut.m_sdfLoadedArgs.m_bodyUniqueIds[i] = m_data->m_sdfRecentLoadedBodies[i];
		}

		serverStatusOut.m_type = CMD_MJCF_LOADING_COMPLETED;
	}
	else
	{
		serverStatusOut.m_type = CMD_MJCF_LOADING_FAILED;
	}
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processSaveBulletCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	bool hasStatus = true;

	BT_PROFILE("CMD_SAVE_BULLET");
	SharedMemoryStatus& serverCmd = serverStatusOut;

	FILE* f = fopen(clientCmd.m_fileArguments.m_fileName, "wb");
	if (f)
	{
		btDefaultSerializer* ser = new btDefaultSerializer();
		int currentFlags = ser->getSerializationFlags();
		ser->setSerializationFlags(currentFlags | BT_SERIALIZE_CONTACT_MANIFOLDS);

		m_data->m_dynamicsWorld->serialize(ser);
		fwrite(ser->getBufferPointer(), ser->getCurrentBufferSize(), 1, f);
		fclose(f);
		serverCmd.m_type = CMD_BULLET_SAVING_COMPLETED;
		delete ser;
		return hasStatus;
	}
	serverCmd.m_type = CMD_BULLET_SAVING_FAILED;
	return hasStatus;
}

bool PhysicsServerCommandProcessor::processCommand(const struct SharedMemoryCommand& clientCmd, struct SharedMemoryStatus& serverStatusOut, char* bufferServerToClient, int bufferSizeInBytes)
{
	//	BT_PROFILE("processCommand");

	int sz = sizeof(SharedMemoryStatus);
	int sz2 = sizeof(SharedMemoryCommand);

	bool hasStatus = false;

	if (m_data->m_commandLogger)
	{
		m_data->m_commandLogger->logCommand(clientCmd);
	}
	serverStatusOut.m_type = CMD_INVALID_STATUS;
	serverStatusOut.m_numDataStreamBytes = 0;
	serverStatusOut.m_dataStream = 0;

	//consume the command
	switch (clientCmd.m_type)
	{
		case CMD_STATE_LOGGING:
		{
			hasStatus = processStateLoggingCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_SET_VR_CAMERA_STATE:
		{
			hasStatus = processSetVRCameraStateCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REQUEST_VR_EVENTS_DATA:
		{
			hasStatus = processRequestVREventsCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		};
		case CMD_REQUEST_MOUSE_EVENTS_DATA:
		{
			hasStatus = processRequestMouseEventsCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		};
		case CMD_REQUEST_KEYBOARD_EVENTS_DATA:
		{
			hasStatus = processRequestKeyboardEventsCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		};

		case CMD_REQUEST_RAY_CAST_INTERSECTIONS:
		{
			hasStatus = processRequestRaycastIntersectionsCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		};
		case CMD_REQUEST_DEBUG_LINES:
		{
			hasStatus = processRequestDebugLinesCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}

		case CMD_REQUEST_CAMERA_IMAGE_DATA:
		{
			hasStatus = processRequestCameraImageCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_SYNC_BODY_INFO:
		{
			hasStatus = processSyncBodyInfoCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REQUEST_BODY_INFO:
		{
			hasStatus = processRequestBodyInfoCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_SAVE_WORLD:
		{
			hasStatus = processSaveWorldCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_LOAD_SDF:
		{
			hasStatus = processLoadSDFCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CREATE_COLLISION_SHAPE:
		{
			hasStatus = processCreateCollisionShapeCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CREATE_VISUAL_SHAPE:
		{
			hasStatus = processCreateVisualShapeCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CREATE_MULTI_BODY:
		{
			hasStatus = processCreateMultiBodyCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_SET_ADDITIONAL_SEARCH_PATH:
		{
			hasStatus = processSetAdditionalSearchPathCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_LOAD_URDF:
		{
			hasStatus = processLoadURDFCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_LOAD_SOFT_BODY:
		{
			hasStatus = processLoadSoftBodyCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CREATE_SENSOR:
		{
			hasStatus = processCreateSensorCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_PROFILE_TIMING:
		{
			hasStatus = processProfileTimingCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}

		case CMD_SEND_DESIRED_STATE:
		{
			hasStatus = processSendDesiredStateCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REQUEST_COLLISION_INFO:
		{
			hasStatus = processRequestCollisionInfoCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}

		case CMD_REQUEST_ACTUAL_STATE:
		{
			hasStatus = processRequestActualStateCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_STEP_FORWARD_SIMULATION:
		{
			hasStatus = processForwardDynamicsCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}

		case CMD_REQUEST_INTERNAL_DATA:
		{
			hasStatus = processRequestInternalDataCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		};
		case CMD_CHANGE_DYNAMICS_INFO:
		{
			hasStatus = processChangeDynamicsInfoCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		};
		case CMD_GET_DYNAMICS_INFO:
		{
			hasStatus = processGetDynamicsInfoCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}

		case CMD_REQUEST_PHYSICS_SIMULATION_PARAMETERS:
		{
			hasStatus = processRequestPhysicsSimulationParametersCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}

		case CMD_SEND_PHYSICS_SIMULATION_PARAMETERS:
		{
			hasStatus = processSendPhysicsParametersCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		};
		case CMD_INIT_POSE:
		{
			hasStatus = processInitPoseCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_RESET_SIMULATION:
		{
			hasStatus = processResetSimulationCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CREATE_RIGID_BODY:
		{
			hasStatus = processCreateRigidBodyCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CREATE_BOX_COLLISION_SHAPE:
		{
			//for backward compatibility, CMD_CREATE_BOX_COLLISION_SHAPE is the same as CMD_CREATE_RIGID_BODY
			hasStatus = processCreateRigidBodyCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_PICK_BODY:
		{
			hasStatus = processPickBodyCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_MOVE_PICKED_BODY:
		{
			hasStatus = processMovePickedBodyCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REMOVE_PICKING_CONSTRAINT_BODY:
		{
			hasStatus = processRemovePickingConstraintCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REQUEST_AABB_OVERLAP:
		{
			hasStatus = processRequestAabbOverlapCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REQUEST_OPENGL_VISUALIZER_CAMERA:
		{
			hasStatus = processRequestOpenGLVisualizeCameraCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CONFIGURE_OPENGL_VISUALIZER:
		{
			hasStatus = processConfigureOpenGLVisualizerCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REQUEST_CONTACT_POINT_INFORMATION:
		{
			hasStatus = processRequestContactpointInformationCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CALCULATE_INVERSE_DYNAMICS:
		{
			hasStatus = processInverseDynamicsCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CALCULATE_JACOBIAN:
		{
			hasStatus = processCalculateJacobianCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CALCULATE_MASS_MATRIX:
		{
			hasStatus = processCalculateMassMatrixCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_APPLY_EXTERNAL_FORCE:
		{
			hasStatus = processApplyExternalForceCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REMOVE_BODY:
		{
			hasStatus = processRemoveBodyCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_USER_CONSTRAINT:
		{
			hasStatus = processCreateUserConstraintCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CALCULATE_INVERSE_KINEMATICS:
		{
			hasStatus = processCalculateInverseKinematicsCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REQUEST_VISUAL_SHAPE_INFO:
		{
			hasStatus = processRequestVisualShapeInfoCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REQUEST_COLLISION_SHAPE_INFO:
		{
			hasStatus = processRequestCollisionShapeInfoCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_UPDATE_VISUAL_SHAPE:
		{
			hasStatus = processUpdateVisualShapeCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CHANGE_TEXTURE:
		{
			hasStatus = processChangeTextureCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_LOAD_TEXTURE:
		{
			hasStatus = processLoadTextureCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_RESTORE_STATE:
		{
			hasStatus = processRestoreStateCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}

		case CMD_SAVE_STATE:
		{
			hasStatus = processSaveStateCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}

		case CMD_LOAD_BULLET:
		{
			hasStatus = processLoadBulletCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_SAVE_BULLET:
		{
			hasStatus = processSaveBulletCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_LOAD_MJCF:
		{
			hasStatus = processLoadMJCFCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_USER_DEBUG_DRAW:
		{
			hasStatus = processUserDebugDrawCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_CUSTOM_COMMAND:
		{
			hasStatus = processCustomCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_SYNC_USER_DATA:
		{
			hasStatus = processSyncUserDataCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REQUEST_USER_DATA:
		{
			hasStatus = processRequestUserDataCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_ADD_USER_DATA:
		{
			hasStatus = processAddUserDataCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_REMOVE_USER_DATA:
		{
			hasStatus = processRemoveUserDataCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		case CMD_COLLISION_FILTER:
		{
			hasStatus = processCollisionFilterCommand(clientCmd, serverStatusOut, bufferServerToClient, bufferSizeInBytes);
			break;
		}
		default:
		{
			BT_PROFILE("CMD_UNKNOWN");
			b3Error("Unknown command encountered");
			SharedMemoryStatus& serverCmd = serverStatusOut;
			serverCmd.m_type = CMD_UNKNOWN_COMMAND_FLUSHED;
			hasStatus = true;
		}
	};

	return hasStatus;
}

void PhysicsServerCommandProcessor::syncPhysicsToGraphics()
{
	m_data->m_guiHelper->syncPhysicsToGraphics(m_data->m_dynamicsWorld);
}

void PhysicsServerCommandProcessor::renderScene(int renderFlags)
{
	if (m_data->m_guiHelper)
	{
		if (0 == (renderFlags & COV_DISABLE_SYNC_RENDERING))
		{
			m_data->m_guiHelper->syncPhysicsToGraphics(m_data->m_dynamicsWorld);
		}

		m_data->m_guiHelper->render(m_data->m_dynamicsWorld);
	}
}

void PhysicsServerCommandProcessor::physicsDebugDraw(int debugDrawFlags)
{
	if (m_data->m_dynamicsWorld)
	{
		if (m_data->m_dynamicsWorld->getDebugDrawer())
		{
			m_data->m_dynamicsWorld->getDebugDrawer()->setDebugMode(debugDrawFlags);
			m_data->m_dynamicsWorld->debugDrawWorld();

#ifndef SKIP_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD
			for (int i = 0; i < m_data->m_dynamicsWorld->getSoftBodyArray().size(); i++)
			{
				btSoftBody* psb = (btSoftBody*)m_data->m_dynamicsWorld->getSoftBodyArray()[i];
				if (m_data->m_dynamicsWorld->getDebugDrawer() && !(m_data->m_dynamicsWorld->getDebugDrawer()->getDebugMode() & (btIDebugDraw::DBG_DrawWireframe)))
				{
					//btSoftBodyHelpers::DrawFrame(psb,m_data->m_dynamicsWorld->getDebugDrawer());
					btSoftBodyHelpers::Draw(psb, m_data->m_dynamicsWorld->getDebugDrawer(), m_data->m_dynamicsWorld->getDrawFlags());
				}
			}
#endif
		}
	}
}

struct MyResultCallback : public btCollisionWorld::ClosestRayResultCallback
{
	MyResultCallback(const btVector3& rayFromWorld, const btVector3& rayToWorld)
		: btCollisionWorld::ClosestRayResultCallback(rayFromWorld, rayToWorld)
	{
	}

	virtual bool needsCollision(btBroadphaseProxy* proxy0) const
	{
		return true;
	}
};

bool PhysicsServerCommandProcessor::pickBody(const btVector3& rayFromWorld, const btVector3& rayToWorld)
{
	if (m_data->m_dynamicsWorld == 0)
		return false;

	//btCollisionWorld::ClosestRayResultCallback rayCallback(rayFromWorld, rayToWorld);
	MyResultCallback rayCallback(rayFromWorld, rayToWorld);
	m_data->m_dynamicsWorld->rayTest(rayFromWorld, rayToWorld, rayCallback);
	if (rayCallback.hasHit())
	{
		btVector3 pickPos = rayCallback.m_hitPointWorld;

		btRigidBody* body = (btRigidBody*)btRigidBody::upcast(rayCallback.m_collisionObject);
		if (body)
		{
			//other exclusions?
			if (!(body->isStaticObject() || body->isKinematicObject()))
			{
				m_data->m_pickedBody = body;
				m_data->m_savedActivationState = body->getActivationState();
				m_data->m_pickedBody->setActivationState(DISABLE_DEACTIVATION);
				//printf("pickPos=%f,%f,%f\n",pickPos.getX(),pickPos.getY(),pickPos.getZ());
				btVector3 localPivot = body->getCenterOfMassTransform().inverse() * pickPos;
				btPoint2PointConstraint* p2p = new btPoint2PointConstraint(*body, localPivot);
				m_data->m_dynamicsWorld->addConstraint(p2p, true);
				m_data->m_pickedConstraint = p2p;
				btScalar mousePickClamping = 30.f;
				p2p->m_setting.m_impulseClamp = mousePickClamping;
				//very weak constraint for picking
				p2p->m_setting.m_tau = 0.001f;
			}
		}
		else
		{
			btMultiBodyLinkCollider* multiCol = (btMultiBodyLinkCollider*)btMultiBodyLinkCollider::upcast(rayCallback.m_collisionObject);
			if (multiCol && multiCol->m_multiBody)
			{
				m_data->m_prevCanSleep = multiCol->m_multiBody->getCanSleep();
				multiCol->m_multiBody->setCanSleep(false);

				btVector3 pivotInA = multiCol->m_multiBody->worldPosToLocal(multiCol->m_link, pickPos);

				btMultiBodyPoint2Point* p2p = new btMultiBodyPoint2Point(multiCol->m_multiBody, multiCol->m_link, 0, pivotInA, pickPos);
				//if you add too much energy to the system, causing high angular velocities, simulation 'explodes'
				//see also http://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?f=4&t=949
				//so we try to avoid it by clamping the maximum impulse (force) that the mouse pick can apply
				//it is not satisfying, hopefully we find a better solution (higher order integrator, using joint friction using a zero-velocity target motor with limited force etc?)
				btScalar scaling = 10;
				p2p->setMaxAppliedImpulse(2 * scaling);

				btMultiBodyDynamicsWorld* world = (btMultiBodyDynamicsWorld*)m_data->m_dynamicsWorld;
				world->addMultiBodyConstraint(p2p);
				m_data->m_pickingMultiBodyPoint2Point = p2p;
			}
		}

		//					pickObject(pickPos, rayCallback.m_collisionObject);
		m_data->m_oldPickingPos = rayToWorld;
		m_data->m_hitPos = pickPos;
		m_data->m_oldPickingDist = (pickPos - rayFromWorld).length();
		//					printf("hit !\n");
		//add p2p
	}
	return false;
}

bool PhysicsServerCommandProcessor::movePickedBody(const btVector3& rayFromWorld, const btVector3& rayToWorld)
{
	if (m_data->m_pickedBody && m_data->m_pickedConstraint)
	{
		btPoint2PointConstraint* pickCon = static_cast<btPoint2PointConstraint*>(m_data->m_pickedConstraint);
		if (pickCon)
		{
			//keep it at the same picking distance

			btVector3 dir = rayToWorld - rayFromWorld;
			dir.normalize();
			dir *= m_data->m_oldPickingDist;

			btVector3 newPivotB = rayFromWorld + dir;
			pickCon->setPivotB(newPivotB);
		}
	}

	if (m_data->m_pickingMultiBodyPoint2Point)
	{
		//keep it at the same picking distance

		btVector3 dir = rayToWorld - rayFromWorld;
		dir.normalize();
		dir *= m_data->m_oldPickingDist;

		btVector3 newPivotB = rayFromWorld + dir;

		m_data->m_pickingMultiBodyPoint2Point->setPivotInB(newPivotB);
	}

	return false;
}

void PhysicsServerCommandProcessor::removePickingConstraint()
{
	if (m_data->m_pickedConstraint)
	{
		m_data->m_dynamicsWorld->removeConstraint(m_data->m_pickedConstraint);
		delete m_data->m_pickedConstraint;
		m_data->m_pickedConstraint = 0;
		m_data->m_pickedBody->forceActivationState(m_data->m_savedActivationState);
		m_data->m_pickedBody = 0;
	}
	if (m_data->m_pickingMultiBodyPoint2Point)
	{
		m_data->m_pickingMultiBodyPoint2Point->getMultiBodyA()->setCanSleep(m_data->m_prevCanSleep);
		btMultiBodyDynamicsWorld* world = (btMultiBodyDynamicsWorld*)m_data->m_dynamicsWorld;
		world->removeMultiBodyConstraint(m_data->m_pickingMultiBodyPoint2Point);
		delete m_data->m_pickingMultiBodyPoint2Point;
		m_data->m_pickingMultiBodyPoint2Point = 0;
	}
}

void PhysicsServerCommandProcessor::enableCommandLogging(bool enable, const char* fileName)
{
	if (enable)
	{
		if (0 == m_data->m_commandLogger)
		{
			m_data->m_commandLogger = new CommandLogger(fileName);
		}
	}
	else
	{
		if (0 != m_data->m_commandLogger)
		{
			delete m_data->m_commandLogger;
			m_data->m_commandLogger = 0;
		}
	}
}

void PhysicsServerCommandProcessor::replayFromLogFile(const char* fileName)
{
	CommandLogPlayback* pb = new CommandLogPlayback(fileName);
	m_data->m_logPlayback = pb;
}

int gDroppedSimulationSteps = 0;
int gNumSteps = 0;
double gDtInSec = 0.f;
double gSubStep = 0.f;

void PhysicsServerCommandProcessor::enableRealTimeSimulation(bool enableRealTimeSim)
{
	m_data->m_useRealTimeSimulation = enableRealTimeSim;
}

bool PhysicsServerCommandProcessor::isRealTimeSimulationEnabled() const
{
	return m_data->m_useRealTimeSimulation;
}

void PhysicsServerCommandProcessor::stepSimulationRealTime(double dtInSec, const struct b3VRControllerEvent* vrControllerEvents, int numVRControllerEvents, const struct b3KeyboardEvent* keyEvents, int numKeyEvents, const struct b3MouseEvent* mouseEvents, int numMouseEvents)
{
	m_data->m_vrControllerEvents.addNewVREvents(vrControllerEvents, numVRControllerEvents);
	m_data->m_pluginManager.addEvents(vrControllerEvents, numVRControllerEvents, keyEvents, numKeyEvents, mouseEvents, numMouseEvents);

	for (int i = 0; i < m_data->m_stateLoggers.size(); i++)
	{
		if (m_data->m_stateLoggers[i]->m_loggingType == STATE_LOGGING_VR_CONTROLLERS)
		{
			VRControllerStateLogger* vrLogger = (VRControllerStateLogger*)m_data->m_stateLoggers[i];
			vrLogger->m_vrEvents.addNewVREvents(vrControllerEvents, numVRControllerEvents);
		}
	}

	for (int ii = 0; ii < numMouseEvents; ii++)
	{
		const b3MouseEvent& event = mouseEvents[ii];
		bool found = false;
		//search a matching one first, otherwise add new event
		for (int e = 0; e < m_data->m_mouseEvents.size(); e++)
		{
			if (event.m_eventType == m_data->m_mouseEvents[e].m_eventType)
			{
				if (event.m_eventType == MOUSE_MOVE_EVENT)
				{
					m_data->m_mouseEvents[e].m_mousePosX = event.m_mousePosX;
					m_data->m_mouseEvents[e].m_mousePosY = event.m_mousePosY;
					found = true;
				}
				else if ((event.m_eventType == MOUSE_BUTTON_EVENT) && event.m_buttonIndex == m_data->m_mouseEvents[e].m_buttonIndex)
				{
					m_data->m_mouseEvents[e].m_buttonState |= event.m_buttonState;
					if (event.m_buttonState & eButtonIsDown)
					{
						m_data->m_mouseEvents[e].m_buttonState |= eButtonIsDown;
					}
					else
					{
						m_data->m_mouseEvents[e].m_buttonState &= ~eButtonIsDown;
					}
					found = true;
				}
			}
		}
		if (!found)
		{
			m_data->m_mouseEvents.push_back(event);
		}
	}

	for (int i = 0; i < numKeyEvents; i++)
	{
		const b3KeyboardEvent& event = keyEvents[i];
		bool found = false;
		//search a matching one first, otherwise add new event
		for (int e = 0; e < m_data->m_keyboardEvents.size(); e++)
		{
			if (event.m_keyCode == m_data->m_keyboardEvents[e].m_keyCode)
			{
				m_data->m_keyboardEvents[e].m_keyState |= event.m_keyState;
				if (event.m_keyState & eButtonIsDown)
				{
					m_data->m_keyboardEvents[e].m_keyState |= eButtonIsDown;
				}
				else
				{
					m_data->m_keyboardEvents[e].m_keyState &= ~eButtonIsDown;
				}
				found = true;
			}
		}
		if (!found)
		{
			m_data->m_keyboardEvents.push_back(event);
		}
	}
	if (gResetSimulation)
	{
		resetSimulation();
		gResetSimulation = false;
	}

	if (gVRTrackingObjectUniqueId >= 0)
	{
		InternalBodyHandle* bodyHandle = m_data->m_bodyHandles.getHandle(gVRTrackingObjectUniqueId);
		if (bodyHandle && bodyHandle->m_multiBody)
		{
			//			gVRTrackingObjectTr  = bodyHandle->m_multiBody->getBaseWorldTransform();

			if (gVRTrackingObjectUniqueId >= 0)
			{
				gVRTrackingObjectTr.setOrigin(bodyHandle->m_multiBody->getBaseWorldTransform().getOrigin());
				gVRTeleportPos1 = gVRTrackingObjectTr.getOrigin();
			}
			if (gVRTrackingObjectFlag & VR_CAMERA_TRACK_OBJECT_ORIENTATION)
			{
				gVRTrackingObjectTr.setBasis(bodyHandle->m_multiBody->getBaseWorldTransform().getBasis());
				gVRTeleportOrn = gVRTrackingObjectTr.getRotation();
			}
		}
	}

	if ((m_data->m_useRealTimeSimulation) && m_data->m_guiHelper)
	{
		int maxSteps = m_data->m_numSimulationSubSteps + 3;
		if (m_data->m_numSimulationSubSteps)
		{
			gSubStep = m_data->m_physicsDeltaTime / m_data->m_numSimulationSubSteps;
		}
		else
		{
			gSubStep = m_data->m_physicsDeltaTime;
		}

		int numSteps = m_data->m_dynamicsWorld->stepSimulation(dtInSec * simTimeScalingFactor, maxSteps, gSubStep);
		gDroppedSimulationSteps += numSteps > maxSteps ? numSteps - maxSteps : 0;

		if (numSteps)
		{
			gNumSteps = numSteps;
			gDtInSec = dtInSec;

			addTransformChangedNotifications();
		}
	}
}

b3Notification createTransformChangedNotification(int bodyUniqueId, int linkIndex, const btCollisionObject* colObj)
{
	b3Notification notification;
	notification.m_notificationType = TRANSFORM_CHANGED;
	notification.m_transformChangeArgs.m_bodyUniqueId = bodyUniqueId;
	notification.m_transformChangeArgs.m_linkIndex = linkIndex;

	const btTransform& tr = colObj->getWorldTransform();
	notification.m_transformChangeArgs.m_worldPosition[0] = tr.getOrigin()[0];
	notification.m_transformChangeArgs.m_worldPosition[1] = tr.getOrigin()[1];
	notification.m_transformChangeArgs.m_worldPosition[2] = tr.getOrigin()[2];

	notification.m_transformChangeArgs.m_worldRotation[0] = tr.getRotation()[0];
	notification.m_transformChangeArgs.m_worldRotation[1] = tr.getRotation()[1];
	notification.m_transformChangeArgs.m_worldRotation[2] = tr.getRotation()[2];
	notification.m_transformChangeArgs.m_worldRotation[3] = tr.getRotation()[3];

	const btVector3& scaling = colObj->getCollisionShape()->getLocalScaling();
	notification.m_transformChangeArgs.m_localScaling[0] = scaling[0];
	notification.m_transformChangeArgs.m_localScaling[1] = scaling[1];
	notification.m_transformChangeArgs.m_localScaling[2] = scaling[2];
	return notification;
}

void PhysicsServerCommandProcessor::addTransformChangedNotifications()
{
	b3Notification notification;
	notification.m_notificationType = SIMULATION_STEPPED;
	m_data->m_pluginManager.addNotification(notification);

	b3AlignedObjectArray<int> usedHandles;
	m_data->m_bodyHandles.getUsedHandles(usedHandles);
	for (int i = 0; i < usedHandles.size(); i++)
	{
		int bodyUniqueId = usedHandles[i];
		InternalBodyData* bodyData = m_data->m_bodyHandles.getHandle(bodyUniqueId);
		if (!bodyData)
		{
			continue;
		}
		if (bodyData->m_multiBody && bodyData->m_multiBody->isAwake())
		{
			btMultiBody* mb = bodyData->m_multiBody;
			m_data->m_pluginManager.addNotification(createTransformChangedNotification(bodyUniqueId, -1, mb->getBaseCollider()));

			for (int linkIndex = 0; linkIndex < mb->getNumLinks(); linkIndex++)
			{
				m_data->m_pluginManager.addNotification(createTransformChangedNotification(bodyUniqueId, linkIndex, mb->getLinkCollider(linkIndex)));
			}
		}
		else if (bodyData->m_rigidBody && bodyData->m_rigidBody->isActive())
		{
			m_data->m_pluginManager.addNotification(createTransformChangedNotification(bodyUniqueId, -1, bodyData->m_rigidBody));
		}
	}
}

void PhysicsServerCommandProcessor::resetSimulation()
{
	//clean up all data

	m_data->m_cachedVUrdfisualShapes.clear();

#ifndef SKIP_SOFT_BODY_MULTI_BODY_DYNAMICS_WORLD
	if (m_data && m_data->m_dynamicsWorld)
	{
		m_data->m_dynamicsWorld->getWorldInfo().m_sparsesdf.Reset();
	}
#endif
	if (m_data && m_data->m_guiHelper)
	{
		m_data->m_guiHelper->removeAllGraphicsInstances();
		m_data->m_guiHelper->removeAllUserDebugItems();
	}
	if (m_data)
	{
		if (m_data->m_pluginManager.getRenderInterface())
		{
			m_data->m_pluginManager.getRenderInterface()->resetAll();
		}

		if (m_data->m_pluginManager.getCollisionInterface())
		{
			m_data->m_pluginManager.getCollisionInterface()->resetAll();
		}

		for (int i = 0; i < m_data->m_savedStates.size(); i++)
		{
			delete m_data->m_savedStates[i].m_bulletFile;
			delete m_data->m_savedStates[i].m_serializer;
		}
		m_data->m_savedStates.clear();
	}

	removePickingConstraint();

	deleteDynamicsWorld();
	createEmptyDynamicsWorld();

	m_data->m_bodyHandles.exitHandles();
	m_data->m_bodyHandles.initHandles();

	m_data->m_userCollisionShapeHandles.exitHandles();
	m_data->m_userCollisionShapeHandles.initHandles();

	m_data->m_userDataHandles.exitHandles();
	m_data->m_userDataHandles.initHandles();
	m_data->m_userDataHandleLookup.clear();

	b3Notification notification;
	notification.m_notificationType = SIMULATION_RESET;
	m_data->m_pluginManager.addNotification(notification);
}

void PhysicsServerCommandProcessor::setTimeOut(double /*timeOutInSeconds*/)
{
}

const btVector3& PhysicsServerCommandProcessor::getVRTeleportPosition() const
{
	return gVRTeleportPos1;
}
void PhysicsServerCommandProcessor::setVRTeleportPosition(const btVector3& vrTeleportPos)
{
	gVRTeleportPos1 = vrTeleportPos;
}
const btQuaternion& PhysicsServerCommandProcessor::getVRTeleportOrientation() const
{
	return gVRTeleportOrn;
}
void PhysicsServerCommandProcessor::setVRTeleportOrientation(const btQuaternion& vrTeleportOrn)
{
	gVRTeleportOrn = vrTeleportOrn;
}
