// pmSoftTransform Plugin
// Executed Fri Sep 9 18:34:57 UTC+0200 2011 by leon

#pragma warning(disable: 4244) // possible loss of data
#pragma warning(disable: 4800) // forcing value to bool 'true' or 'false' (performance warning)
#define GLEW_STATIC
#include "includes.h"

using namespace XSI::MATH; 
using namespace XSI; 

#define MOUSE_MOVE_TIME_DELAY 2

const double cfcrvStepsize = 0.01;

SICALLBACK XSILoadPlugin( PluginRegistrar& in_reg )
{
	in_reg.PutAuthor(L"piotrek marczak");
	in_reg.PutName(L"pmSoftTransform Plugin");
	in_reg.PutVersion(1,1);
	in_reg.RegisterTool(L"pmSoftTransform");
	in_reg.RegisterMenu(siMenuTbModelModifyComponentID,L"SoftTransform_Menu",false,false);

	//RegistrationInsertionPoint - do not remove this line
	
	return CStatus::OK;
}

SICALLBACK XSIUnloadPlugin( const PluginRegistrar& in_reg )
{
	CString strPluginName;
	strPluginName = in_reg.GetName();
	Application().LogMessage(strPluginName + L" has been unloaded.",siVerboseMsg);
	return CStatus::OK;
}

////////////////////////////////////////////////////////////////////////////////
//
// pmSoftTransform
//
////////////////////////////////////////////////////////////////////////////////

class pmSoftTransform {
private: // Data
	// Add tool data members here...
	LONG m_mouseDownX,m_mouseDownY;
	LONG m_mouseDragX,m_mouseDragY;
	LONG m_mouseX,m_mouseY;

	CVector3		m_mouseworldposition;
	CPlane			m_gridPlane;		// Visible grid plane
	CPlane			m_manipPlane;		// Manipulation plane
	CLongArray		m_circlepts;
	CLongArray		m_mousepoints;
	CString			m_pickfilter;

	CRefArray		m_pickedObjects;	//objects picked
	CRefArray		m_collideObjects;
	CDoubleArray	m_objBrushDist;		//distance brush center-brush radius-object position * fcurve value
	CVector3Array	m_collTriPos;			//collision meshes triangle vertex data cache
	CVector3Array	m_collTriNormals;		//collision meshes triangel normal data cache

	CVector3		m_lastOffset;

	CVector3		m_tempV,m_tempV2;

	POINT			lp_mouse; // absolute mouse pos.
	
	enum			enToolMode {kMenu,kInteracting,kButtonDrag,kBrushRadius,kIdle} m_maintoolMode;
	enum			toolMode {cTrans,cRot,cScale,cNone};
	enum			collisionMode {cBoundingBox, cBoundingSphere, cBoundingCapsule, cMesh, cConvexHull, cRaycastPivot};
	enum			frozenMode {cMove,cCollide,cIgnore};

	toolMode		m_toolMode, m_temptoolMode;
	frozenMode		m_frozenMode, m_selectedMode;
	collisionMode	m_collMS_StaticMode,m_collMS_MovingMode,m_collMM_MovingMode;
	double			m_bboxScale;//shrink bbox for collisions

	struct			spoint { double x,y; };

	bool			m_rotAboutPoint, m_rotLocal, m_sclAboutPoint;
	int				m_testCollision, m_continousCollisions,m_interpartikleCollisions;
	double			m_transSpeed, m_rotSpeed, m_sclSpeed;
	int				m_collIter;//collision iterations test 

	BrushTool		m_brush;			//brush
	clock_t			m_toolTime;

	ButtonBar		m_Buttons;

	CString			m_pluginPath;

	bool			m_mouseEnteredButtonView;	//mouse under buttons (for cursor changing);
public: // Methods
	
	pmSoftTransform()
	{}

	~pmSoftTransform()
	{}

	template<typename T>
	void logvalue(CString s,T val)
	{
		Application().LogMessage(s+":"+CValue(val) );
	}
	void logvalue(CString s,CVector3 val)
	{
		Application().LogMessage(s+L"x:"+CString(CValue(val.GetX() )) +L"		y:"+CString(CValue(val.GetY() ))
			+L"		z:"+CString(CValue(val.GetZ() )) 
			);

	}

	void switchCursor(ToolContext &in_ctxt)
	{
			switch (m_toolMode){
				case cTrans:
					in_ctxt.SetCursor(siTranslateCursor);
					break;
				case cRot:
					in_ctxt.SetCursor(siRotateCursor);
					break;
				case cScale:
					in_ctxt.SetCursor(siScaleCursor);
					break;
			}
			
	}
	bool GetAutoKeyValue()
	{
		Application app;
		Preferences pref = app.GetPreferences();

		CValue autokey;
		CStatus prefStatus = pref.GetPreferenceValue(L"animation.autokey", autokey);
		if (prefStatus == CStatus::OK){
			return autokey;
		}
		else 
		{
			app.LogMessage("No preferences found.");	
			return false;
		}
	}

	void GetToolPreferences()
	{
		Application app;
		CValue outArg;
		CValueArray inArgs; 
		
		Preferences pref = app.GetPreferences();

		CValue ipref;
		CString prefname = L"SoftTransformPreferences";
		//if(pref.GetPreferenceValue(prefname+L".trspeed", ipref) != CStatus::OK)
		//	app.ExecuteCommand(L"InstallSoftTransformPreferences",inArgs,outArg);
		
		if (pref.GetPreferenceValue(prefname+L".trspeed", ipref) == CStatus::OK) {
			m_transSpeed = ipref;
			m_transSpeed*=0.01;
		}

		if (pref.GetPreferenceValue(prefname+L".rspeed", ipref) == CStatus::OK)	{
				m_rotSpeed = ipref;
				m_rotSpeed*=0.01;
		}

		if (pref.GetPreferenceValue(prefname+L".sspeed", ipref) == CStatus::OK) {
			m_sclSpeed = ipref; 
			m_sclSpeed*=0.01;
		}
		if ( pref.GetPreferenceValue(prefname+L".coll_iter", ipref) == CStatus::OK)
				this->m_collIter = ipref;
	//	if ( pref.GetPreferenceValue(prefname+L".ms_coll_bool", ipref) == CStatus::OK)
		//		this->m_testCollision = (bool)ipref;
		if ( pref.GetPreferenceValue(prefname+L".coll_type_moving", ipref) == CStatus::OK)
				switch ((int)ipref){
					case 1: m_collMS_MovingMode = cBoundingBox;break;
					case 0: m_collMS_MovingMode = cBoundingSphere;break;
					case 2: m_collMS_MovingMode = cBoundingCapsule;break;
					case 3: m_collMS_MovingMode = cRaycastPivot;break;
				}
		if ( pref.GetPreferenceValue(prefname+L".coll_type_static", ipref) == CStatus::OK)
				switch ((int)ipref){
					case 0: m_collMS_StaticMode = cMesh;break;
					case 1: m_collMS_StaticMode = cConvexHull;break;
				}		
	//	if ( pref.GetPreferenceValue(prefname+L".mm_coll_bool", ipref) == CStatus::OK)
	//			this->m_interpartikleCollisions = (bool)ipref;
		if ( pref.GetPreferenceValue(prefname+L".mm_coll_type", ipref) == CStatus::OK)
				switch ((int)ipref){
					case 0: m_collMM_MovingMode = cBoundingBox;break;
					case 1: m_collMM_MovingMode = cBoundingSphere;break;
					case 2: m_collMM_MovingMode = cBoundingCapsule;break;
					case 3: m_collMM_MovingMode = cConvexHull;break;
				}
		if (pref.GetPreferenceValue(prefname+L".bbox_scale", ipref) == CStatus::OK)
				this->m_bboxScale = (double)ipref;	
		if (pref.GetPreferenceValue(prefname+L".selectedMode", ipref) == CStatus::OK)	
				switch ((int)ipref){
					case 0: m_selectedMode = cMove;break;
					case 1: m_selectedMode = cCollide;break;
					case 2: m_selectedMode = cIgnore;break;
				}
		if (pref.GetPreferenceValue(prefname+L".frozenMode", ipref) == CStatus::OK)	
				switch ((int)ipref){
					case 0: m_frozenMode = cMove;break;
					case 1: m_frozenMode = cCollide;break;
					case 2: m_frozenMode = cIgnore;break;
				}
	}

