/**********************************************************************
 *<
	FILE: selmod.cpp

	DESCRIPTION:  A volumetric selection modifier

	CREATED BY: Rolf Berteig

	HISTORY: 10/21/95

 *>	Copyright (c) 1994, All Rights Reserved.
 **********************************************************************/

#include "mods.h"
#include "iparamm.h"
#include "buildver.h"

class SelMod : public Modifier {	
	public:
		IParamBlock *pblock;		
		Control *tmControl;
		Control *posControl;
		DWORD flags;		

		static IObjParam *ip;
		static IParamMap *pmapParam;
		static MoveModBoxCMode *moveMode;
		static RotateModBoxCMode *rotMode;
		static UScaleModBoxCMode *uscaleMode;
		static NUScaleModBoxCMode *nuscaleMode;
		static SquashModBoxCMode *squashMode;		

		SelMod(BOOL create);
		void InitControl(ModContext &mc,TriObject *obj,int type,TimeValue t);
		Matrix3 CompMatrix(TimeValue t,ModContext *mc, Matrix3 *ntm, BOOL scale=TRUE, BOOL offset=TRUE);
		void DoIcon(PolyLineProc& lp,BOOL sel);
		BOOL PointInVolume(Point3 pt,int vol,Matrix3 &tm,Box3 box);
		void SelectVertices(Mesh &mesh,int method,int type,int vol,int invert,Matrix3 &tm,Box3 &box);
		void SelectFaces(Mesh &mesh,int method,int type,int vol,int invert,Matrix3 &tm,Box3 &box);

		// From Animatable
		void DeleteThis() { delete this; }
		void GetClassName(TSTR& s) { s= GetString(IDS_RB_VOLSELECT_CLASS); }  
		virtual Class_ID ClassID() { return Class_ID(SELECTOSM_CLASS_ID,0);}
		void BeginEditParams( IObjParam  *ip, ULONG flags,Animatable *prev);
		void EndEditParams( IObjParam *ip,ULONG flags,Animatable *next);
		TCHAR *GetObjectName() {return GetString(IDS_RB_VOLSELECT);}
		CreateMouseCallBack* GetCreateMouseCallBack() {return NULL;} 
		int SubNumToRefNum(int subNum);
		BOOL AssignController(Animatable *control,int subAnim);

		ChannelMask ChannelsUsed()  {return OBJ_CHANNELS;}		
		ChannelMask ChannelsChanged() {return SELECT_CHANNEL|SUBSEL_TYPE_CHANNEL|GEOM_CHANNEL;}
		Class_ID InputType() {return triObjectClassID;}
		void ModifyObject(TimeValue t, ModContext &mc, ObjectState *os, INode *node);
		Interval LocalValidity(TimeValue t);

		int NumRefs() {return 3;}
		RefTargetHandle GetReference(int i);
		void SetReference(int i, RefTargetHandle rtarg);

		int NumSubs() {return 3;}
		Animatable* SubAnim(int i);
		TSTR SubAnimName(int i);

		RefTargetHandle Clone(RemapDir& remap = NoRemap());
		RefResult NotifyRefChanged(Interval changeInt, RefTargetHandle hTarget, PartID& partID, RefMessage message);

		IOResult Load(ILoad *iload);
		IOResult Save(ISave *isave);

		int HitTest(TimeValue t, INode* inode, int type, int crossing, int flags, IPoint2 *p, ViewExp *vpt, ModContext* mc);
		int Display(TimeValue t, INode* inode, ViewExp *vpt, int flagst, ModContext *mc);
		void GetWorldBoundBox(TimeValue t,INode* inode, ViewExp *vpt, Box3& box, ModContext *mc);			

		void Move(TimeValue t, Matrix3& partm, Matrix3& tmAxis, Point3& val, BOOL localOrigin=FALSE);
		void Rotate(TimeValue t, Matrix3& partm, Matrix3& tmAxis, Quat& val, BOOL localOrigin=FALSE);
		void Scale(TimeValue t, Matrix3& partm, Matrix3& tmAxis, Point3& val, BOOL localOrigin=FALSE);
		void GetSubObjectCenters(SubObjAxisCallback *cb,TimeValue t,INode *node,ModContext *mc);
		void GetSubObjectTMs(SubObjAxisCallback *cb,TimeValue t,INode *node,ModContext *mc);
		void ActivateSubobjSel(int level, XFormModes& modes);
	};


static void DoBoxIcon(BOOL sel,float length, PolyLineProc& lp);
static void DoCylinderIcon(BOOL sel,float radius, float height, PolyLineProc& lp);
static void DoSphereIcon(BOOL sel,float radius, PolyLineProc& lp);

//--- ClassDescriptor and class vars ---------------------------------