	CStatus Setup( ToolContext& in_ctxt )
	{
		Application().LogMessage( L"pmSoftTransform" );

		m_pickfilter = L"";
		//default data incase of non existing preferences
		m_collIter = 10;
		m_toolMode = cTrans;
		m_maintoolMode = kIdle;
		m_collMS_StaticMode = cMesh;
		m_collMS_MovingMode = cBoundingBox;
		m_collMM_MovingMode = cBoundingBox;
		m_rotAboutPoint = true;
		m_rotLocal = true;
		m_sclAboutPoint = true;
		m_testCollision = false;
		m_continousCollisions = true;
		m_interpartikleCollisions = false;
		m_mouseEnteredButtonView = false;
		m_toolTime = clock();
		GetToolPreferences();
		// Load data / preferences
		Preferences pref = Application().GetPreferences();
		CValue ipref = -1;
		LONG LONGsaved;
		if ( GetSavedData(L"pmSoftTransform_BrushState",LONGsaved) == CStatus:: OK)
			ipref = LONGsaved;	
		else
			if (pref.GetPreferenceValue(L"SoftTransformPreferences.toolmode", ipref) != CStatus::OK)
				ipref = -1;

		if (ipref == -1)
			Application().LogMessage("Preferences file not found!",siErrorMsg);
		else
			switch ((int)ipref){
					case 0: m_toolMode = cTrans;break;
					case 1: m_toolMode = cRot;break;
					case 2: m_toolMode = cScale;
			}	
		// setup brush
		m_brush.Setup(100.0, kBrushSegments);
		double xyzsaved;
		if ( GetSavedData(L"pmSoftTransform_BrushSize",xyzsaved) == CStatus:: OK)	{
			m_brush.SetRadius(xyzsaved);
			m_brush.SetBaseRadius(xyzsaved);
		}
		// setup buttons
		Plugin thisPlugin = Application().GetPlugins().GetItem( L"pmSoftTransform Plugin" )  ;	
		m_pluginPath = thisPlugin.GetOriginPath();

		CStringArray l_stra; 
		l_stra.Add(L"staticCollisions");l_stra.Add(L"movingCollisions");l_stra.Add(L"pickmode");l_stra.Add(L"ignoreChildren");			
		m_Buttons.Setup(0,4,L"buttons",m_pluginPath,false,l_stra);
		
		LONG bd1=0,bd2=0, bd3=1, bd4 = 0;
		if ( GetSavedData(L"pmSoftTransform_staticCollisionsSelected", bd1 ) != CStatus::OK ) bd1 = 0;
		if ( GetSavedData(L"pmSoftTransform_movingCollisionsSelected",bd2)  != CStatus::OK ) bd2 = 0;
		if ( GetSavedData(L"pmSoftTransform_pickmodeSelected",bd3)  != CStatus::OK ) bd3 = 1;
		if ( GetSavedData(L"pmSoftTransform_ignoreChildren",bd4)  != CStatus::OK ) bd4 = 1;
		m_Buttons.SetSelected(0,(bool)bd1);
		m_Buttons.SetSelected(1,(bool)bd2);
		m_Buttons.SetSelected(2,(bool)bd3);
		m_Buttons.SetSelected(3,(bool)bd4);
		return CStatus::OK;
	}

	CStatus Cleanup( ToolContext& in_ctxt )
	{
		//save global data to reuse on next tool launch
		SaveData((double)m_brush.GetRadius(),L"pmSoftTransform_BrushSize");
		SaveData( (LONG) m_toolMode, L"pmSoftTransform_BrushState");
		SaveData( (LONG) m_Buttons.IsSelected(0),L"pmSoftTransform_staticCollisionsSelected");
		SaveData( (LONG) m_Buttons.IsSelected(1),L"pmSoftTransform_movingCollisionsSelected");
		SaveData( (LONG) m_Buttons.IsSelected(2),L"pmSoftTransform_pickmodeSelected");
		SaveData( (LONG) m_Buttons.IsSelected(3),L"pmSoftTransform_ignoreChildren");
		m_Buttons.Cleanup();
		// Called when tool is destroyed
		return CStatus::OK;
	}

	CStatus Activate( ToolContext& in_ctxt )
	{
		// Called when tool becomes the active tool
		switchCursor(in_ctxt);

		// Uncomment this to enable standard tool snapping behaviour
		in_ctxt.EnableSnapping( false );

		//Uncomment this to setup a custom tool shortcut key
		in_ctxt.RegisterShortcutKey( kKeyK, L"Switch transform type" );
		in_ctxt.RegisterShortcutKey( kKeyR, L"Brush Radius" );
		in_ctxt.RegisterShortcutKey( 0x0D/*enter*/, L"Inspect preferences" );

		// Set custom status message
		in_ctxt.SetToolDescription( L"SoftTransform. K - change transform mode. R - brush radius. SHIFT - rotate/scale about screen point. CTRL - transform with full strength.\nTranslate\nRotate\nScale" );

		return CStatus::OK;
	}

	CStatus Deactivate( ToolContext& in_ctxt )
	{
		// Called when tool becomes inactive (e.g. when user orbits using nav or chooses another tool)
		return CStatus::OK;
	}

//linear interpolation
//unused
	void lerp(spoint& dest, const spoint& a, const spoint& b, const double t)
	{
		dest.x = a.x + (b.x-a.x)*t;
		dest.y = a.y + (b.y-a.y)*t;
	}

// evaluate a point on a bezier-curve. t goes from 0 to 1.0
//unused
	void bezier(spoint &dest, const spoint& a, const spoint& b, const spoint& c, const spoint& d, const double t)
	{
		spoint ab,bc,cd,abbc,bccd;
		lerp(ab, a,b,t);           // point between a and b (green)
		lerp(bc, b,c,t);           // point between b and c (green)
		lerp(cd, c,d,t);           // point between c and d (green)
		lerp(abbc, ab,bc,t);       // point between ab and bc (blue)
		lerp(bccd, bc,cd,t);       // point between bc and cd (blue)
		lerp(dest, abbc,bccd,t);   // point on the bezier-curve (black)
	}
	
	bool getToolModKeyStatus(ToolContext& in_ctxt) //mod key for tool pressed
	{
		return in_ctxt.IsShiftKeyDown();
	}

	bool getToolModKey2Status(ToolContext& in_ctxt) //mod key for tool pressed
	{
		return false;//in_ctxt.IsShortcutKeyDown('J');
	}
	bool getNonUniformScalingModKeyStatus(ToolContext& in_ctxt) //mod key for tool pressed
	{
		return false;//in_ctxt.IsShortcutKeyDown('L');
	}
// get move proportional Fcurve
	CStatus getPropFcurve(FCurve &out_fc) //get move proportional fcurve
	{

		CSIObjectRefArray objArray = Application().FindObjects( siOperatorID );
		Operator movprop;
        for ( LONG i=0; i<objArray.GetCount(); i++ )
        {
			Operator op(objArray[i]);
			if (op.GetName() == "MoveProportional")
			{
				movprop = op;
				break;
			}          
        }
		if(movprop.GetName() != "MoveProportional")
			return CStatus::Fail;
		CParameterRefArray par = movprop.GetParameters();
		out_fc = par.GetValue(L"profile");
		return CStatus::OK;
	}