IParamMap*          SelMod::pmapParam   = NULL;
IObjParam*          SelMod::ip          = NULL;
MoveModBoxCMode*    SelMod::moveMode    = NULL;
RotateModBoxCMode*  SelMod::rotMode     = NULL;
UScaleModBoxCMode*  SelMod::uscaleMode  = NULL;
NUScaleModBoxCMode* SelMod::nuscaleMode = NULL;
SquashModBoxCMode*  SelMod::squashMode  = NULL;


class SelClassDesc:public ClassDesc {
	public:
	int 			IsPublic() { return 1; }
	void *			Create(BOOL loading = FALSE) { return new SelMod(!loading);}
	const TCHAR *	ClassName() { return GetString(IDS_RB_VOLSELECT_CLASS); }
	SClass_ID		SuperClassID() { return OSM_CLASS_ID; }
	Class_ID		ClassID() { return Class_ID(SELECTOSM_CLASS_ID,0); }
	const TCHAR* 	Category() { return GetString(IDS_RB_DEFDEFORMATIONS);}
	};

static SelClassDesc selDesc;
extern ClassDesc* GetSelModDesc() {return &selDesc;}


//--- Parameter map/block descriptors -------------------------------

#define PBLOCK_REF	0
#define TM_REF		1
#define POS_REF		2

#define PB_LEVEL	0
#define PB_METHOD	1
#define PB_TYPE		2
#define PB_VOLUME	3
#define PB_INVERT	4


// Levels
#define SEL_OBJECT		0
#define SEL_VERTEX		1
#define SEL_FACE		2

// Volumes
#define SEL_BOX			0
#define SEL_SPHERE		1
#define SEL_CYLINDER	2

// Methods
#define SEL_REPLACE		0
#define SEL_ADD			1
#define SEL_SUBTRACT	2

// Types
#define SEL_WINDOW		0
#define SEL_CROSSING	1


// Flags
#define CONTROL_FIT		(1<<0)
#define CONTROL_CENTER	(1<<1)
#define CONTROL_UNIFORM	(1<<3)
#define CONTROL_HOLD	(1<<4)
#define CONTROL_INIT	(1<<5)
#define CONTROL_OP		(CONTROL_FIT|CONTROL_CENTER|CONTROL_UNIFORM)

#define CONTROL_USEBOX	(1<<6) // new for version 2. Means that gizmo space is not 0-1 but instead is based on the dimensions of the mod context box


//
//
// Parameters

static int levelIDs[] = {IDC_SEL_OBJECT,IDC_SEL_VERTEX,IDC_SEL_FACE};
static int methodIDs[] = {IDC_SEL_REPLACE,IDC_SEL_ADD,IDC_SEL_SUBTRACT};
static int typeIDs[] = {IDC_SEL_WINDOW,IDC_SEL_CROSSING};
static int volumeIDs[] = {IDC_SEL_BOX,IDC_SEL_SPHERE,IDC_SEL_CYLINDER};

static ParamUIDesc descParam[] = {
	// Selection level
	ParamUIDesc(PB_LEVEL,TYPE_RADIO,levelIDs,3),
	
	// Selection method
	ParamUIDesc(PB_METHOD,TYPE_RADIO,methodIDs,3),
	
	// Selection type
	ParamUIDesc(PB_TYPE,TYPE_RADIO,typeIDs,2),

	// Selection volume
	ParamUIDesc(PB_VOLUME,TYPE_RADIO,volumeIDs,3),

	// Invert
	ParamUIDesc(PB_INVERT,TYPE_SINGLECHEKBOX,IDC_SEL_INVERT),
	};
#define PARAMDESC_LENGH 5


static ParamBlockDescID descVer0[] = {
	{ TYPE_INT, NULL, FALSE, 0 },
	{ TYPE_INT, NULL, FALSE, 1 },
	{ TYPE_INT, NULL, FALSE, 2 },
	{ TYPE_INT, NULL, FALSE, 3 },
	{ TYPE_INT, NULL, FALSE, 5 }};	
#define PBLOCK_LENGTH	5

#define CURRENT_VERSION 0


//--- SelDlgProc -----------------------------------
 

class SelDlgProc : public ParamMapUserDlgProc {
	public:
		SelMod *mod;		
		SelDlgProc(SelMod *m) {mod = m;}		
		BOOL DlgProc(TimeValue t,IParamMap *map,HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam);		
		void DeleteThis() {delete this;}		
	};