	double distanceToBrushCenter(ToolContext &in_ctxt, X3DObject &xobj)
	{			
		KinematicState objKine = xobj.GetKinematics().GetGlobal();
		CTransformation  l_objtrans  = objKine.GetTransform();
		CVector3 objPos(l_objtrans.GetPosX(),l_objtrans.GetPosY(), l_objtrans.GetPosZ());
		CVector3 objViewPos;
		in_ctxt.WorldToView(objPos, objViewPos);
		CVector3 mouseVector(m_mouseX,m_mouseY,objViewPos.GetZ());
		objViewPos -= mouseVector;				
		return objViewPos.GetLength();
	}

	void UpdateCollisionObjects(ToolContext &in_ctxt)
	{
		m_collideObjects.Clear();
		CRefArray l_moveableObjects;
		Selection sel (Application().GetSelection() );
		CRefArray l_selObjects = sel.GetArray();
	
		if ( (m_selectedMode == cCollide) && l_selObjects.GetCount() )
			m_collideObjects = sel.GetArray();
		if ( (m_selectedMode == cMove) && l_selObjects.GetCount() )
			l_moveableObjects += sel.GetArray();

		for (int i=0;i<m_pickedObjects.GetCount();i++)
		{
			X3DObject l_obj(m_pickedObjects[i]);
			Property p;
			l_obj.GetPropertyFromName(L"Visibility",p);
			
			bool selectable = p.GetParameterValue("selectability");

			if (!selectable){
				if (m_frozenMode == cMove)	{
					if( distanceToBrushCenter(in_ctxt,l_obj) <= m_brush.GetRadius() )
						l_moveableObjects.Add(l_obj);
				}
				else
					if (m_frozenMode == cCollide)
						m_collideObjects.Add(l_obj);
			}
			else
			{
				if (!l_selObjects.GetCount())	{
					if( distanceToBrushCenter(in_ctxt,l_obj) <= m_brush.GetRadius() )
						l_moveableObjects.Add(l_obj);
				}
				else{
					if(!l_obj.GetSelected())
						if (m_selectedMode == cCollide)	{
							if( distanceToBrushCenter(in_ctxt,l_obj) <= m_brush.GetRadius() )
								l_moveableObjects.Add(l_obj);
						}
						else
								m_collideObjects.Add(l_obj);
					
				}
			}
		}
	
		CRefArray l_objlist;
	// remove children from object list
		if ( m_Buttons.IsSelected(3) )	{
			LONG objcount = l_moveableObjects.GetCount();
			for (LONG i=0;i  < objcount;i++)	{
				X3DObject objc( l_moveableObjects[i] );
				X3DObject objc_parent (objc.GetParent3DObject());
				CString l_parentname = objc_parent.GetFullName() ;
				if ( l_parentname != L"Scene_Root")	{
					bool found_parent = false;
					for (LONG k=0;k < objcount;k++)	
						if (k != i)	{
							X3DObject obj( l_moveableObjects[k] );
							if ( obj.GetFullName() == l_parentname )	{
								found_parent = true;
								break;
							}
						}
					if (!found_parent)
						l_objlist.Add( objc );
				}
				else 
					l_objlist.Add( objc );
			}
			m_pickedObjects.Clear();
			for (LONG i = 0; i < l_objlist.GetCount(); i++)
				m_pickedObjects.Add( l_objlist[i] );
			
		}
		else
			m_pickedObjects = l_moveableObjects;
	}

	// calculate polygon normal (Newell's method) http://www.opengl.org/wiki/Calculating_a_Surface_Normal
	CVector3 getPolyNormal(ToolContext& in_ctxt, PolygonFace &in_poly, bool in_mulmatrix)
	{
			X3DObject			in_obj = in_poly.GetParent();
			PolygonMesh			mesh( in_obj.GetActivePrimitive().GetGeometry() );
			CVertexRefArray		verts( in_poly.GetVertices());
			CLongArray			tri_ind( in_poly.GetTriangleSubIndexArray());
			CVector3Array		n,p;	
							
			UINT vertCnt = verts.GetCount();
			double x=0,y=0,z=0;
			for(UINT i=0;i<vertCnt;i++)
			{
				Vertex current = verts[i];			
				Vertex next = verts[ (i+1) % vertCnt];
				CVector3 c(current.GetPosition() );
				CVector3 n(next.GetPosition() );
				x= x+ ( (c[1]-n[1])* (c[2]+n[2]) );
				y= y+ ( (c[2]-n[2])* (c[0]+n[0]) );
				z= z+ ( (c[0]-n[0])* (c[1]+n[1]) );
			}

			CVector3 normal(x,y,z);		
			if( in_mulmatrix )
				normal *= in_obj.GetKinematics().GetGlobal().GetTransform().GetRotationMatrix3();
			normal.NormalizeInPlace();
			return normal;
	}

	void getPolyNormal(CVector3Array &pos, CVector3 &out_normal )
	{
			UINT vertCnt = pos.GetCount();
			double x=0,y=0,z=0;
			CVector3 c,n;
			for(UINT i=0;i < vertCnt;i++)
			{

				c = pos[i];
				n = pos[(i+1) % vertCnt ];
			
				x= x+ ( (c[1]-n[1])* (c[2]+n[2]) );
				y= y+ ( (c[2]-n[2])* (c[0]+n[0]) );
				z= z+ ( (c[0]-n[0])* (c[1]+n[1]) );
			}			
			out_normal.Set(x,y,z);
	}

	// get triangle/normal data from (un) selected objects and put it in raw data array
	CStatus GetCollisionTriangleData()
	{
		m_collTriPos.Clear();
		m_collTriNormals.Clear();
		if (m_testCollision)//cache bounding box data
		{

				CRefArray collObjects(m_collideObjects);

				for (int i=0;i<collObjects.GetCount();i++)
				{
					X3DObject xobj(collObjects[i]);
					if(xobj.GetType() != "polymsh")		//safe test, remove non polymeshes
						collObjects.Remove(xobj);	
				}
				//cache triangle data
				
				for (int i=0;i<collObjects.GetCount();i++)
				{
					X3DObject		collObj(collObjects[i]);
					PolygonMesh		collmesh(collObj.GetActivePrimitive().GetGeometry());
					KinematicState  globalKinematicState = collObj.GetKinematics().GetGlobal();
					CTransformation collObjtransf = globalKinematicState.GetTransform();

					CTriangleRefArray tris(collmesh.GetTriangles());
					CVector3Array triposarray =  tris.GetPositionArray();
					LONG posArrOffset = m_collTriPos.GetCount();
					for(int j=0;j < triposarray.GetCount();j++)
						m_collTriPos.Add( triposarray[j] );

					CMatrix4 msrt=collObjtransf.GetMatrix4();
					//calculate triangle normal
					LONG triNAoffset = m_collTriNormals.GetCount();
					m_collTriNormals.Resize( triNAoffset + tris.GetCount() );

					Concurrency::parallel_for( 0, (int) tris.GetCount()*3,3, [&] (int k)		
				//	for (LONG k=0;k<tris.GetCount();k+=3)
					{
						CVector3 tnormal;
						CVector3Array cv;
						const LONG offset = posArrOffset + k;
						m_collTriPos[offset].MulByMatrix4InPlace(msrt);
						m_collTriPos[offset +1].MulByMatrix4InPlace(msrt);
						m_collTriPos[offset + 2].MulByMatrix4InPlace(msrt);
						cv.Add(m_collTriPos[offset]);cv.Add(m_collTriPos[offset+1]);cv.Add(m_collTriPos[offset+2]);
						getPolyNormal(cv, tnormal);
						tnormal.NormalizeInPlace();
						m_collTriNormals[triNAoffset + k / 3] = tnormal;									
					} );
					
				} //for (collobjects)
		}//if(m_testCollision)
		
		
		return CStatus::OK;

	}