BOOL SelDlgProc::DlgProc(
		TimeValue t,IParamMap *map,HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
	{
	switch (msg) {		
		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDC_SEL_SPHERE:				
					mod->flags |= CONTROL_UNIFORM|CONTROL_HOLD;
					mod->NotifyDependents(FOREVER,SELECT_CHANNEL,REFMSG_CHANGE);
					break;

				case IDC_SEL_FIT:
					mod->flags |= CONTROL_FIT|CONTROL_HOLD;
					mod->NotifyDependents(FOREVER,SELECT_CHANNEL,REFMSG_CHANGE);
					break;

				case IDC_SEL_CENTER:
					mod->flags |= CONTROL_CENTER|CONTROL_HOLD;
					mod->NotifyDependents(FOREVER,SELECT_CHANNEL,REFMSG_CHANGE);
					break;
				
				case IDC_SEL_RESET:
					theHold.Begin();
					mod->ReplaceReference(TM_REF,NULL);
					mod->flags |= CONTROL_FIT|CONTROL_CENTER|CONTROL_INIT;
					theHold.Accept(0);
					mod->NotifyDependents(FOREVER,SELECT_CHANNEL,REFMSG_CHANGE);
					break;
				}
			break;
		}
	return FALSE;
	}


//--- SelMod methods -------------------------------


SelMod::SelMod(BOOL create)
	{	
	if (create) flags = CONTROL_CENTER|CONTROL_FIT|CONTROL_INIT|CONTROL_USEBOX;
	else flags = 0;

	MakeRefByID(FOREVER, PBLOCK_REF, 
		CreateParameterBlock(descVer0, PBLOCK_LENGTH, CURRENT_VERSION));	
	tmControl  = NULL;
	posControl = NULL;
	}

#define USEBOX_CHUNK	0x0100

IOResult SelMod::Load(ILoad *iload)
	{
	IOResult res;	
	Modifier::Load(iload);	
	flags &= ~CONTROL_USEBOX;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch(iload->CurChunkID())  {
			case USEBOX_CHUNK:
				flags |= CONTROL_USEBOX;
				break; 
			}
		iload->CloseChunk();
		if (res!=IO_OK) 
			return res;
		}
	return IO_OK;
	}

IOResult SelMod::Save(ISave *isave)
	{
	Modifier::Save(isave);
	if (flags&CONTROL_USEBOX) {
		isave->BeginChunk(USEBOX_CHUNK);
		isave->EndChunk();
		}
	return IO_OK;
	}

void SelMod::BeginEditParams(IObjParam *ip, ULONG flags,Animatable *prev)
	{
	this->ip = ip;

	// Add our sub object type
	TSTR type1( GetString(IDS_RB_APPARATUS));
	TSTR type2( GetString(IDS_RB_CENTER));
	const TCHAR *ptype[] = {type1,type2};
	ip->RegisterSubObjectTypes(ptype, 2);

	// Create sub object editing modes.
	moveMode    = new MoveModBoxCMode(this,ip);
	rotMode     = new RotateModBoxCMode(this,ip);
	uscaleMode  = new UScaleModBoxCMode(this,ip);
	nuscaleMode = new NUScaleModBoxCMode(this,ip);
	squashMode  = new SquashModBoxCMode(this,ip);	
	
	pmapParam = CreateCPParamMap(
		descParam,PARAMDESC_LENGH,
		pblock,
		ip,
		hInstance,
		MAKEINTRESOURCE(IDD_SELECTPARAM),
		GetString(IDS_RB_PARAMETERS),
		0);		
	pmapParam->SetUserDlgProc(new SelDlgProc(this));

	TimeValue t = ip->GetTime();
	NotifyDependents(Interval(t,t), PART_ALL, REFMSG_BEGIN_EDIT);
	NotifyDependents(Interval(t,t), PART_ALL, REFMSG_MOD_DISPLAY_ON);
	SetAFlag(A_MOD_BEING_EDITED);
	}
		
void SelMod::EndEditParams( IObjParam *ip, ULONG flags,Animatable *next )
	{	
	this->ip = NULL;

	TimeValue t = ip->GetTime();
	ClearAFlag(A_MOD_BEING_EDITED);
 	NotifyDependents(Interval(t,t), PART_ALL, REFMSG_END_EDIT);
	NotifyDependents(Interval(t,t), PART_ALL, REFMSG_MOD_DISPLAY_OFF);

	ip->DeleteMode(moveMode);
	ip->DeleteMode(rotMode);
	ip->DeleteMode(uscaleMode);
	ip->DeleteMode(nuscaleMode);
	ip->DeleteMode(squashMode);	
	delete moveMode; moveMode = NULL;
	delete rotMode; rotMode = NULL;
	delete uscaleMode; uscaleMode = NULL;
	delete nuscaleMode; nuscaleMode = NULL;
	delete squashMode; squashMode = NULL;

	DestroyCPParamMap(pmapParam);
	pmapParam = NULL;
	}

Interval SelMod::LocalValidity(TimeValue t)
	{	
	Interval valid = FOREVER;
	if (tmControl) {
		Matrix3 tm(1);
		tmControl->GetValue(t,&tm,valid,CTRL_RELATIVE);		
		if (posControl) posControl->GetValue(t,&tm,valid,CTRL_RELATIVE);
		}
	return valid;
	}