	CStatus MouseDown( ToolContext& in_ctxt )
	{

		in_ctxt.GetMousePosition( m_mouseX, m_mouseY );
		in_ctxt.GetWorldPosition( m_mouseX, m_mouseY, m_mouseworldposition);
		m_mouseDownX = m_mouseX;
		m_mouseDownY = m_mouseY;
		m_maintoolMode = kIdle;
		m_lastOffset = m_mouseworldposition;

		if ( in_ctxt.IsLeftButtonDown() )	
		{		
			// radius change
			if ( in_ctxt.IsShortcutKeyDown(kKeyR) )	{
				m_maintoolMode = kBrushRadius;
				ShowCursor( NULL );
				GetCursorPos(&lp_mouse);
				return CStatus::OK;
			}
			//check buttons
			LONG l_index;
			if( m_Buttons.IsPointInside(in_ctxt,m_mouseX,m_mouseY,l_index) )	{
				m_Buttons.SetSelected(l_index,!m_Buttons.IsSelected(l_index) );
				return CStatus::Abort;
			}
		}
		m_maintoolMode = kInteracting;
		
		GetToolPreferences();
		// picking...
		Selection sel (Application().GetSelection() );
		CRefArray l_selObjects = sel.GetArray();

		m_collideObjects.Clear();

		m_mousepoints.Clear();
		m_mousepoints.Add( LONG(m_mouseX+ m_brush.GetRadius()) );
		m_mousepoints.Add( LONG(m_mouseY+ m_brush.GetRadius()) );
		m_mousepoints.Add( LONG(m_mouseX- m_brush.GetRadius()) );
		m_mousepoints.Add( LONG(m_mouseY- m_brush.GetRadius()) );
		m_pickfilter = sel.GetFilter().GetName();	
		siPickType pmode;
		// raycast picking on/off
		if ( m_Buttons.IsSelected(2) )	
			pmode = siPickRectangle;
		else 
			pmode = siPickRectangleRaycast;
		if ( in_ctxt.Pick( m_mousepoints, siPickMultipleObjects, pmode,m_pickfilter,CRefArray(),m_pickedObjects) != CStatus::OK)
				return CStatus::Fail;

		UpdateCollisionObjects(in_ctxt);
			
		bool left = in_ctxt.IsLeftButtonDown(), mid = in_ctxt.IsMiddleButtonDown(), right = in_ctxt.IsRightButtonDown();
		if ( left || mid || right )
		{
			
			m_temptoolMode = m_toolMode;
			// get right tool mode
			if (right){
				if(m_toolMode == cScale)
					m_toolMode = cTrans;
				else	
					m_toolMode = cScale;
				switchCursor(in_ctxt);
			}
			else
				if (mid){
					if (m_toolMode == cRot)
						m_toolMode = cTrans;
					else
						m_toolMode = cRot;
					switchCursor(in_ctxt);
				}

			if ( m_Buttons.IsSelected(0) )
				m_testCollision = true;
			else
				m_testCollision = false;
			if ( m_Buttons.IsSelected(1) )
				m_interpartikleCollisions = true;
			else
				m_interpartikleCollisions = false;

			m_sclAboutPoint = getToolModKeyStatus(in_ctxt);
			m_rotAboutPoint = getToolModKeyStatus(in_ctxt);

			FCurve fc;
			if ( getPropFcurve(fc) == CStatus::Fail )		//couldn't find move proportional op
			{
				Application().LogMessage(L"Fcurve not found! Please right click on PROP button...");
				fc = FCurve();
			};
			// for every object

			for (LONG i=0;i< m_pickedObjects.GetCount();++i )
			{
					X3DObject xobj = m_pickedObjects[i];
					
					KinematicState objKine = xobj.GetKinematics().GetGlobal();
					CTransformation  l_objtrans  = objKine.GetTransform();

					// calculate distance from brush center to objects pivot point
					double l_factor;
					double objDist;
					CVector3 objViewPos, objWorldPos;

					if ( fc.IsValid() )
					{
						CVector3 objPos(l_objtrans.GetPosX(),l_objtrans.GetPosY(), l_objtrans.GetPosZ());
						in_ctxt.WorldToView(objPos, objViewPos);

						CVector3 mouseVector(m_mouseX,m_mouseY,objViewPos.GetZ());
						objViewPos -= mouseVector;				
						objDist = objViewPos.GetLength() > m_brush.GetRadius() ?  m_brush.GetRadius() : objViewPos.GetLength();			
						l_factor = objDist /  m_brush.GetRadius();					
						l_factor = fc.Eval(l_factor*100);			//get value from fcurve
					}
					else
						l_factor = 1.0;

					m_objBrushDist.Add(l_factor);				//cache it to array		
					in_ctxt.BeginKinematicStateUpdate(objKine);
			}
			
			GetCollisionTriangleData();

			return CStatus::OK; // To start MouseDrag/MouseUp interaction...
		}

		return CStatus::False; // Ignore other mouse buttons etc.
	}

	CStatus CollisionTest(int i, CVector3Array &l_objPos, CVector3Array &bboxmin, CVector3Array &bboxmax,CVector3Array &boxhalfsize,CDoubleArray &bsphererad,CVector3Array &bspherepos, int &collTest, int &IntersectTest, CVector3 &l_offset, CVector3 &l_newoffset)
	{
			X3DObject		xobj (m_pickedObjects[i] );
			PolygonMesh		pmesh( xobj.GetActivePrimitive().GetGeometry() );
			KinematicState	objKine = xobj.GetKinematics().GetGlobal();
			CTransformation l_objTransform = objKine.GetTransform();
			
			if( m_testCollision )
			{							
										
				double v[3][3];	
				
				// static vs moving
				if(m_collMS_MovingMode == cRaycastPivot)
				{
					CVector3 vdir(l_offset);
					double boxcenter[3];
					double t, min_t=100000, u, v2, dir[3];
					vdir.NormalizeInPlace();
					dir[0]=vdir[0];
					dir[1]=vdir[1];
					dir[2]=vdir[2];
					boxcenter[0]=l_objTransform.GetPosX();
					boxcenter[1]=l_objTransform.GetPosY();
					boxcenter[2]=l_objTransform.GetPosZ();
						
					LONG hit_triangle_ind = -1;
					for (LONG j=0;j<m_collTriPos.GetCount();j+=3)
					{							
														
						v[0][0] = m_collTriPos[j][0];	v[0][1] = m_collTriPos[j][1];	v[0][2] = m_collTriPos[j][2];
						v[1][0] = m_collTriPos[j+1][0]; v[1][1] = m_collTriPos[j+1][1]; v[1][2] = m_collTriPos[j+1][2];
						v[2][0] = m_collTriPos[j+2][0]; v[2][1] = m_collTriPos[j+2][1]; v[2][2] = m_collTriPos[j+2][2];
											
						collTest = intersect_triangle(boxcenter, dir, v[0], v[1], v[2], &t, &u, &v2);
									
						if (collTest == 1)
							if ( t < min_t) {
								min_t = t;	
								hit_triangle_ind = j/3;
							}
													
					}	//triangle testing for loop

					collTest = 0;
					if (hit_triangle_ind != -1)	{
						double dot = m_collTriNormals[hit_triangle_ind].Dot(vdir);
						
						if (dot > 0)
							l_newoffset = l_offset;
						else
						{	
							CVector3 l_scaledoffset(l_offset);
							if ( (l_offset.GetLength() > abs(min_t) ) || ( abs(min_t)  < 0.001  ) )
							{
								double nl = l_offset.GetLength();
								nl *= min_t;
								l_scaledoffset *= nl;
								collTest = 1;
												
							}
							l_newoffset = l_scaledoffset;
						}
					}
					else	{
						l_newoffset = l_offset;
					}
					//	m_collTriNormals	
										
				}
				else 
					if (m_collMS_MovingMode == cBoundingSphere) //swept sphere
					{
						Vec3f reaction;
						CVector3 l_addoffset;
						int colltest_temp;
						float mindistTravel = 1000.0;

						Concurrency::concurrent_vector <float> mindist;
						Concurrency::concurrent_vector <double> collTestTemp_parallel;
						Concurrency::structured_task_group tasks;
	
						colltest_temp = false;
						Vec3f sphereVel(l_offset[0],l_offset[1],l_offset[2]);
						Sphere bsphere(Vec3f(bspherepos[i][0],bspherepos[i][1],bspherepos[i][2]),bsphererad[i]);
												 
						for (int j=0;j < m_collTriPos.GetCount();j+=3)
					//	Concurrency::parallel_for( 0, (int) m_collTriPos.GetCount(),3, [&] (int j)												
						{			
								float distTravel;
								Vec3f locreaction;
								LONG ind = j/3;
								
								Vec3f triNormal(m_collTriNormals[ind][0],m_collTriNormals[ind][1],m_collTriNormals[ind][2]);
								Vec3f v[3];
								
								v[0].x = m_collTriPos[j][0];   v[0].y = m_collTriPos[j][1];	v[0].z= m_collTriPos[j][2];
								v[1].x = m_collTriPos[j+1][0]; v[1].y = m_collTriPos[j+1][1]; v[1].z = m_collTriPos[j+1][2];
								v[2].x = m_collTriPos[j+2][0]; v[2].y = m_collTriPos[j+2][1]; v[2].z = m_collTriPos[j+2][2];
															
								const Vec3f *triPts[3];
								triPts[0] = &v[0];
								triPts[1] = &v[1];
								triPts[2] = &v[2];
													
								collTest = testIntersectionTriSphere(triPts,triNormal,bsphere,sphereVel,distTravel,&locreaction);
								
								if ( (distTravel < l_offset.GetLength() ) && collTest){
									colltest_temp=1;
									mindistTravel = distTravel;
									reaction = locreaction;
									//tasks.cancel();
									break;
								}											
						};//);

					l_newoffset = l_offset;
					if(colltest_temp != 0){
						if ( (mindistTravel < l_offset.GetLength() )  ) 
						{
							
						//	l_newoffset.NormalizeInPlace();
						//	l_newoffset.ScaleInPlace(mindistTravel);
						//	l_newoffset.AddInPlace(CVector3 (reaction.x,reaction.y,reaction.z));	
							collTest = 1;
						}
						else 
							collTest = 0;	
					}
					else 
						collTest = 0;						
				}
				else 
						if (m_collMS_MovingMode == cBoundingBox) {
								double boxcenter[3];
								double boxhalfsize[3];

								CVector3 l_addoffset;
								//get bounding box size
											
								pmesh.GetBoundingBox(boxcenter[0],boxcenter[1],boxcenter[2],boxhalfsize[0],boxhalfsize[1],boxhalfsize[2],l_objTransform);
								boxhalfsize[0] /=2;
								boxhalfsize[1] /=2;
								boxhalfsize[2] /=2;

								//test collision with every triangle
								double v[3][3];
											
								for (int ci=0;ci < m_collIter; ci++)		//for every iteration
								{
									// check velocity from 0 to l_offset
									l_addoffset.LinearlyInterpolate(CVector3(0,0,0),l_offset,(double)( (double)(ci+1)/(double)m_collIter) );
									boxcenter[0] += l_addoffset.GetX();
									boxcenter[1] += l_addoffset.GetY();
									boxcenter[2] += l_addoffset.GetZ();
									for (int j=0;j<m_collTriPos.GetCount();j+=3)	//for every triangle
									{							
										v[0][0] = m_collTriPos[j][0];		v[0][1] = m_collTriPos[j][1];		v[0][2] = m_collTriPos[j][2];
										v[1][0] = m_collTriPos[j+1][0];		v[1][1] = m_collTriPos[j+1][1];		v[1][2] = m_collTriPos[j+1][2];
										v[2][0] = m_collTriPos[j+2][0];		v[2][1] = m_collTriPos[j+2][1];		v[2][2] = m_collTriPos[j+2][2];
								
										collTest = triBoxOverlap(boxcenter,boxhalfsize,v);
										if (collTest == 1)
											break;									
									}	//triangle testing for loop

													
									if (collTest == 1)		
										break;
												
								} //coll iter for loop

												
								if (collTest == 0)
									l_newoffset = l_offset;
											
						}
															
			} //test collisions if
			else
				l_newoffset = l_offset;
									
										
			//interparticle collisions
			IntersectTest=0;

			if (m_interpartikleCollisions==1)
			{			
				if (this->m_collMM_MovingMode == cBoundingBox) {
					double bmin[3];double bmax[3];
											
					bboxmin[i].Get(bmin[0],bmin[1],bmin[2]);						
					bboxmax[i].Get(bmax[0],bmax[1],bmax[2]);
											
					bmin[0]+=l_offset[0];bmin[1]+=l_offset[1];bmin[2]+=l_offset[2];
					bmax[0]+=l_offset[0];bmax[1]+=l_offset[1];bmax[2]+=l_offset[2];
					
					for (int k=0;k<m_pickedObjects.GetCount();k++)						
						if (k != i ) {
							double bmin2[3];double bmax2[3];
							bboxmin[k].Get(bmin2[0],bmin2[1],bmin2[2]);
							bboxmax[k].Get(bmax2[0],bmax2[1],bmax2[2]);
							IntersectTest = bboxIntersect(bmin, bmax, bmin2, bmax2);
							if(IntersectTest == 1)
								break;
						}
					} //if (this->m_collMM_MovingMode == cBoundingBox)
					else
						if (this->m_collMM_MovingMode == cBoundingSphere) {
							CVector3 vel1(l_offset), pos1(l_objPos[i]);
							for (int k=0;k<m_pickedObjects.GetCount();k++)
								if (k != i ) {
									CVector3 vel2(l_offset), pos2(l_objPos[k]);
									vel2*=m_objBrushDist[k];
									IntersectTest = bSphereSweepIntersect(vel1, vel2, pos1, pos2, bsphererad[i], bsphererad[k]);
									if(IntersectTest == 1)
										break;
								}
						}
			} //if (m_interpartikleCollisions==1)

			return CStatus::OK;
	}