RefTargetHandle SelMod::Clone(RemapDir& remap) 
	{
	SelMod* newmod = new SelMod(FALSE);	
	newmod->ReplaceReference(PBLOCK_REF,pblock->Clone(remap));
	newmod->ReplaceReference(TM_REF,tmControl->Clone(remap));
	newmod->flags = CONTROL_USEBOX;
	return newmod;
	}

static void FixupBox(Box3 &box)
	{
	if (box.IsEmpty()) box.MakeCube(Point3(0,0,0),10.0f);
	for (int i=0; i<3; i++) {
		if (fabs(box.pmax[i]-box.pmin[i])<0.001) {
			float cent = (box.pmax[i]-box.pmin[i])/2.0f;
			box.pmax[i] = cent + 0.0005f;
			box.pmin[i] = cent - 0.0005f;
			}
		}
	}

void SelMod::InitControl(ModContext &mc,TriObject *obj,int type,TimeValue t)
	{
	Box3 box;
	Matrix3 tm;

	Box3 mcbox = *mc.box;
	FixupBox(mcbox);	

	if (tmControl==NULL) {
		MakeRefByID(FOREVER,TM_REF,NewDefaultMatrix3Controller()); 
		NotifyDependents(FOREVER,0,REFMSG_CONTROLREF_CHANGE);
		}
	if (posControl==NULL) {
		MakeRefByID(FOREVER,POS_REF,NewDefaultPositionController()); 
		NotifyDependents(FOREVER,0,REFMSG_CONTROLREF_CHANGE);
		}

	if (flags&(CONTROL_FIT|CONTROL_CENTER)) {
		Point3 zero(0,0,0);
		posControl->SetValue(t,&zero);
		}

	tm = Inverse(CompMatrix(t,&mc,NULL,FALSE));	
	obj->GetDeformBBox(t,box,&tm,TRUE);
	FixupBox(box);
	box.Scale(1.05f);	

	if (flags&CONTROL_HOLD) theHold.Begin();
	if (flags&CONTROL_INIT) {
		SuspendAnimate();
		AnimateOff();
		}
	
	if (flags&CONTROL_UNIFORM) {
		Matrix3 tm(1), id(1);
		Interval valid;
		tmControl->GetValue(t,&tm,valid,CTRL_RELATIVE);
		float av = 0.0f;
		Point3 s;
		av += Length(tm.GetRow(0));
		av += Length(tm.GetRow(1));
		av += Length(tm.GetRow(2));
		av /= 3.0f;
		s.x = av/Length(tm.GetRow(0));
		s.y = av/Length(tm.GetRow(1));
		s.z = av/Length(tm.GetRow(2));

		SetXFormPacket pckt(s,TRUE,id,tm);
		tmControl->SetValue(t,&pckt,TRUE,CTRL_RELATIVE);
		}

	if (flags&CONTROL_FIT) {
		Point3 s, w  = box.Width();
		Matrix3 tm(1), id(1);
		Interval valid;
		tmControl->GetValue(t,&tm,valid,CTRL_RELATIVE);
				
		
		if (type==SEL_BOX) {
			s.x = w.x==0.0f ? 1.0f : w.x/2.0f;
			s.y = w.y==0.0f ? 1.0f : w.y/2.0f;
			s.z = w.z==0.0f ? 1.0f : w.z/2.0f;
		} else
		if (type==SEL_SPHERE) {
			float max = w.x;
			if (w.y>max) max = w.y;
			if (w.z>max) max = w.z;
			if (max==0.0f) max = 1.0f;
			s.x = s.y = s.z = max/2.0f;
		} else {
			if (w.x>w.y) s.x = s.y = w.x/2.0f;
			else s.x = s.y = w.y/2.0f;
			s.z = w.z;
			if (s.x==0.0f) s.x = 1.0f;
			if (s.y==0.0f) s.y = 1.0f;
			if (s.z==0.0f) s.z = 1.0f;
			}
		
		if (flags&CONTROL_USEBOX) {
			s.x /= mcbox.Width().x*0.5f;
			s.y /= mcbox.Width().y*0.5f;
			s.z /= mcbox.Width().z*0.5f;
			}

		SetXFormPacket pckt(s,TRUE,id,tm);
		tmControl->SetValue(t,&pckt,TRUE,CTRL_RELATIVE);
		
		// redo the box so our center op works
		tm = Inverse(CompMatrix(t,&mc,NULL,FALSE));	
		obj->GetDeformBBox(t,box,&tm,TRUE);
		FixupBox(box);
		}		

	if (flags&(CONTROL_CENTER|CONTROL_FIT)) {
		Matrix3 tm(1);
		Interval valid;
		tmControl->GetValue(t,&tm,valid,CTRL_RELATIVE);		
		if (!(flags&CONTROL_USEBOX)) {
			SetXFormPacket pckt(VectorTransform(tm,box.Center()));
			tmControl->SetValue(t,&pckt,TRUE,CTRL_RELATIVE);
		} else {			
			SetXFormPacket pckt(mcbox.Center()-(mcbox.Center()*tm));
			tmControl->SetValue(t,&pckt,TRUE,CTRL_RELATIVE);

			//tm.SetTrans(box.Center()*tm);
			//tm.SetTrans((mc.box->Center()*Inverse(tm))-mc.box->Center());
			//SetXFormPacket pckt(tm);
			//tmControl->SetValue(t,&pckt,TRUE,CTRL_ABSOLUTE);
			}
		}
	
	if (flags&CONTROL_HOLD) theHold.Accept(0);	
	if (flags&CONTROL_INIT) {
		ResumeAnimate();
		}

	// Turn off everything except the use box flag
	flags &= CONTROL_USEBOX;
	}