	CStatus MouseDrag( ToolContext& in_ctxt )
	{
#ifdef _DEBUG
		clock_t now=clock()-m_toolTime;
#endif
		if (1)//(now > MOUSE_MOVE_TIME_DELAY) 
		{
			CVector3	l_dragmouseworldpos, 
						l_offset;

			in_ctxt.GetMousePosition( m_mouseX, m_mouseY );
			m_mouseDragX=m_mouseX;
			m_mouseDragY=m_mouseY;
			in_ctxt.GetWorldPosition( m_mouseX,m_mouseY, l_dragmouseworldpos);
			// brush radius change mode
			if (m_maintoolMode == kBrushRadius)					
			{			
				double boff = (m_mouseY - m_mouseDownY)/2 + (m_mouseX- m_mouseDownX)/2;
			//	boff *= 0.1;
				m_brush.SetRadius( m_brush.GetBaseRadius() + boff);
				return CStatus::OK;
			}

			bool left = in_ctxt.IsLeftButtonDown(), mid = in_ctxt.IsMiddleButtonDown(), right = in_ctxt.IsRightButtonDown();

			double len;
			CVector3 v,vn,vr;
			double cosa,sina,t,x,y,z;
			clock_t timer;

			CVector3Array bboxmin, bboxmax, bboxhalfsize; // bounding box data
			CDoubleArray bsphererad;					  // bounding sphere data
			CVector3Array bspherepos;
			CVector3Array l_objPos;						//objects position
				
			CPlane vp;
			CMatrix4 m;
				
			in_ctxt.GetViewPlane(vp);
			vn = vp.GetNormal();
			for (int k=0;k<m_pickedObjects.GetCount();k++)		
			{
				X3DObject l_obj(m_pickedObjects[k]);
				CTransformation ct = l_obj.GetKinematics().GetGlobal().GetTransform();
				l_objPos.Add(CVector3(ct.GetTranslation() ) );
			}
			//************* cache bounding box data
			if ( (m_interpartikleCollisions==1) || (m_testCollision) ) 
			{
				if (this->m_collMM_MovingMode == cBoundingBox)
					for (int k=0;k<m_pickedObjects.GetCount();k++)		
					{
						double nbox[3];
						double nboxs[3];
											
						X3DObject l_obj(m_pickedObjects[k]);
						PolygonMesh p(l_obj.GetActivePrimitive().GetGeometry());
						CTransformation ct = l_obj.GetKinematics().GetGlobal().GetTransform();
						//get bounding box
						p.GetBoundingBox(nbox[0],nbox[1],nbox[2],nboxs[0],nboxs[1],nboxs[2],ct);
						//shrink bounding box with preferences value
						nboxs[0]*=0.5;nboxs[1]*=0.5;nboxs[2]*=0.5;
						double boxScale = m_bboxScale /100.0;
						nboxs[0] *= boxScale;nboxs[1] *= boxScale;nboxs[2] *= boxScale;
						//fill array
						bboxmin.Add( CVector3(nbox[0]-nboxs[0],nbox[1]-nboxs[1],nbox[2]-nboxs[2]) );
						bboxmax.Add( CVector3(nbox[0]+nboxs[0],nbox[1]+nboxs[1],nbox[2]+nboxs[2]) );
						bboxhalfsize.Add( CVector3(nboxs[0],nboxs[1],nboxs[2]) );
					}
					
					if ( (this->m_collMM_MovingMode == cBoundingSphere) || (this->m_collMS_MovingMode == cBoundingSphere) )
					{
						for (int k=0;k<m_pickedObjects.GetCount();k++)
						{
							double nbox[3];
							double rad;
							X3DObject l_obj(m_pickedObjects[k]);
							PolygonMesh p(l_obj.GetActivePrimitive().GetGeometry());
							CTransformation ct = l_obj.GetKinematics().GetGlobal().GetTransform();
							p.GetBoundingSphere(nbox[0],nbox[1],nbox[2],rad,siVolumeCenterMethodBBoxCenter,ct);
							rad *= (m_bboxScale /100.0);
							bsphererad.Add(rad);
							bspherepos.Add(CVector3(nbox[0],nbox[1],nbox[2]));
						}
					}
			}
			//************* END cache bounding box data

				
			timer = clock();	
			
			for (LONG i=0;i < m_pickedObjects.GetCount();i++)
			{
					l_offset = l_dragmouseworldpos;
					l_offset -= m_lastOffset;

					CVector3 l_offset_stored = l_offset;

					X3DObject		xobj (m_pickedObjects[i] );
					PolygonMesh		pmesh( xobj.GetActivePrimitive().GetGeometry() );
					KinematicState	objKine = xobj.GetKinematics().GetGlobal();
					CTransformation l_objTransform = objKine.GetTransform();

					if ( (m_toolMode == cScale) )
					{
						//uniform scaling
						if ( !getNonUniformScalingModKeyStatus(in_ctxt) )
						{
							len = ( l_offset.GetX() + l_offset.GetY()  + l_offset.GetZ() )/3.0;
							l_offset.PutX(len);
							l_offset.PutY(len);
							l_offset.PutZ(len);
						}
						l_offset *= 0.1;	//slower scaling
					}
					if ( !in_ctxt.IsControlKeyDown() )
						l_offset *= m_objBrushDist[i];	//factor calculated in mouse down event, object distance to brush center

					if (getToolModKey2Status(in_ctxt))	//slower transform
						l_offset *= 0.1;
						
					int collTest = 0, IntersectTest;
						
					CVector3 l_newoffset;
									
					if (m_toolMode == cTrans)	//*** translation
					{
									
								l_offset *= m_transSpeed;
								if(m_interpartikleCollisions || m_testCollision )
									this->CollisionTest(i, l_objPos, bboxmin, bboxmax, bboxhalfsize, bsphererad,bspherepos, collTest, IntersectTest, l_offset, l_newoffset);
								else
									l_newoffset = l_offset;
								if( ( (IntersectTest == 0) && (m_interpartikleCollisions ==1)) || (m_interpartikleCollisions != 1) )
									if (collTest != 1)	{
										l_objTransform.AddParentTranslation(l_newoffset);
										in_ctxt.UpdateKinematicState(objKine, l_objTransform);
									}
					}
					else
						if (m_toolMode == cRot)		//*** rotation
						{
								len = ( l_offset.GetX() + l_offset.GetY()  + l_offset.GetZ() )/3.0;
								
								len *= 1/PI;
								len *= m_rotSpeed;
									
								cosa = cos(len);
								sina = sin(len);
								t = 1.0 - cosa;
								//rotation matrix
								x=vn.GetX();y=vn.GetY();z=vn.GetZ();
								CMatrix3 m2;
								m2.Set(	t*x*x+cosa,t*x*y+sina*z,t*x*z-sina*y,
										t*x*y - sina*z, t*y*y+cosa, t*y*z +sina*x,
										t*x*z + sina*y, t*y*z - sina*x, t*z*z + cosa
										);
										
								if (m_rotAboutPoint)
								{
									v = l_objPos[i];
									
									v -=m_mouseworldposition;
									v.MulByMatrix3InPlace(m2);
									v +=m_mouseworldposition;
										
									CVector3 l_vobjvelocity(v);
									l_vobjvelocity -= l_objPos[i];
									
									if( m_interpartikleCollisions || m_testCollision )
										CollisionTest(i, l_objPos, bboxmin, bboxmax, bboxhalfsize, bsphererad,bspherepos, collTest, IntersectTest, l_vobjvelocity, l_newoffset);
									else
										l_newoffset = l_vobjvelocity;
									
									
									if( ( (IntersectTest == 0) && (m_interpartikleCollisions ==1)) || (m_interpartikleCollisions != 1) )
										if (collTest != 1)	{
											CVector3 pos(l_objTransform.GetPosX(),l_objTransform.GetPosY(),l_objTransform.GetPosZ());
											l_newoffset += pos;
											l_objTransform.SetTranslation(l_newoffset);
											in_ctxt.UpdateKinematicState(objKine, l_objTransform);
										}									

								}
								else	// free rotation
									{

										CMatrix3 rotm = l_objTransform.GetRotationMatrix3();

										rotm *= m2;
										CRotation rotfin(rotm);
										l_objTransform.SetRotation(rotfin);
									
										in_ctxt.UpdateKinematicState(objKine, l_objTransform);
										
									}
						}
						else
							if (m_toolMode == cScale)			//**scaling
							{
									double nbox[3];
									double rad;
									X3DObject l_obj(m_pickedObjects[i]);
									PolygonMesh p(l_obj.GetActivePrimitive().GetGeometry());
									CTransformation ct = l_obj.GetKinematics().GetGlobal().GetTransform();
									p.GetBoundingSphere(nbox[0],nbox[1],nbox[2],rad,siVolumeCenterMethodBBoxCenter,ct);
									rad *= (m_bboxScale /100.0);
									bsphererad[i] = rad;
										
									if (m_sclAboutPoint)
									{
										CVector3 sub,sub2;
										len = ( l_offset.GetX() + l_offset.GetY()  + l_offset.GetZ() )/3.0;
										
										len *= m_sclSpeed;
										sub = m_mouseworldposition;
										
										sub2 = l_objPos[i];
										sub -= sub2;
										sub.ScaleInPlace(len);
										
										sub2 = l_objPos[i];
										sub2 +=sub;
											
										CVector3 l_vobjvelocity(sub2);
										l_vobjvelocity-=l_objPos[i];
										if (m_interpartikleCollisions)
											CollisionTest(i, l_objPos, bboxmin, bboxmax, bboxhalfsize, bsphererad,bspherepos, collTest, IntersectTest, l_vobjvelocity, l_newoffset);
										else
											l_newoffset = l_vobjvelocity;
										if( ( (IntersectTest == 0) && (m_interpartikleCollisions ==1)) || (m_interpartikleCollisions != 1) )
											if (collTest != 1)	{
												CVector3 pos(l_objTransform.GetPosX(),l_objTransform.GetPosY(),l_objTransform.GetPosZ());
												l_newoffset += pos;
												l_objTransform.SetTranslation(l_newoffset);
												in_ctxt.UpdateKinematicState(objKine, l_objTransform);
											}
									}
									else
									{
										l_newoffset = l_offset;
										CVector3 l_vobjvelocity;
										if(m_interpartikleCollisions)
											this->CollisionTest(i, l_objPos, bboxmin, bboxmax, bboxhalfsize, bsphererad,bspherepos, collTest, IntersectTest, l_vobjvelocity, l_newoffset);
										if( ( (IntersectTest == 0) && (m_interpartikleCollisions ==1)) || (m_interpartikleCollisions != 1) )
											if (collTest != 1)	{
												l_newoffset.ScaleInPlace(m_sclSpeed);
												l_newoffset += l_objTransform.GetScaling();
												l_objTransform.SetScaling( l_newoffset );
												in_ctxt.UpdateKinematicState(objKine, l_objTransform);
											}
									}
										
									
						}
							

					}; // for each object
#ifdef _DEBUG
			logvalue("timed:",clock() - timer);
#endif
			
			m_toolTime=clock();
			m_lastOffset = l_dragmouseworldpos;
			
		};
		
		return CStatus::OK;
	}

	CStatus MouseUp( ToolContext& in_ctxt )
	{
		// Called when mouse button is released
		LONG x, y;
		in_ctxt.GetMousePosition( x, y );

		if (m_maintoolMode == kBrushRadius)	{
			SetCursorPos(lp_mouse.x,lp_mouse.y);
			ShowCursor( TRUE );
			m_brush.SetBaseRadius( m_brush.GetRadius() );
		}
		else
		if ( in_ctxt.IsLeftButtonDown() || in_ctxt.IsRightButtonDown() || in_ctxt.IsMiddleButtonDown()  )
		{
			bool autokey = this->GetAutoKeyValue();
			CString	autokeyString = L"";					//what values to key
			for (int i=0;i< m_pickedObjects.GetCount();i++)
			{				
					X3DObject xobj(m_pickedObjects[i]);
					KinematicState objKine = xobj.GetKinematics().GetGlobal();
					CString p1,p2,p3;
					switch (m_toolMode){
						case cTrans:
								p1 = L"posx";
								p2 = L"posy";
								p3 = L"posz";
							break;
						case cRot:
								if (m_rotAboutPoint) 
								{	
									if (autokey)
										autokeyString += xobj.GetName()+L".kine.local.posx,"+xobj.GetName()+L".kine.local.posy,"+xobj.GetName()+L".kine.local.posz,";
								}							
								p1 = L"rotx";
								p2 = L"roty";
								p3 = L"rotz";
							break;
						case cScale:
								if (m_sclAboutPoint) 
								{
									if (autokey)
										autokeyString += xobj.GetName()+L".kine.local.posx,"+xobj.GetName()+L".kine.local.posy,"+xobj.GetName()+L".kine.local.posz,";
								}
								p1 = L"sclx";
								p2 = L"scly";
								p3 = L"sclz";
							break;
					}
					in_ctxt.EndKinematicStateUpdate(objKine);
					if (autokey)
					{
						autokeyString += xobj.GetName()+L".kine.local."+p1+L","+
							xobj.GetName()+L".kine.local."+p2+L","+
							xobj.GetName()+L".kine.local."+p3+L",";
					}
			}
			if (autokey)
			{

				CValueArray args(7);
				CValue outArg;

				args[0] = autokeyString;
				Application().ExecuteCommand( L"SaveKey", args, outArg );
			}

			m_toolMode = m_temptoolMode;
			switchCursor(in_ctxt);
			m_objBrushDist.Clear();
		}
		m_maintoolMode = kIdle;
		return CStatus::OK;
	}
	void debugDrawCollisionMeshNormals()
	{			
			glDisable(GL_DEPTH_TEST);
			glBegin(GL_LINES);
			glColor3f(0.8f,0.1f,0.2f);	
			for (int i=0;i<this->m_collTriPos.GetCount();i+=3)
			{	
			
				float x,y,z;
				x=m_collTriPos[i][0]+m_collTriPos[i+1][0]+m_collTriPos[i+2][0];
				y=m_collTriPos[i][1]+m_collTriPos[i+1][1]+m_collTriPos[i+2][1];
				z=m_collTriPos[i][2]+m_collTriPos[i+1][2]+m_collTriPos[i+2][2];
				x/=3;
				y/=3;
				z/=3;
				float nx,ny,nz;
				nx=x+x*this->m_collTriNormals[i/3][0];
				ny=y+y*this->m_collTriNormals[i/3][1];
				nz=z+z*this->m_collTriNormals[i/3][2];
				glVertex3f(x,y,z);
				glVertex3f(nx,ny,nz);
			
			}
			glEnd();
			glEnable(GL_DEPTH_TEST);	
	}

	CStatus DrawInfo(ToolContext& in_ctxt, CString l_str)
	{
			LONG viewWidth, viewHeight;

			if ( in_ctxt.GetViewSize( viewWidth, viewHeight ) != CStatus::OK )
				return CStatus::Fail;
			in_ctxt.BeginViewDraw();
			glColor3f( 1.0,1.0, 1.0 );
			LONG width = 0, height = 0, descent = 0;
			in_ctxt.GetTextSize( l_str, width, height, descent );
			GLint x = (GLint)m_mouseX - width/2, y = (GLint)m_mouseY - 27/*pixels*/;
			glRasterPos2i(x ,y);
			
			in_ctxt.DrawTextString( l_str );

			in_ctxt.EndViewDraw();
			return CStatus::OK;
	}
	CStatus Draw( ToolContext& in_ctxt )
	{
		LONG x,y;
		
		x = m_mouseX;
		y = m_mouseY;
		LONG l_index;
		bool mouseUnderButton = m_Buttons.IsPointInside( in_ctxt,x,y, l_index );
		if (  in_ctxt.IsActiveView() && !in_ctxt.IsNavigating() && !mouseUnderButton ) 
		{	

			in_ctxt.BeginViewDraw();

			if (m_maintoolMode == kBrushRadius)
				m_brush.Draw(in_ctxt,1,m_mouseDownX,m_mouseDownY,0);
			else
				if ( !in_ctxt.IsInteracting() )
					m_brush.Draw(in_ctxt,1,x,y,0);
			
			// draw radius text
			/*
			LONG viewWidth, viewHeight;
			CString l_Str;
			
			if( ( m_maintoolMode == kBrushRadius ) && in_ctxt.IsInteracting() )
			{
				if ( in_ctxt.GetViewSize( viewWidth, viewHeight ) == CStatus::OK )
				{
					glColor3f( 1.0,1.0, 1.0 );
					
					l_Str = CValue(  m_brush.GetRadius() ).GetAsText();
					LONG width = 0, height = 0, descent = 0;
					in_ctxt.GetTextSize( l_Str, width, height, descent );
					GLint rx = (GLint)m_mouseDownX - width/2, ry = (GLint)m_mouseDownY - 27;
					glRasterPos2i(rx ,ry);
					in_ctxt.DrawTextString( l_Str );
				}
			}
			*/

			in_ctxt.EndViewDraw();
		}
		// draw buttons
		if ( !in_ctxt.IsInteracting() && in_ctxt.IsActiveView() && m_maintoolMode == kIdle ){			
				m_Buttons.Draw(in_ctxt);
				if (mouseUnderButton){
					CString l_str = L"";
					switch (l_index){
						case 0: l_str = L"Moving vs static collision toggle";break;
						case 1: l_str = L"Moving vs moving collision toggle";break;
						case 2: l_str = L"Raycast picking toggle";break;
						case 3: l_str = L"Ignore children toggle";break;
						default: break;
					}
					DrawInfo(in_ctxt, l_str);
				}
		}
		return CStatus::OK;
	}