BOOL SelMod::PointInVolume(Point3 pt,int vol,Matrix3 &tm,Box3 box)
	{
	pt = pt*tm;
	
	switch (vol) {
		case SEL_BOX:
			if (pt.x>=-1.0f && pt.x<=1.0f &&
				pt.y>=-1.0f && pt.y<=1.0f &&
				pt.z>=-1.0f && pt.z<=1.0f) {
				return TRUE;
			} else {
				return FALSE;
				}
			break;			

		case SEL_SPHERE:
			return Length(pt) <= 1.0f;
			break;

		case SEL_CYLINDER:
			if (pt.z>=-1.0f && pt.z<=1.0f) {
				pt.z = 0.0f;
				return Length(pt) < 1.0f;
			} else {
				return FALSE;
				}
			break;
		}

	return FALSE;
	}

void SelMod::SelectVertices(
		Mesh &mesh,int method,int type,int vol,int invert,
		Matrix3 &tm,Box3 &box)
	{
	if (method==SEL_REPLACE) mesh.vertSel.ClearAll();

	for (int i=0; i<mesh.getNumVerts(); i++) {
		if (PointInVolume(mesh.verts[i],vol,tm,box)) {
			if (method==SEL_SUBTRACT) {
				mesh.vertSel.Clear(i);
			} else {
				mesh.vertSel.Set(i);
				}
			}
		if (invert) mesh.vertSel.Set(i,!mesh.vertSel[i]);
		}
	}

void SelMod::SelectFaces(
		Mesh &mesh,int method,int type,int vol,int invert,
		Matrix3 &tm,Box3 &box)
	{
	if (method==SEL_REPLACE) mesh.faceSel.ClearAll();
	int in;

	for (int i=0; i<mesh.getNumFaces(); i++) {
		in = 0;
		for (int k=0; k<3; k++) {
			if (PointInVolume(mesh.verts[mesh.faces[i].v[k]],vol,tm,box)) {
				in++;
				if (type==SEL_CROSSING) break;
			} else {
				if (type==SEL_WINDOW) goto nextFace;
				}
			}
		
		if (in) {
			if (method==SEL_SUBTRACT) {
				mesh.faceSel.Clear(i);
			} else {
				mesh.faceSel.Set(i);
				}
			}

		nextFace:;

		if (invert) mesh.faceSel.Set(i,!mesh.faceSel[i]);
		}
	}

void SelMod::ModifyObject(TimeValue t, ModContext &mc, ObjectState *os, INode *node)
	{
	Interval valid = LocalValidity(t);
	int level, method, type, vol, invert;
	
	pblock->GetValue(PB_LEVEL,t,level,FOREVER);
	pblock->GetValue(PB_METHOD,t,method,FOREVER);
	pblock->GetValue(PB_TYPE,t,type,FOREVER);
	pblock->GetValue(PB_VOLUME,t,vol,FOREVER);
	pblock->GetValue(PB_INVERT,t,invert,FOREVER);

	assert(os->obj->IsSubClassOf(triObjectClassID));
	TriObject *obj = (TriObject*)os->obj;
	Mesh &mesh = obj->mesh;	

	// Prepare the controller and set up mats
	if (!tmControl || (flags&CONTROL_OP)) InitControl(mc,obj,type,t);
	Matrix3 tm;	
	tm = Inverse(CompMatrix(t,&mc,NULL,TRUE,FALSE));
	
	Box3 mcbox = *mc.box;
	FixupBox(mcbox);

	switch (level) {
		case SEL_OBJECT:
			obj->mesh.selLevel = MESH_OBJECT;
			obj->mesh.ClearDispFlag(DISP_VERTTICKS|DISP_SELVERTS|DISP_SELFACES);
			break;

		case SEL_VERTEX:
			obj->mesh.selLevel = MESH_VERTEX;
			obj->mesh.SetDispFlag(DISP_VERTTICKS|DISP_SELVERTS);
			SelectVertices(obj->mesh,method,type,vol,invert,tm,mcbox);
			break;

		case SEL_FACE:
			obj->mesh.selLevel = MESH_FACE;
			obj->mesh.SetDispFlag(DISP_SELFACES);
			SelectFaces(obj->mesh,method,type,vol,invert,tm,mcbox);
			break;
		}

	obj->UpdateValidity(SELECT_CHAN_NUM,valid);
	obj->UpdateValidity(GEOM_CHAN_NUM,valid); // Have to do this to get it to evaluate
	obj->UpdateValidity(TOPO_CHAN_NUM,valid); // Have to do this to get it to evaluate
	obj->UpdateValidity(SUBSEL_TYPE_CHAN_NUM,FOREVER);
	}