	CStatus MouseMove( ToolContext& in_ctxt )
	{
		in_ctxt.GetMousePosition( m_mouseX, m_mouseY );
		LONG l_index;
		bool b_mouse = m_Buttons.IsPointInside(in_ctxt,m_mouseX,m_mouseY, l_index);
		if (b_mouse)	{
			if (!m_mouseEnteredButtonView)	{
				in_ctxt.SetCursor(siArrowCursor);
				m_mouseEnteredButtonView = true;
			}
		}
		else	
			if (m_mouseEnteredButtonView)	{//mouse left button area
				this->switchCursor(in_ctxt);
				m_mouseEnteredButtonView = false;
			}
		in_ctxt.Redraw(false);
		return CStatus::OK;
	}

	CStatus MouseEnter( ToolContext& in_ctxt )
	{
		// Called when mouse enters a view
	//	Camera camera = in_ctxt.GetCamera();
	//	Application().LogMessage( L"Entering camera view: "  + camera.GetName() );
		return CStatus::OK;
	}

	CStatus MouseLeave( ToolContext& in_ctxt )
	{
		// Called when mouse leaves a view
		
		return CStatus::OK;
	}

	CStatus SelectionChanged( ToolContext& in_ctxt )
	{
		// Selection has changed...
		return CStatus::OK;
	}

	CStatus KeyDown( ToolContext& in_ctxt )
	{
		// Called when a registered tool shortcut key is pressed
		switch (in_ctxt.GetShortcutKey()) {
		case kKeyK: {
			switch (m_toolMode){
				case cTrans:
					m_toolMode = cRot;
					break;
				case cRot:
					m_toolMode = cScale;
					break;
				case cScale:
					m_toolMode = cTrans;
					break;
			}
			switchCursor(in_ctxt);
			
			break;
		}
		 //enter
		case 0x0D:{
			CValue outArg;
			CValueArray inArgs; 
		
		//	Preferences pref = app.GetPreferences();

		//	CValue ipref;
			CString prefname = L"preferences.SoftTransformPreferences";
			inArgs.Add(prefname);
		
			Application().ExecuteCommand(L"InspectObj",inArgs,outArg);
			break;
		}
		default:
			break;
		}
		return CStatus::OK;
	}

	CStatus KeyUp( ToolContext& in_ctxt )
	{
		// Called when a registered tool shortcut key is release
		switch (in_ctxt.GetShortcutKey()) {
		case 0x09:/*Tab*/ {
			// Tab key pressed...
			
			break;
		}
		default:
			break;
		}
			
		return CStatus::OK;
	}

	CStatus MenuInit( ToolContext& in_ctxt )
	{
		// Called to initialize the context menu
		/*
		MenuItem menuItem;
		Menu menu = in_ctxt.GetSource();
		menu.AddCallbackItem( L"Menu Item 1", L"pmSoftTransform_DoMenuItem1", menuItem );
		menu.AddSeparatorItem();
		menu.AddCallbackItem( L"Exit Tool", L"pmSoftTransform_DoExitTool", menuItem );*/
		return CStatus::OK;
	}

	CStatus DoMenuItem1( ToolContext& in_ctxt )
	{
		// Menu item selected...
		//Application().LogMessage( L"Menu item 1 selected" );
		return CStatus::OK;
	}

	CStatus DoExitTool( ToolContext& in_ctxt )
	{
		in_ctxt.ExitTool();
		return CStatus::OK;
	}

}; // end class

////////////////////////////////////////////////////////////////////////////////
//
// Plugin Callbacks
//
////////////////////////////////////////////////////////////////////////////////

SICALLBACK SoftTransform_Menu_Init( CRef& in_ctxt )
{
	Context ctxt( in_ctxt );
 	MenuItem menuItem;
 	Menu menu = ctxt.GetSource();
 	menu.AddCommandItem( L"View Plane Object Proportional Transform Tool", L"pmSoftTransform", menuItem );
 	return true;
}

SICALLBACK pmSoftTransform_Init( CRef& in_ctxt )
{
	pmSoftTransform *l_pTool = new pmSoftTransform;
	if ( !l_pTool ) return CStatus::Fail;
	ToolContext l_ctxt( in_ctxt );
	l_ctxt.PutUserData( CValue((CValue::siPtrType)l_pTool) );
	return CStatus::OK;
}

SICALLBACK pmSoftTransform_Term( CRef& in_ctxt )
{
	ToolContext l_ctxt( in_ctxt );
	pmSoftTransform *l_pTool = (pmSoftTransform *)(CValue::siPtrType)l_ctxt.GetUserData();
	if ( !l_pTool ) return CStatus::Fail;
	delete l_pTool;
	l_ctxt.PutUserData( CValue((CValue::siPtrType)NULL) ); // Clear user data
	return CStatus::OK;
}

// Use a macro to simplify callback setup and forwarding...
#define DECLARE_TOOL_CALLBACK(TOOL,CALLBACK) \
SICALLBACK TOOL##_##CALLBACK( ToolContext& in_ctxt ) { \
	TOOL *l_pTool = (TOOL *)(CValue::siPtrType)in_ctxt.GetUserData(); \
	return ( l_pTool ? l_pTool->CALLBACK( in_ctxt ) : CStatus::Fail ); }

DECLARE_TOOL_CALLBACK( pmSoftTransform, Setup );
DECLARE_TOOL_CALLBACK( pmSoftTransform, Cleanup );
DECLARE_TOOL_CALLBACK( pmSoftTransform, Activate );
DECLARE_TOOL_CALLBACK( pmSoftTransform, Deactivate );
DECLARE_TOOL_CALLBACK( pmSoftTransform, MouseDown );
DECLARE_TOOL_CALLBACK( pmSoftTransform, MouseDrag );
DECLARE_TOOL_CALLBACK( pmSoftTransform, MouseUp );
DECLARE_TOOL_CALLBACK( pmSoftTransform, Draw );
DECLARE_TOOL_CALLBACK( pmSoftTransform, MouseMove );
DECLARE_TOOL_CALLBACK( pmSoftTransform, MouseEnter );
DECLARE_TOOL_CALLBACK( pmSoftTransform, MouseLeave );
DECLARE_TOOL_CALLBACK( pmSoftTransform, SelectionChanged );
DECLARE_TOOL_CALLBACK( pmSoftTransform, KeyDown );
DECLARE_TOOL_CALLBACK( pmSoftTransform, KeyUp );
DECLARE_TOOL_CALLBACK( pmSoftTransform, MenuInit );
DECLARE_TOOL_CALLBACK( pmSoftTransform, DoMenuItem1 );
DECLARE_TOOL_CALLBACK( pmSoftTransform, DoExitTool );