int SelMod::SubNumToRefNum(int subNum)
	{
	switch (subNum) {
		case TM_REF:
		case POS_REF: return subNum;
		default:      return -1;
		}
	}

BOOL SelMod::AssignController(Animatable *control,int subAnim)
	{
	ReplaceReference(subAnim,(Control*)control);
	NotifyDependents(FOREVER,0,REFMSG_CONTROLREF_CHANGE);
	NotifyDependents(FOREVER,0,REFMSG_CHANGE);
	return TRUE;
	}

RefTargetHandle SelMod::GetReference(int i)
	{
	switch (i) {
		case PBLOCK_REF: return pblock;
		case TM_REF: return tmControl;
		case POS_REF: return posControl;
		default: return NULL;
		}
	}

void SelMod::SetReference(int i, RefTargetHandle rtarg)
	{
	switch (i) {
		case PBLOCK_REF: pblock = (IParamBlock*)rtarg; break;
		case TM_REF: tmControl = (Control*)rtarg; break;
		case POS_REF: posControl = (Control*)rtarg; break;
		}
	}

Animatable* SelMod::SubAnim(int i)
	{
	switch (i) {
		case PBLOCK_REF: return pblock;
		case TM_REF: return tmControl;
		case POS_REF: return posControl;
		default: return NULL;
		}
	}

TSTR SelMod::SubAnimName(int i)
	{
	switch (i) {
		case PBLOCK_REF: return TSTR(GetString(IDS_RB_PARAMETERS));
		case TM_REF: return TSTR(GetString(IDS_RB_APPARATUS));
		case POS_REF: return TSTR(GetString(IDS_RB_CENTER));
		default: return TSTR(_T(""));
		}
	}

RefResult SelMod::NotifyRefChanged(
		Interval changeInt, 
		RefTargetHandle hTarget, 
   		PartID& partID, 
   		RefMessage message) 
   	{
	switch (message) {
		case REFMSG_CHANGE:
			if (pmapParam && pmapParam->GetParamBlock()==pblock) {
				pmapParam->Invalidate();
				}
			break;

		case REFMSG_GET_PARAM_DIM: {
			GetParamDim *gpd = (GetParamDim*)partID;
			switch (gpd->index) {				
				case 0:
				default: gpd->dim = defaultDim; break;
				}			
			return REF_STOP; 
			}

		case REFMSG_GET_PARAM_NAME: {
			GetParamName *gpn = (GetParamName*)partID;
			switch (gpn->index) {				
				case 0:
				default: gpn->name = TSTR(_T("")); break;
				}
			return REF_STOP; 
			}
		}
	return REF_SUCCEED;
	}


// --- Gizmo transformations ------------------------------------------

void SelMod::Move(
		TimeValue t, Matrix3& partm, Matrix3& tmAxis, 
		Point3& val, BOOL localOrigin) 
	{	
#ifdef DESIGN_VER
	t=0;
#endif
	if (ip && ip->GetSubObjectLevel()==1) {		
		SetXFormPacket pckt(val,partm,tmAxis);
		tmControl->SetValue(t,&pckt,TRUE,CTRL_RELATIVE);
	} else {
		Matrix3 ptm = partm;				
		tmControl->GetValue(t,&ptm,FOREVER,CTRL_RELATIVE);
		posControl->SetValue(t,
			VectorTransform(tmAxis*Inverse(ptm),val),TRUE,CTRL_RELATIVE);
		}
	}


void SelMod::Rotate(
		TimeValue t, Matrix3& partm, Matrix3& tmAxis, 
		Quat& val, BOOL localOrigin) 
	{
#ifdef DESIGN_VER
	t=0;
#endif	
	SetXFormPacket pckt(val,localOrigin,partm,tmAxis);
	tmControl->SetValue(t,&pckt,TRUE,CTRL_RELATIVE);		
	}

void SelMod::Scale(
		TimeValue t, Matrix3& partm, Matrix3& tmAxis, 
		Point3& val, BOOL localOrigin) 
	{
#ifdef DESIGN_VER
	t=0;
#endif	
	SetXFormPacket pckt(val,localOrigin,partm,tmAxis);
	tmControl->SetValue(t,&pckt,TRUE,CTRL_RELATIVE);		
	}


Matrix3 SelMod::CompMatrix(
		TimeValue t,ModContext *mc, Matrix3 *ntm, 
		BOOL scale, BOOL offset)
	{
	Matrix3 tm(1);
	Interval valid;

	Box3 mcbox;
	if (mc && mc->box) mcbox = *mc->box;
	FixupBox(mcbox);

	if (mc && mc->box && scale && (flags&CONTROL_USEBOX)) {
		tm.Scale(mcbox.Width()/2.0f);
		tm.Translate(mcbox.Center());
		}
	
	if (posControl && offset) {
		Matrix3 tmc(1);
		posControl->GetValue(t,&tmc,valid,CTRL_RELATIVE);		
		tm = tm * tmc;
		}
	if (tmControl) {
		Matrix3 tmc(1);
		tmControl->GetValue(t,&tmc,valid,CTRL_RELATIVE);		
		tm = tm * tmc;
		}

	if (mc && mc->tm) {
		tm = tm * Inverse(*mc->tm);
		}
	if (ntm) {
		tm = tm * *ntm;
		}
	return tm;
	}


void SelMod::DoIcon(PolyLineProc& lp,BOOL sel)
	{
	int type;
	pblock->GetValue(PB_VOLUME,0,type,FOREVER);
	switch (type) {
		case SEL_BOX: DoBoxIcon(sel,2.0f,lp); break;
		case SEL_SPHERE: DoSphereIcon(sel,1.0f,lp); break;
		case SEL_CYLINDER: DoCylinderIcon(sel,1.0f,2.0f,lp); break;
		}
	}

static Box3 unitBox(Point3(-0.5f,-0.5f,-0.5f),Point3(0.5f,0.5f,0.5f));
 
int SelMod::HitTest(
		TimeValue t, INode* inode, int type, int crossing, 
		int flags, IPoint2 *p, ViewExp *vpt, ModContext* mc) 
	{	
	int savedLimits;	
	GraphicsWindow *gw = vpt->getGW();
	HitRegion hr;
	MakeHitRegion(hr,type, crossing,4,p);
	gw->setHitRegion(&hr);
	Matrix3 modmat, ntm = inode->GetObjectTM(t);
	DrawLineProc lp(gw);
		
	gw->setRndLimits(((savedLimits = gw->getRndLimits()) | GW_PICK) & ~GW_ILLUM);
	gw->clearHitCode();
	
	if (ip && ip->GetSubObjectLevel()==1) {
		modmat = CompMatrix(t,mc,&ntm,TRUE,FALSE);
		gw->setTransform(modmat);
		DoIcon(lp,FALSE);
		}

	if (ip && (
		ip->GetSubObjectLevel()==1 ||
		ip->GetSubObjectLevel()==2)) {
		modmat = CompMatrix(t,mc,&ntm);
		gw->setTransform(modmat);
		DrawCenterMark(lp,unitBox);		
		}
	
	gw->setRndLimits(savedLimits);	
	if (gw->checkHitCode()) {
		vpt->LogHit(inode, mc, gw->getHitDistance(), 0, NULL); 
		return 1;
		}
	return 0;
	}

int SelMod::Display(
		TimeValue t, INode* inode, ViewExp *vpt, int flags, 
		ModContext *mc) 
	{	
	GraphicsWindow *gw = vpt->getGW();
	Matrix3 modmat, ntm = inode->GetObjectTM(t);
	DrawLineProc lp(gw);

	modmat = CompMatrix(t,mc,&ntm,TRUE,FALSE);	
	gw->setTransform(modmat);	
	DoIcon(lp, ip&&ip->GetSubObjectLevel()==1);

	modmat = CompMatrix(t,mc,&ntm);
	gw->setTransform(modmat);
	if (ip && (
		ip->GetSubObjectLevel()==1 ||
		ip->GetSubObjectLevel()==2)) {
		//gw->setColor(LINE_COLOR, (float)1.0, (float)1.0, (float)0.0);
		gw->setColor(LINE_COLOR,GetUIColor(COLOR_SEL_GIZMOS));
	} else {
		//gw->setColor( LINE_COLOR, (float).85, (float).5, (float)0.0);
		gw->setColor(LINE_COLOR,GetUIColor(COLOR_GIZMOS));
		}
	DrawCenterMark(lp,unitBox);
	return 0;	
	}

void SelMod::GetWorldBoundBox(
		TimeValue t,INode* inode, ViewExp *vpt, Box3& box, 
		ModContext *mc) 
	{
	Matrix3 modmat, ntm = inode->GetObjectTM(t);	
	modmat = CompMatrix(t,mc,&ntm,TRUE,FALSE);		
	BoxLineProc bproc(&modmat);
	DoIcon(bproc,FALSE);

	modmat = CompMatrix(t,mc,&ntm);
	DrawCenterMark(bproc,unitBox);

	box = bproc.Box();	
	}

void SelMod::GetSubObjectCenters(
		SubObjAxisCallback *cb,TimeValue t,INode *node,ModContext *mc)
	{	
	Matrix3 modmat, ntm = node->GetObjectTM(t);		
	modmat = CompMatrix(t,mc,&ntm);
	cb->Center(modmat.GetTrans(),0);	
	}

void SelMod::GetSubObjectTMs(
		SubObjAxisCallback *cb,TimeValue t,INode *node,ModContext *mc)
	{
	Matrix3 ntm = node->GetObjectTM(t), modmat;
	modmat = CompMatrix(t,mc,&ntm);
	cb->TM(modmat,0);
	}

void SelMod::ActivateSubobjSel(int level, XFormModes& modes )
	{	
	switch (level) {
		case 1: // Modifier box
			modes = XFormModes(moveMode,rotMode,nuscaleMode,uscaleMode,squashMode,NULL);
			break;
		case 2: // Modifier Center
			modes = XFormModes(moveMode,NULL,NULL,NULL,NULL,NULL);
			break;
		}
	NotifyDependents(FOREVER,PART_DISPLAY,REFMSG_CHANGE);
	}


//----------------------------------------------------------------



#define NUM_SEGS	16

static void DoSphereIcon(BOOL sel,float radius, PolyLineProc& lp)
	{
	float u;
	Point3 pt[3];
	
	if (sel) //lp.SetLineColor(1.0f,1.0f,0.0f);
		lp.SetLineColor(GetUIColor(COLOR_SEL_GIZMOS));
	else //lp.SetLineColor(0.85f,0.5f,0.0f);		
		lp.SetLineColor(GetUIColor(COLOR_GIZMOS));
	
	// XY
	pt[0] = Point3(radius,0.0f,0.0f);
	for (int i=1; i<=NUM_SEGS; i++) {
		u = float(i)/float(NUM_SEGS) * TWOPI;
		pt[1].x = (float)cos(u) * radius;
		pt[1].y = (float)sin(u) * radius;
		pt[1].z = 0.0f;
		lp.proc(pt,2);
		pt[0] = pt[1];
		}

	// YZ	
	pt[0] = Point3(0.0f,radius,0.0f);
	for (i=1; i<=NUM_SEGS; i++) {
		u = float(i)/float(NUM_SEGS) * TWOPI;
		pt[1].y = (float)cos(u) * radius;
		pt[1].z = (float)sin(u) * radius;
		pt[1].x = 0.0f;
		lp.proc(pt,2);
		pt[0] = pt[1];
		}
	
	// ZX	
	pt[0] = Point3(0.0f,0.0f,radius);
	for (i=1; i<=NUM_SEGS; i++) {		
		u = float(i)/float(NUM_SEGS) * TWOPI;
		pt[1].z = (float)cos(u) * radius;
		pt[1].x = (float)sin(u) * radius;
		pt[1].y = 0.0f;
		lp.proc(pt,2);
		pt[0] = pt[1];
		}
	}

static void DoCylinderIcon(BOOL sel,float radius, float height, PolyLineProc& lp)
	{
	float u;
	Point3 pt[5], opt;
	
	if (sel) //lp.SetLineColor(1.0f,1.0f,0.0f);
		lp.SetLineColor(GetUIColor(COLOR_SEL_GIZMOS));
	else //lp.SetLineColor(0.85f,0.5f,0.0f);		
		lp.SetLineColor(GetUIColor(COLOR_GIZMOS));
	
	height *= 0.5f;
		
	opt = Point3(radius,0.0f,-height);
	for (int i=1; i<=NUM_SEGS; i++) {
		u = float(i)/float(NUM_SEGS) * TWOPI;
		pt[0]   = opt;
		
		pt[1].x = (float)cos(u) * radius;
		pt[1].y = (float)sin(u) * radius;
		pt[1].z = -height;
		
		pt[2].x = pt[1].x;
		pt[2].y = pt[1].y;
		pt[2].z = height;

		pt[3]   = opt;
		pt[3].z = height;
		
		lp.proc(pt,4);		
		opt = pt[1];
		}

	}


static int lStart[12] = {0,1,3,2,4,5,7,6,0,1,2,3};
static int lEnd[12]   = {1,3,2,0,5,7,6,4,4,5,6,7};

static void DoBoxIcon(BOOL sel,float length, PolyLineProc& lp)
	{
	Point3 pt[3];
	
	length *= 0.5f;
	Box3 box;
	box.pmin = Point3(-length,-length,-length);
	box.pmax = Point3( length, length, length);

	if (sel) //lp.SetLineColor(1.0f,1.0f,0.0f);
		lp.SetLineColor(GetUIColor(COLOR_SEL_GIZMOS));
	else //lp.SetLineColor(0.85f,0.5f,0.0f);		
		lp.SetLineColor(GetUIColor(COLOR_GIZMOS));
	
	for (int i=0; i<12; i++) {
		pt[0] = box[lStart[i]];
		pt[1] = box[lEnd[i]];
		lp.proc(pt,2);
		}
	}


