/**********************************************************************
 *<
	FILE: mapmod.cpp

	DESCRIPTION:  A UVW mapping modifier

	CREATED BY: Rolf Berteig

	HISTORY: 10/21/95

 *>	Copyright (c) 1994, All Rights Reserved.
 **********************************************************************/
#include "mods.h"
#include "iparamm.h"
#include "texutil.h"
#include "bmmlib.h"
#include "simpobj.h"
#include "simpmod.h"
#include "decomp.h"
#include "mapping.h"
#include "buildver.h"

class MapMod : public MappingMod {	
	public:
		IParamBlock *pblock;
				
		static IParamMap *pmapParam;
		static MoveModBoxCMode *moveMode;
		static RotateModBoxCMode *rotMode;
		static UScaleModBoxCMode *uscaleMode;
		static NUScaleModBoxCMode *nuscaleMode;
		static SquashModBoxCMode *squashMode;		
		static FaceAlignMode *faceAlignMode;
		static RegionFitMode *regionFitMode;
		static PickAcquire pickAcquire;
		static MapMod *editMod;

		MapMod(BOOL create);		
				
		// From Animatable
		void DeleteThis() { delete this; }
		void GetClassName(TSTR& s) {s=GetString(IDS_RB_MAPMOD);}
		virtual Class_ID ClassID() {return Class_ID(UVWMAPOSM_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_UVWMAPPING); }
		CreateMouseCallBack* GetCreateMouseCallBack() {return NULL;} 
		BOOL AssignController(Animatable *control,int subAnim);
		int SubNumToRefNum(int subNum);

		ChannelMask ChannelsUsed()  {return PART_GEOM|PART_TOPO|PART_SELECT|PART_SUBSEL_TYPE|PART_VERTCOLOR;}
		ChannelMask ChannelsChanged() {return TEXMAP_CHANNEL|PART_VERTCOLOR; }		
		Class_ID InputType() {return mapObjectClassID;}
		void ModifyObject(TimeValue t, ModContext &mc, ObjectState *os, INode *node);
		Interval LocalValidity(TimeValue t);

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

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

		int NumSubs() {return 2;}
		Animatable* SubAnim(int i);
		TSTR SubAnimName(int i);
		
		RefTargetHandle Clone(RemapDir& remap = NoRemap());
		RefResult NotifyRefChanged(Interval changeInt, RefTargetHandle hTarget, PartID& partID, RefMessage message);
		
		void ActivateSubobjSel(int level, XFormModes& modes);

		// From MappingMod
		void EnterNormalAlign();
		void ExitNormalAlign();
		void EnterRegionFit();
		void ExitRegionFit();
		int GetMapType();
		void SetMapType(int type);
		float GetTile(TimeValue t,int which);
		void SetTile(TimeValue t,int which, float tile);
		BOOL GetFlip(int which);
		void SetFlip(int which,BOOL flip);
		void EnterAcquire();
		void ExitAcquire();
		float GetLength(TimeValue t);
		float GetWidth(TimeValue t);
		float GetHeight(TimeValue t);
		int GetAxis();
		void SetLength(TimeValue t,float v);
		void SetWidth(TimeValue t,float v);
		void SetHeight(TimeValue t,float v);
		void SetAxis(int v);
		int GetFirstParamVer() {return 3;}
		int GetPBlockVersion() {return pblock->GetVersion();}

	};


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

IObjParam*          MappingMod::ip        = NULL;

IParamMap*          MapMod::pmapParam     = NULL;
MoveModBoxCMode*    MapMod::moveMode      = NULL;
RotateModBoxCMode*  MapMod::rotMode       = NULL;
UScaleModBoxCMode*  MapMod::uscaleMode    = NULL;
NUScaleModBoxCMode* MapMod::nuscaleMode   = NULL;
SquashModBoxCMode*  MapMod::squashMode    = NULL;
FaceAlignMode*      MapMod::faceAlignMode = NULL;
RegionFitMode*      MapMod::regionFitMode = NULL;
PickAcquire         MapMod::pickAcquire;
MapMod*             MapMod::editMod       = NULL;

class UVWMapClassDesc:public ClassDesc {
	public:
	int 			IsPublic() { return 1; }
	void *			Create(BOOL loading = FALSE) {return new MapMod(!loading);}
	const TCHAR *	ClassName() { return GetString(IDS_RB_UVWMAP_CLASS); }
	SClass_ID		SuperClassID() { return OSM_CLASS_ID; }
	Class_ID		ClassID() { return Class_ID(UVWMAPOSM_CLASS_ID,0); }
	const TCHAR* 	Category() {return GetString(IDS_RB_DEFSURFACE);}
	};

static UVWMapClassDesc mapDesc;
extern ClassDesc* GetUVWMapModDesc() {return &mapDesc;}

static void DoBoxIcon(BOOL sel,float length, PolyLineProc& lp);

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

#define PB_MAPTYPE		0
#define PB_UTILE		1
#define PB_VTILE		2
#define PB_WTILE		3
#define PB_UFLIP		4
#define PB_VFLIP		5
#define PB_WFLIP		6
#define PB_CAP			7
#define PB_CHANNEL		8
#define PB_LENGTH		9
#define PB_WIDTH		10
#define PB_HEIGHT		11
#define PB_AXIS			12

//
//
// Parameters

static int mapIDs[]  = {IDC_MAP_PLANAR,IDC_MAP_CYL,IDC_MAP_SPHERE,IDC_MAP_BALL,IDC_MAP_BOX,IDC_MAP_FACE};
static int chanIDs[] = {IDC_MAP_CHAN1,IDC_MAP_CHAN2};
static int axisIDs[] = {IDC_MAP_X,IDC_MAP_Y,IDC_MAP_Z};

static ParamUIDesc descParam[] = {
	// Map type
	ParamUIDesc(PB_MAPTYPE,TYPE_RADIO,mapIDs,6),
	
	// U Tile
	ParamUIDesc(
		PB_UTILE,
		EDITTYPE_FLOAT,
		IDC_MAP_UTILE,IDC_MAP_UTILESPIN,
		-999999999.0f,999999999.0f,
		0.01f),

	// V Tile
	ParamUIDesc(
		PB_VTILE,
		EDITTYPE_FLOAT,
		IDC_MAP_VTILE,IDC_MAP_VTILESPIN,
		-999999999.0f,999999999.0f,
		0.01f),

	// W Tile
	ParamUIDesc(
		PB_WTILE,
		EDITTYPE_FLOAT,
		IDC_MAP_WTILE,IDC_MAP_WTILESPIN,
		-999999999.0f,999999999.0f,
		0.01f),

	// U Flip
	ParamUIDesc(PB_UFLIP,TYPE_SINGLECHEKBOX,IDC_MAP_UFLIP),

	// V Flip
	ParamUIDesc(PB_VFLIP,TYPE_SINGLECHEKBOX,IDC_MAP_VFLIP),

	// W Flip
	ParamUIDesc(PB_WFLIP,TYPE_SINGLECHEKBOX,IDC_MAP_WFLIP),

	// Cap
	ParamUIDesc(PB_CAP,TYPE_SINGLECHEKBOX,IDC_MAP_CAP),

	// Channel
	ParamUIDesc(PB_CHANNEL,TYPE_RADIO,chanIDs,2),

	// Length
	ParamUIDesc(
		PB_LENGTH,
		EDITTYPE_UNIVERSE,
		IDC_MAP_LENGTH,IDC_MAP_LENGTHSPIN,
		-999999999.0f,999999999.0f,
		SPIN_AUTOSCALE),

	// Width
	ParamUIDesc(
		PB_WIDTH,
		EDITTYPE_UNIVERSE,
		IDC_MAP_WIDTH,IDC_MAP_WIDTHSPIN,
		-999999999.0f,999999999.0f,
		SPIN_AUTOSCALE),

	// Height
	ParamUIDesc(
		PB_HEIGHT,
		EDITTYPE_UNIVERSE,
		IDC_MAP_HEIGHT,IDC_MAP_HEIGHTSPIN,
		-999999999.0f,999999999.0f,
		SPIN_AUTOSCALE),

	// Axis
	ParamUIDesc(PB_AXIS,TYPE_RADIO,axisIDs,3),
	};
#define PARAMDESC_LENGH 13

static ParamBlockDescID descVer0[] = {
	{ TYPE_INT, NULL, FALSE, 0 },
	{ TYPE_FLOAT, NULL, TRUE, 1 },
	{ TYPE_FLOAT, NULL, TRUE, 2 },
	{ TYPE_FLOAT, NULL, TRUE, 3 },
	{ TYPE_INT, NULL, TRUE, 4 },
	{ TYPE_INT, NULL, TRUE, 5 },
	{ TYPE_INT, NULL, TRUE, 6 }};

static ParamBlockDescID descVer1[] = {
	{ TYPE_INT, NULL, FALSE, 0 },
	{ TYPE_FLOAT, NULL, TRUE, 1 },
	{ TYPE_FLOAT, NULL, TRUE, 2 },
	{ TYPE_FLOAT, NULL, TRUE, 3 },
	{ TYPE_INT, NULL, FALSE, 4 },
	{ TYPE_INT, NULL, FALSE, 5 },
	{ TYPE_INT, NULL, FALSE, 6 },
	{ TYPE_INT, NULL, FALSE, 7 }};

static ParamBlockDescID descVer2[] = {
	{ TYPE_INT, NULL, FALSE, 0 },
	{ TYPE_FLOAT, NULL, TRUE, 1 },
	{ TYPE_FLOAT, NULL, TRUE, 2 },
	{ TYPE_FLOAT, NULL, TRUE, 3 },
	{ TYPE_INT, NULL, FALSE, 4 },
	{ TYPE_INT, NULL, FALSE, 5 },
	{ TYPE_INT, NULL, FALSE, 6 },
	{ TYPE_INT, NULL, FALSE, 7 },
	{ TYPE_INT, NULL, FALSE, 8 }};

static ParamBlockDescID descVer3[] = {
	{ TYPE_INT, NULL, FALSE, 0 },
	{ TYPE_FLOAT, NULL, TRUE, 1 },
	{ TYPE_FLOAT, NULL, TRUE, 2 },
	{ TYPE_FLOAT, NULL, TRUE, 3 },
	{ TYPE_INT, NULL, FALSE, 4 },
	{ TYPE_INT, NULL, FALSE, 5 },
	{ TYPE_INT, NULL, FALSE, 6 },
	{ TYPE_INT, NULL, FALSE, 7 },
	{ TYPE_INT, NULL, FALSE, 8 },
	{ TYPE_FLOAT, NULL, TRUE, 9 },
	{ TYPE_FLOAT, NULL, TRUE, 10 },
	{ TYPE_FLOAT, NULL, TRUE, 11 },
	{ TYPE_INT, NULL, FALSE, 12 },
	};

#define PBLOCK_LENGTH	13


// Array of old versions
static ParamVersionDesc versions[] = {
	ParamVersionDesc(descVer0,7,0),
	ParamVersionDesc(descVer1,8,1),	
	ParamVersionDesc(descVer2,9,2),	
	};
#define NUM_OLDVERSIONS	3

// Current version
#define CURRENT_VERSION	3
static ParamVersionDesc curVersion(descVer3,PBLOCK_LENGTH,CURRENT_VERSION);


//--- MapDlgProc -------------------------------------------------------

void MappingMod::ViewportAlign()
	{
	ViewExp *vpt = ip->GetActiveViewport();
	if (!vpt) return;

	// Get mod contexts and nodes for this modifier
	ModContextList mcList;
	INodeTab nodeList;
	ip->GetModContexts(mcList,nodeList);

	// Viewport tm
	Matrix3 vtm;
	vpt->GetAffineTM(vtm);
	vtm = Inverse(vtm);

	// Node tm
	Matrix3 ntm = nodeList[0]->GetObjectTM(ip->GetTime());	
	ntm.NoScale();

	// MC tm
	Matrix3 mctm(1);
	if (mcList[0]->tm) mctm = *mcList[0]->tm;

	// Compute the new destination transform for tmCont
	Matrix3 destTM = vtm * Inverse(ntm) * mctm;
	destTM.PreRotateZ(PI);
	switch (GetAxis()) {
		case 0:
			destTM.PreRotateY(-HALFPI);
			break;
		case 1:
			destTM.PreRotateX(HALFPI);
			break;
		}

	// Current val of tmCont
	Matrix3 curTM(1);
	tmControl->GetValue(ip->GetTime(),&curTM,FOREVER,CTRL_RELATIVE);
	Point3 s;
	for (int i=0; i<3; i++) s[i] = Length(curTM.GetRow(i));

	// These types are aligned differently
	if (GetMapType()==MAP_CYLINDRICAL ||
		GetMapType()==MAP_SPHERICAL ||
		GetMapType()==MAP_BALL) {
		destTM.PreRotateX(-HALFPI);
		}

	// Keep position and scale the same	
	destTM.SetTrans(curTM.GetTrans());
	destTM.PreScale(s);	

	// Plug-in the new value
	SetXFormPacket pckt(destTM);
	tmControl->SetValue(ip->GetTime(), &pckt);

	nodeList.DisposeTemporary();
	ip->ReleaseViewport(vpt);
	ip->RedrawViews(ip->GetTime());
	}

static void MatrixFromNormal(Point3& normal, Matrix3& mat)
	{
	Point3 vx;
	vx.z = .0f;
	vx.x = -normal.y;
	vx.y = normal.x;	
	if ( vx.x == .0f && vx.y == .0f ) {
		vx.x = 1.0f;
		}
	mat.SetRow(0,vx);
	mat.SetRow(1,normal^vx);
	mat.SetRow(2,normal);
	mat.SetTrans(Point3(0,0,0));
	mat.NoScale();
	}

void FaceAlignMouseProc::FaceAlignMap(HWND hWnd,IPoint2 m)
	{
	ViewExp *vpt = ip->GetViewport(hWnd);
	if (!vpt) return;

	Ray ray, wray;
	float at;
	TimeValue t = ip->GetTime();	
	GeomObject *obj;
	Point3 norm, pt;
	Interval valid;

	// Get mod contexts and nodes for this modifier
	ModContextList mcList;
	INodeTab nodeList;
	ip->GetModContexts(mcList,nodeList);

	// Calculate a ray from the mouse point
	vpt->MapScreenToWorldRay(float(m.x), float(m.y),wray);

	for (int i=0; i<nodeList.Count(); i++) {
		INode *node = nodeList[i];

		// Get the object from the node
		ObjectState os = node->EvalWorldState(t);
		if (os.obj->SuperClassID()==GEOMOBJECT_CLASS_ID) {
			obj = (GeomObject*)os.obj;
		} else {
			continue;
			}

		// Back transform the ray into object space.		
		Matrix3 obtm  = node->GetObjectTM(t);
		Matrix3 iobtm = Inverse(obtm);
		ray.p   = iobtm * wray.p;
		ray.dir = VectorTransform(iobtm, wray.dir);
	
		// See if we hit the object
		if (obj->IntersectRay(t,ray,at,norm)) {
			// Calculate the hit point
			pt = ray.p + ray.dir * at;
					
			// Get the mod context tm
			Matrix3 tm(1);
			if (mcList[0]->tm) tm = tm * *mcList[0]->tm;
		
			// Transform the point and ray into mod context space
			pt = pt * tm;
			norm = Normalize(VectorTransform(tm,norm));
		
			// Construct the target transformation in mod context space
			Matrix3 destTM;
			MatrixFromNormal(norm,destTM);
			destTM.SetTrans(pt);
			destTM.PreRotateZ(PI);
			switch (mod->GetAxis()) {
				case 0:
					destTM.PreRotateY(-HALFPI);
					break;
				case 1:
					destTM.PreRotateX(HALFPI);
					break;
				}
			// Our current transformation... gives relative TM
			Matrix3 curTM(1), relTM, id(1);
			mod->tmControl->GetValue(t,&curTM,valid,CTRL_RELATIVE);
			relTM = Inverse(curTM) * destTM;
		
			// Here's the modifications we need to make to get there
			tm.IdentityMatrix();
			tm.SetTrans(curTM.GetTrans());
			AffineParts parts;			
			decomp_affine(relTM,&parts);
			Point3 delta = destTM.GetTrans()-curTM.GetTrans();
			mod->Rotate(t,id,tm,parts.q);
			mod->Move(t,id,id,delta);
			break;
			}
		}

	nodeList.DisposeTemporary();
	ip->ReleaseViewport(vpt);
	}

int FaceAlignMouseProc::proc(
		HWND hWnd, int msg, int point, int flags, IPoint2 m)
	{
	switch (msg) {
		case MOUSE_POINT:
			if (point==0) {				
				theHold.Begin();
				ip->RedrawViews(ip->GetTime(),REDRAW_BEGIN);
			} else {
				theHold.Accept(0);
				ip->RedrawViews(ip->GetTime(),REDRAW_END);
				}
			break;

		case MOUSE_MOVE: {
			theHold.Restore();
			FaceAlignMap(hWnd,m);
			ip->RedrawViews(ip->GetTime(),REDRAW_INTERACTIVE);			
			break;
			}

		case MOUSE_ABORT:
			theHold.Cancel();
			ip->RedrawViews(ip->GetTime(),REDRAW_END);
			break;

		case MOUSE_FREEMOVE:			
			SetCursor(ip->GetSysCursor(SYSCUR_SELECT));
			break;
		}
	return TRUE;
	}

void FaceAlignMode::EnterMode()
	{
	mod->EnterNormalAlign();	
	}

void FaceAlignMode::ExitMode()
	{
	mod->ExitNormalAlign();	
	}


void RegionFitMouseProc::RegionFitMap(HWND hWnd,IPoint2 m)
	{
	ViewExp *vpt = ip->GetViewport(hWnd);
	if (!vpt) return;

	// Get mod contexts and nodes for this modifier
	ModContextList mcList;
	INodeTab nodeList;
	ip->GetModContexts(mcList,nodeList);

	// Viewport tm
	Matrix3 vtm;
	vpt->GetAffineTM(vtm);
	vtm = Inverse(vtm);

	// Node tm
	Matrix3 ntm = nodeList[0]->GetObjectTM(ip->GetTime());	
	
	// MC tm
	Matrix3 mctm(1);
	if (mcList[0]->tm) mctm = *mcList[0]->tm;

	// Current val of tmCont.. remove any scale
	Matrix3 ctm(1);
	mod->tmControl->GetValue(ip->GetTime(),&ctm,FOREVER,CTRL_RELATIVE);
	AffineParts parts;
	decomp_affine(ctm, &parts);
	parts.q.MakeMatrix(ctm);
	ctm.Translate(parts.t);
	
	// Compute the inverse world space tm for the gizmo
	Matrix3 iwtm = Inverse(ctm * Inverse(mctm) * ntm);
	
	// Calculate a ray from the two mouse points
	Ray mray, omray;
	float at;
	Point3 p1, p2;
	vpt->MapScreenToWorldRay(float(m.x), float(m.y),mray);
	vpt->MapScreenToWorldRay(float(om.x), float(om.y),omray);
	
	// Back transform the rays into gizmo space
	mray.p    = iwtm * mray.p;
	mray.dir  = VectorTransform(iwtm, mray.dir);
	omray.p   = iwtm * omray.p;
	omray.dir = VectorTransform(iwtm, omray.dir);

	float dir, pnt, odir, opnt;
	switch (mod->GetAxis()) {
		case 0:
			dir = mray.dir.x; odir = omray.dir.x;
			pnt = mray.p.x; opnt = omray.p.x;
			break;
		case 1:
			dir = mray.dir.y; odir = omray.dir.y;
			pnt = mray.p.y; opnt = omray.p.y;
			break;
		case 2:
			dir = mray.dir.z; odir = omray.dir.z;
			pnt = mray.p.z; opnt = omray.p.z;
			break;
		}
#define EPSILON	0.001
	// Make sure we're going to hit
	if (fabs(dir)>EPSILON && fabs(odir)>EPSILON) {
	
		// Compute the point of intersection
		at = -pnt/dir;
		p1 = mray.p + at*mray.dir;
		at = -opnt/odir;
		p2 = omray.p + at*omray.dir;
		
		// Center the map in the region
		ctm.PreTranslate((p1+p2)/2.0f);

		// Compute scale factors and scale
		float sx;
		float sy;
		switch (mod->GetAxis()) {
			case 0:
				sx = (float)fabs(p1.z-p2.z);
				sy = (float)fabs(p1.y-p2.y);
				break;
			case 1:
				sx = (float)fabs(p1.x-p2.x);
				sy = (float)fabs(p1.z-p2.z);
				break;
			case 2:
				sx = (float)fabs(p1.x-p2.x);
				sy = (float)fabs(p1.y-p2.y);
				break;
			}
		
		// Scale params instead of the matrix
		TimeValue t = ip->GetTime();		
		mod->SetWidth(t,sx);
		mod->SetLength(t,sy);		
		/*
		if (sx>0.0f && sy>0.0f) {
			ctm.PreScale(Point3(sx,sy,1.0f));
			}
		*/

		// Plug-in the new value		
		SetXFormPacket pckt(ctm);
		mod->tmControl->SetValue(ip->GetTime(), &pckt);		
		}

	nodeList.DisposeTemporary();
	ip->ReleaseViewport(vpt);
	ip->RedrawViews(ip->GetTime());
	}

int RegionFitMouseProc::proc(
		HWND hWnd, int msg, int point, int flags, IPoint2 m)
	{
	switch (msg) {
		case MOUSE_PROPCLICK:
			ip->SetStdCommandMode(CID_OBJMOVE);
			break;

		case MOUSE_POINT:
			if (point==0) {				
				om = m;
				theHold.Begin();
				ip->RedrawViews(ip->GetTime(),REDRAW_BEGIN);
			} else {
				theHold.Accept(0);
				ip->RedrawViews(ip->GetTime(),REDRAW_END);
				}
			break;

		case MOUSE_MOVE: {
			theHold.Restore();
			RegionFitMap(hWnd,m);
			ip->RedrawViews(ip->GetTime(),REDRAW_INTERACTIVE);			
			break;
			}

		case MOUSE_ABORT:
			theHold.Cancel();
			ip->RedrawViews(ip->GetTime(),REDRAW_END);
			break;

		case MOUSE_FREEMOVE:			
			SetCursor(ip->GetSysCursor(SYSCUR_SELECT));
			break;
		}
	return TRUE;
	}

void RegionFitMode::EnterMode()
	{
	mod->EnterRegionFit();	
	}

void RegionFitMode::ExitMode()
	{
	mod->ExitRegionFit();	
	}


//--- PickAcquire -------------------------------------------------------


static BOOL CALLBACK AcquireTypeDlgProc(
		HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
	static int type = IDC_ACQUIRE_REL;

	switch (msg) {
		case WM_INITDIALOG:
			CheckRadioButton(hWnd,IDC_ACQUIRE_REL,IDC_ACQUIRE_ABS,type);
			CenterWindow(hWnd,GetParent(hWnd));
			break;

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDOK:
					if (IsDlgButtonChecked(hWnd,IDC_ACQUIRE_REL)) 
						 type = IDC_ACQUIRE_REL;
					else type = IDC_ACQUIRE_ABS;
					EndDialog(hWnd,type);
					break;

				case IDCANCEL:
					EndDialog(hWnd,-1);
					break;
				}
			break;

		default:
			return FALSE;
		}
	
	return TRUE;
	}

static BOOL GetAcquireType(HWND hWnd,int &type)
	{
	type = DialogBox(
		hInstance,
		MAKEINTRESOURCE(IDD_MAP_ACQUIRE),
		hWnd,
		AcquireTypeDlgProc);
	if (type<0) return FALSE;
	else return TRUE;
	}

MappingMod *PickAcquire::FindFirstMap(ReferenceTarget *ref)
	{
	MappingMod *mod;
	if (mod = GetMappingInterface(ref)) {
		if (!mod->TestAFlag(A_MOD_DISABLED)) return mod;
		}
	
	for (int i=ref->NumRefs()-1; i>=0; i--) {
		ReferenceTarget *cref = ref->GetReference(i);
		if (cref) {
			if (mod = FindFirstMap(cref)) return mod;			
			}
		}
	return NULL;
	}

BOOL PickAcquire::Filter(INode *node)
	{
	MappingMod *amod = FindFirstMap(node->GetObjectRef());
	if (amod!=mod && amod) return TRUE;
	else return FALSE;
	}

BOOL PickAcquire::HitTest(
		IObjParam *ip,HWND hWnd,ViewExp *vpt,IPoint2 m,int flags)
	{
	INode *node = 
		ip->PickNode(hWnd,m,this);
	return node ? TRUE : FALSE;
	}
	

class PickAcquireSourceMC : public ModContextEnumProc {
	public:
		ModContext *mc;
		PickAcquireSourceMC() {mc = NULL;}
		BOOL proc(ModContext *mc) {
			this->mc = mc;
			return FALSE;
			}
	};

BOOL PickAcquire::Pick(IObjParam *ip,ViewExp *vpt)
	{
	INode *node = vpt->GetClosestHit();
	assert(node);
	MappingMod *amod = FindFirstMap(node->GetObjectRef());
	if (amod) {
		int type;
		if (!GetAcquireType(ip->GetMAXHWnd(),type)) return TRUE;

		// Get our node and mod context
		ModContextList mcList;
		INodeTab nodes;
		ip->GetModContexts(mcList,nodes);
		assert(nodes.Count());
		
		// Get the source mod's mod context
		PickAcquireSourceMC pasmc;
		amod->EnumModContexts(&pasmc);
		assert(pasmc.mc);

		// Do it!
		theHold.Begin();
		AcquireMapping(mod,mcList[0],nodes[0],amod,pasmc.mc,node,type);
		theHold.Accept(_T("Acquire Mapping"));

		nodes.DisposeTemporary();
		}
	return TRUE;
	}

void PickAcquire::EnterMode(IObjParam *ip)
	{
	mod->EnterAcquire();
	}

void PickAcquire::ExitMode(IObjParam *ip)
	{
	mod->ExitAcquire();
	}

void PickAcquire::AcquireMapping(
		MappingMod *toMod, ModContext *toMC, INode *toNode,
		MappingMod *fromMod, ModContext *fromMC, INode *fromNode,
		int type)
	{
	// Build the mats
	Matrix3 fromNTM(1);
	Matrix3 toNTM(1);
	if (type==IDC_ACQUIRE_ABS) {
		fromNTM = fromNode->GetObjectTM(ip->GetTime());	
		toNTM   = toNode->GetObjectTM(ip->GetTime());	
		}
	Matrix3 fromTM  = fromMod->CompMatrix(ip->GetTime(),fromMC,&fromNTM,FALSE,type==IDC_ACQUIRE_ABS);
	Matrix3 destTM  = fromTM * Inverse(toNTM);
	if (toMC->tm) destTM = destTM * (*toMC->tm);
	
	if (type==IDC_ACQUIRE_ABS && toMod->TestAFlag(A_PLUGIN1)) {
		switch (toMod->GetMapType()) {
			case MAP_BOX:
			case MAP_PLANAR:
				destTM.PreRotateZ(-PI);
				break;
			
			case MAP_BALL:
			case MAP_SPHERICAL:
			case MAP_CYLINDRICAL:
				destTM.PreRotateZ(-HALFPI);
				break;
			}		
		}

	// Set the TM
	SetXFormPacket pckt(destTM);
	toMod->tmControl->SetValue(ip->GetTime(),&pckt);
	toMod->SetMapType(fromMod->GetMapType());
	TimeValue t = ip->GetTime();
	for (int i=0; i<3; i++) {
		toMod->SetTile(t, i, fromMod->GetTile(t,i));
		toMod->SetFlip(i, fromMod->GetFlip(i));
		}	
	
	// Copy length/width/height
	toMod->SetLength(t,fromMod->GetLength(t));
	toMod->SetWidth(t,fromMod->GetWidth(t));
	toMod->SetHeight(t,fromMod->GetHeight(t));
	if (type!=IDC_ACQUIRE_ABS) {
		toMod->SetAxis(fromMod->GetAxis());
	} else {
		toMod->SetAxis(2);
		}

	ip->RedrawViews(ip->GetTime());
	}


//--- MapDlgProc -----------------------------------------------------

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

void MapDlgProc::DoBitmapFit(HWND hWnd)
	{
	BitmapInfo bi;
	TheManager->SelectFileInputEx(
		&bi, hWnd, GetString(IDS_RB_SELECTIMAGE));
	if (bi.Name()[0]) {		
		TheManager->GetImageInfo(&bi);
		mod->aspect = bi.Aspect() * float(bi.Width())/float(bi.Height());
		mod->flags |= CONTROL_ASPECT|CONTROL_HOLD;
		mod->NotifyDependents(FOREVER,PART_TEXMAP,REFMSG_CHANGE);
		}
	}

BOOL MapDlgProc::DlgProc(
		TimeValue t,IParamMap *map,HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
	{
	switch (msg) {
		case WM_INITDIALOG:
			SendMessage(GetDlgItem(hWnd,IDC_MAP_FITREGION),CC_COMMAND,CC_CMD_SET_TYPE,CBT_CHECK);
			SendMessage(GetDlgItem(hWnd,IDC_MAP_FITREGION),CC_COMMAND,CC_CMD_HILITE_COLOR,GREEN_WASH);
			SendMessage(GetDlgItem(hWnd,IDC_MAP_NORMALALIGN),CC_COMMAND,CC_CMD_SET_TYPE,CBT_CHECK);
			SendMessage(GetDlgItem(hWnd,IDC_MAP_NORMALALIGN),CC_COMMAND,CC_CMD_HILITE_COLOR,GREEN_WASH);
			SendMessage(GetDlgItem(hWnd,IDC_MAP_ACQUIRE),CC_COMMAND,CC_CMD_SET_TYPE,CBT_CHECK);
			SendMessage(GetDlgItem(hWnd,IDC_MAP_ACQUIRE),CC_COMMAND,CC_CMD_HILITE_COLOR,GREEN_WASH);
			break;

		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDC_MAP_SPHERE:
				case IDC_MAP_BALL:
					mod->flags |= CONTROL_UNIFORM|CONTROL_HOLD;
					mod->NotifyDependents(FOREVER,PART_TEXMAP,REFMSG_CHANGE);
					break;

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

				case IDC_MAP_CENTER:
					mod->flags |= CONTROL_CENTER|CONTROL_HOLD;
					mod->NotifyDependents(FOREVER,PART_TEXMAP,REFMSG_CHANGE);
					break;

				case IDC_MAP_BITMAPFIT:
					DoBitmapFit(hWnd);
					break;
				
				case IDC_MAP_ACQUIRE:
					mod->ip->SetPickMode(&mod->pickAcquire);					
					break;

				case IDC_MAP_NORMALALIGN:
					if (mod->ip->GetCommandMode()->ID()==CID_FACEALIGNMAP) {
						mod->ip->SetStdCommandMode(CID_OBJMOVE);
					} else {
						mod->ip->SetCommandMode(mod->faceAlignMode);
						}
					break;

				case IDC_MAP_RESET:
					theHold.Begin();
					mod->ReplaceReference(TM_REF,NULL);
					mod->flags |= CONTROL_FIT|CONTROL_CENTER|CONTROL_INIT;
					theHold.Accept(0);
					mod->NotifyDependents(FOREVER,PART_TEXMAP,REFMSG_CHANGE);
					break;

				case IDC_MAP_VIEWALIGN:
					theHold.Begin();
					mod->ViewportAlign();
					theHold.Accept(0);
					break;

				case IDC_MAP_FITREGION:
					if (mod->ip->GetCommandMode()->ID()==CID_REGIONFIT) {
						mod->ip->SetStdCommandMode(CID_OBJMOVE);
					} else {
						mod->ip->SetCommandMode(mod->regionFitMode);
						}
					break;
				}
			break;
		}
	return FALSE;
	}



//--- MapMod methods ---------------------------------------------------


MapMod::MapMod(BOOL create)
	{
	SetAFlag(A_PLUGIN1);
	if (create) flags = CONTROL_CENTER|CONTROL_FIT|CONTROL_INIT;
	else flags = 0;

	MakeRefByID(FOREVER, PBLOCK_REF, 
		CreateParameterBlock(descVer3, PBLOCK_LENGTH, CURRENT_VERSION));	
	tmControl = NULL;
	pblock->SetValue(PB_UTILE,0,1.0f);
	pblock->SetValue(PB_VTILE,0,1.0f);
	pblock->SetValue(PB_WTILE,0,1.0f);
	pblock->SetValue(PB_WIDTH,0,1.0f);
	pblock->SetValue(PB_LENGTH,0,1.0f);
	pblock->SetValue(PB_HEIGHT,0,1.0f);
	pblock->SetValue(PB_AXIS,0,2);
	}

#define NEWMAP_CHUNKID	0x0100


IOResult MapMod::Load(ILoad *iload)
	{
	Modifier::Load(iload);

	ClearAFlag(A_PLUGIN1);

	IOResult res;
	while (IO_OK==(res=iload->OpenChunk())) {
		switch (iload->CurChunkID()) {	
			case NEWMAP_CHUNKID:
				SetAFlag(A_PLUGIN1);
				break;
			}
		iload->CloseChunk();
		if (res!=IO_OK)  return res;
		}

	iload->RegisterPostLoadCallback(
		new FixDimsPLCB(this));

	iload->RegisterPostLoadCallback(
		new ParamBlockPLCB(versions,NUM_OLDVERSIONS,&curVersion,this,PBLOCK_REF));		
		
	return IO_OK;
	}

IOResult MapMod::Save(ISave *isave)
	{
	Modifier::Save(isave);
	if (TestAFlag(A_PLUGIN1)) {
		isave->BeginChunk(NEWMAP_CHUNKID);
		isave->EndChunk();
		}
 	return IO_OK;
	}

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

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

	// 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);	
	faceAlignMode = new FaceAlignMode(this,ip);
	regionFitMode = new RegionFitMode(this,ip);

	pickAcquire.mod = this;
	pickAcquire.ip  = ip;

	pmapParam = CreateCPParamMap(
		descParam,PARAMDESC_LENGH,
		pblock,
		ip,
		hInstance,
		MAKEINTRESOURCE(IDD_UVWMAPPARAM),
		GetString(IDS_RB_PARAMETERS),
		0);		 	
	pmapParam->SetUserDlgProc(new MapDlgProc(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 MapMod::EndEditParams(IObjParam *ip, ULONG flags,Animatable *next)
	{	
	this->ip = NULL;
	editMod  = 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->ClearPickMode();
	pickAcquire.mod = NULL;
	pickAcquire.ip  = NULL;	

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

	DestroyCPParamMap(pmapParam);
	}

void MapMod::EnterNormalAlign()
	{	
	SendMessage(GetDlgItem(pmapParam->GetHWnd(),IDC_MAP_NORMALALIGN),CC_COMMAND,CC_CMD_SET_STATE,1);
	}

void MapMod::ExitNormalAlign()
	{	
	SendMessage(GetDlgItem(pmapParam->GetHWnd(),IDC_MAP_NORMALALIGN),CC_COMMAND,CC_CMD_SET_STATE,0);
	}

void MapMod::EnterRegionFit()
	{
	SendMessage(GetDlgItem(pmapParam->GetHWnd(),IDC_MAP_FITREGION),CC_COMMAND,CC_CMD_SET_STATE,1);
	}

void MapMod::ExitRegionFit()
	{
	SendMessage(GetDlgItem(pmapParam->GetHWnd(),IDC_MAP_FITREGION),CC_COMMAND,CC_CMD_SET_STATE,0);
	}

void MapMod::EnterAcquire()
	{
	SendMessage(GetDlgItem(pmapParam->GetHWnd(),IDC_MAP_ACQUIRE),CC_COMMAND,CC_CMD_SET_STATE,1);
	}

void MapMod::ExitAcquire()
	{
	SendMessage(GetDlgItem(pmapParam->GetHWnd(),IDC_MAP_ACQUIRE),CC_COMMAND,CC_CMD_SET_STATE,0);
	}

float MapMod::GetLength(TimeValue t)
	{
	float f;
	pblock->GetValue(PB_LENGTH,t,f,FOREVER);
	return f;
	}

float MapMod::GetWidth(TimeValue t)
	{
	float f;
	pblock->GetValue(PB_WIDTH,t,f,FOREVER);
	return f;
	}

float MapMod::GetHeight(TimeValue t)
	{
	float f;
	pblock->GetValue(PB_HEIGHT,t,f,FOREVER);
	return f;
	}

int MapMod::GetAxis()
	{
	int a;
	pblock->GetValue(PB_AXIS,0,a,FOREVER);
	return a;
	}

void MapMod::SetLength(TimeValue t,float v)
	{
	pblock->SetValue(PB_LENGTH,t,v);
	}

void MapMod::SetWidth(TimeValue t,float v)
	{
	pblock->SetValue(PB_WIDTH,t,v);
	}

void MapMod::SetHeight(TimeValue t,float v)
	{
	pblock->SetValue(PB_HEIGHT,t,v);
	}

void MapMod::SetAxis(int v)
	{
	pblock->SetValue(PB_AXIS,0,v);
	}

int MapMod::GetMapType()
	{
	int type;
	pblock->GetValue(PB_MAPTYPE,0,type,FOREVER);
	return type;
	}

void MapMod::SetMapType(int type)
	{
	pblock->SetValue(PB_MAPTYPE,0,type);
	if (pmapParam) pmapParam->Invalidate();
	}

float MapMod::GetTile(TimeValue t,int which)
	{
	float tile;
	pblock->GetValue(PB_UTILE+which,t,tile,FOREVER);
	return tile;
	}

void MapMod::SetTile(TimeValue t,int which, float tile)
	{
	pblock->SetValue(PB_UTILE+which,t,tile);
	if (pmapParam) pmapParam->Invalidate();
	}

BOOL MapMod::GetFlip(int which)
	{
	int flip;
	pblock->GetValue(PB_UFLIP+which,0,flip,FOREVER);
	return flip;
	}

void MapMod::SetFlip(int which,BOOL flip)
	{
	pblock->SetValue(PB_UFLIP+which,0,flip);
	if (pmapParam) pmapParam->Invalidate();
	}

Interval MapMod::LocalValidity(TimeValue t)
	{	
	int i;
	float f;
	Interval valid = FOREVER;
	pblock->GetValue(PB_MAPTYPE,t,i,valid);		
	pblock->GetValue(PB_UTILE,t,f,valid);
	pblock->GetValue(PB_VTILE,t,f,valid);
	pblock->GetValue(PB_WTILE,t,f,valid);
	pblock->GetValue(PB_UFLIP,t,i,valid);
	pblock->GetValue(PB_VFLIP,t,i,valid);
	pblock->GetValue(PB_WFLIP,t,i,valid);
	pblock->GetValue(PB_LENGTH,t,f,valid);
	pblock->GetValue(PB_WIDTH,t,f,valid);
	pblock->GetValue(PB_HEIGHT,t,f,valid);
	pblock->GetValue(PB_AXIS,t,i,valid);
	if (tmControl) {
		Matrix3 tm(1);
		tmControl->GetValue(t,&tm,valid,CTRL_RELATIVE);
		}
	return valid;
	}

RefTargetHandle MapMod::Clone(RemapDir& remap) 
	{
	MapMod* newmod = new MapMod(FALSE);	
	if (TestAFlag(A_PLUGIN1)) 
		 newmod->SetAFlag(A_PLUGIN1);
	else newmod->ClearAFlag(A_PLUGIN1); 
	newmod->ReplaceReference(PBLOCK_REF,pblock->Clone(remap));	
	if (tmControl) {
		newmod->ReplaceReference(TM_REF,tmControl->Clone(remap));
		}
	return newmod;
	}

BOOL MapMod::AssignController(Animatable *control,int subAnim)
	{
	if (subAnim==TM_REF) {
		ReplaceReference(TM_REF,(ReferenceTarget*)control);
		NotifyDependents(FOREVER,0,REFMSG_CONTROLREF_CHANGE);
		NotifyDependents(FOREVER,0,REFMSG_CHANGE);
		return TRUE;
	} else {
		return FALSE;
		}
	}

int MapMod::SubNumToRefNum(int subNum)
	{
	if (subNum==TM_REF)
		 return TM_REF;
	else return -1;
	}

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

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

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

TSTR MapMod::SubAnimName(int i)
	{
	switch (i) {
		case TM_REF: 		return GetString(IDS_RB_GIZMO);
		case PBLOCK_REF:	return GetString(IDS_RB_PARAMETERS);
		default: 			return TSTR(_T(""));
		}
	}

RefResult MapMod::NotifyRefChanged(
		Interval changeInt, 
		RefTargetHandle hTarget, 
   		PartID& partID, 
   		RefMessage message) 
   	{
	switch (message) {
		case REFMSG_CHANGE:
			if (editMod==this && pmapParam) pmapParam->Invalidate();
			break;

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

		case REFMSG_GET_PARAM_NAME: {
			GetParamName *gpn = (GetParamName*)partID;
			switch (gpn->index) {				
				case PB_UTILE:		gpn->name = GetString(IDS_RB_UTILE); break;
				case PB_VTILE:		gpn->name = GetString(IDS_RB_VTILE); break;
				case PB_WTILE:		gpn->name = GetString(IDS_RB_WTILE); break;
				case PB_LENGTH:		gpn->name = GetString(IDS_RB_LENGTH); break;
				case PB_WIDTH:		gpn->name = GetString(IDS_RB_WIDTH); break;
				case PB_HEIGHT:		gpn->name = GetString(IDS_RB_HEIGHT); break;
				default:			gpn->name = TSTR(_T("")); break;
				}
			return REF_STOP; 
			}
		}
	return REF_SUCCEED;
	}
	

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


// --- Modify Object ---------------------------------------------------


void MapMod::ModifyObject(TimeValue t, ModContext &mc, ObjectState *os, INode *node)
	{
	// If it's not a mappable object then we can't help
	Object *obj = os->obj;
	if (!obj->IsMappable()) return;

	// Get pblock values	
	int type, uflip, vflip, wflip, cap, channel;	
	float utile, vtile, wtile;
	pblock->GetValue(PB_MAPTYPE,t,type,FOREVER);
	pblock->GetValue(PB_UTILE,t,utile,FOREVER);
	pblock->GetValue(PB_VTILE,t,vtile,FOREVER);
	pblock->GetValue(PB_WTILE,t,wtile,FOREVER);
	pblock->GetValue(PB_UFLIP,t,uflip,FOREVER);
	pblock->GetValue(PB_VFLIP,t,vflip,FOREVER);
	pblock->GetValue(PB_WFLIP,t,wflip,FOREVER);
	pblock->GetValue(PB_CAP,t,cap,FOREVER);
	pblock->GetValue(PB_CHANNEL,t,channel,FOREVER);
	
	// Prepare the controller and set up mats
	if (!tmControl || (flags&CONTROL_OP) || (flags&CONTROL_INITPARAMS)) 
		InitControl(mc,obj,type,t);
	Matrix3 tm;	
	tm = Inverse(CompMatrix(t,&mc,NULL));
		
	obj->ApplyUVWMap(type,utile,vtile,wtile,uflip,vflip,wflip,cap,tm,channel);
	
	// The tex mapping depends on the geom and topo so make sure the validity interval reflects this.
	Interval iv = LocalValidity(t);
	iv = iv & os->obj->ChannelValidity(t,GEOM_CHAN_NUM);
	iv = iv & os->obj->ChannelValidity(t,TOPO_CHAN_NUM);
	os->obj->UpdateValidity(TEXMAP_CHAN_NUM,iv);	
	}




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);
		}
	}





//--- MappingMod methods -----------------------------------------------

void* MappingMod::GetInterface(ULONG id)
	{	
	if (id==I_MAPPINGINTERFACE) return this;
	else return Modifier::GetInterface(id);
	}


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

	if (tmControl==NULL) {
		MakeRefByID(FOREVER,0,NewDefaultMatrix3Controller()); 
		NotifyDependents(FOREVER,0,REFMSG_CONTROLREF_CHANGE);
		}		
	
	if (flags&CONTROL_INIT) {
		SuspendAnimate();
		AnimateOff();		
		// Rotate the seem to the back
		SetXFormPacket pckt(Quat(RotateZMatrix(PI)),TRUE);
		tmControl->SetValue(t,&pckt,TRUE,CTRL_RELATIVE);
		ResumeAnimate();
		}

	if (flags&CONTROL_INITPARAMS) {
		SetLength(0,2.0f);
		SetWidth(0,2.0f);
		if (type==MAP_BALL ||
			type==MAP_SPHERICAL ||
			type==MAP_BOX) {			
			SetHeight(0,2.0f);
		} else {
			SetHeight(0,1.0f);
			}
		SetAxis(2);
		}

	if (!(flags&CONTROL_OP)) {
		flags = 0;
		return;
		}

	if (flags&CONTROL_HOLD) theHold.Begin();	

	Matrix3 oldtm = tm = Inverse(CompMatrix(t,&mc,NULL,FALSE));	
	AffineParts parts;
	decomp_affine(tm, &parts);
	parts.q.MakeMatrix(tm);
	tm.Translate(parts.t);
	
	Matrix3 mctm(1);
	if (mc.tm) mctm = *mc.tm;
	tm.Scale(Point3(
		Length(mctm.GetRow(0)),
		Length(mctm.GetRow(1)),
		Length(mctm.GetRow(2))
		));

	Point3 s;
	s.x = GetWidth(t);
	s.y = GetLength(t);
	s.z = GetHeight(t);	
	obj->GetDeformBBox(t,box,&tm,TRUE);
	box.Scale(1.001f);  // prevent wrap-around on sides of boxes

	if (flags&CONTROL_ASPECT &&
		(type==MAP_PLANAR ||
		 type==MAP_BOX ||
		 type==MAP_CYLINDRICAL)) {

		if (type==MAP_PLANAR || type==MAP_BOX) {			
			float w = aspect*s.y;
			s.x *= w / s.x;			
			s.z *= s.y / s.z;				
		} else
		if (type==MAP_CYLINDRICAL) {
			float w = (s.x+s.y)*PI;			
			s.z *= w/(aspect*s.z);
			}
		}

	if (flags&CONTROL_UNIFORM) {
		float av = (s.x + s.y + s.z)/3.0f;
		s.x = s.y = s.z = av;
		}

	if (flags&CONTROL_FIT) {
		Point3 w = box.Width();
		if (box.IsEmpty()) w = Point3(10.0f,10.0f,10.0f);

		if (type==MAP_PLANAR) {
			s.x = w.x==0.0f ? 1.0f : w.x;
			s.y = w.y==0.0f ? 1.0f : w.y;
			s.z = w.z==0.0f ? 1.0f : w.z;
		} else
		if (type==MAP_BOX) {
			s.x = w.x==0.0f ? 1.0f : w.x;
			s.y = w.y==0.0f ? 1.0f : w.y;
			s.z = w.z==0.0f ? 1.0f : w.z;
		} else
		if (type==MAP_SPHERICAL || type==MAP_BALL) {
			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;
		} else {
			if (w.x>w.y) s.x = s.y = w.x;
			else s.x = s.y = w.y;
			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_CENTER|CONTROL_FIT)) {		
		
		Box3 sbox;		
		obj->GetDeformBBox(t,sbox,&oldtm,TRUE);
		
		Point3 cent = sbox.Center();
		cent = VectorTransform(Inverse(oldtm),cent);
		cent = VectorTransform(mctm,cent);

		SetXFormPacket pckt(
			cent, //sbox.Center(),
			Matrix3(1),
			Matrix3(1));
			//Inverse(oldtm));
		
		SuspendAnimate();
		AnimateOff();
		tmControl->SetValue(t,&pckt,TRUE,CTRL_RELATIVE);
		ResumeAnimate();
		}

	if (flags&(CONTROL_FIT|CONTROL_ASPECT|CONTROL_INITPARAMS|CONTROL_UNIFORM)) {
		SuspendAnimate();
		AnimateOff();

		// Clear out any scale in the transform
		tm = Matrix3(1);	
		tmControl->GetValue(t,&tm,FOREVER,CTRL_RELATIVE);	
		decomp_affine(tm, &parts);
		parts.q.MakeMatrix(tm);
		tm.Translate(parts.t);	
		SetXFormPacket pckt(tm);		
		tmControl->SetValue(t,&pckt,TRUE,CTRL_ABSOLUTE);

		// Set the new dimensions
		SetLength(t,s.y);
		SetWidth(t,s.x);
		SetHeight(t,s.z);
		
		ResumeAnimate();
		}

	if (flags&CONTROL_HOLD) theHold.Accept(0);
	flags = 0;
	}


#if 0
void MappingMod::InitControl(ModContext &mc,Object *obj,int type,TimeValue t)
	{
	Box3 box;
	Matrix3 tm;

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

	if (flags&CONTROL_HOLD) theHold.Begin();
	if (flags&CONTROL_INIT) {
		SuspendAnimate();
		AnimateOff();		
		// Rotate the seem to the back
		SetXFormPacket pckt(Quat(RotateZMatrix(PI)),TRUE);
		tmControl->SetValue(t,&pckt,TRUE,CTRL_RELATIVE);
		}

	tm = Inverse(CompMatrix(t,&mc,NULL));
	obj->GetDeformBBox(t,box,&tm,TRUE);
	box.Scale(1.001f);  // prevent wrap-around on sides of boxes

	if (flags&CONTROL_ASPECT &&
		(type==MAP_PLANAR ||
		 type==MAP_BOX ||
		 type==MAP_CYLINDRICAL)) {
		Matrix3 tm(1), id(1);
		Interval valid;
		tmControl->GetValue(t,&tm,valid,CTRL_RELATIVE);
		Point3 s;

		if (type==MAP_PLANAR || type==MAP_BOX) {			
			float w = aspect*Length(tm.GetRow(1));
			s.x = w / Length(tm.GetRow(0));
			s.y = 1.0f;
			s.z = Length(tm.GetRow(1)) / Length(tm.GetRow(2));	
			/*
			float a = (Length(tm.GetRow(0))+Length(tm.GetRow(1))+Length(tm.GetRow(2)))/3.0f;
			s.x = aspect*a / Length(tm.GetRow(0));
			s.y = a / Length(tm.GetRow(1));
			s.z = a / Length(tm.GetRow(2));
			*/
		} else
		if (type==MAP_CYLINDRICAL) {			
			// Old way where we sized the radius
			/*
			float lz = Length(tm.GetRow(2));
			float rad = lz*aspect/(TWOPI);
			s.x = rad / Length(tm.GetRow(0));
			s.y = rad / Length(tm.GetRow(1));
			s.z = 1.0f;
			*/
			// Now we size the height
			float w = (Length(tm.GetRow(0))+Length(tm.GetRow(1)))*PI;
			s.x = 1.0f;
			s.y = 1.0f;
			s.z = w/(aspect*Length(tm.GetRow(2)));
			}
		
		SetXFormPacket pckt(s,TRUE,id,tm);
		tmControl->SetValue(t,&pckt,TRUE,CTRL_RELATIVE);
		}

	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) {
		
		// Get the box of the object in mod context space but unscaled
		Matrix3 mat = tm;
		mat.NoScale();
		obj->GetDeformBBox(t,box,&mat,TRUE);
		box.Scale(1.001f);  // prevent wrap-around on sides of boxes

		Point3 s, w  = box.Width();
		if (box.IsEmpty()) w = Point3(10.0f,10.0f,10.0f);
		Matrix3 tm(1), id(1);
		Interval valid;
		tmControl->GetValue(t,&tm,valid,CTRL_RELATIVE);

		if (type==MAP_PLANAR) {
			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;
		} else
		if (type==MAP_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==MAP_SPHERICAL || type==MAP_BALL) {
			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;			
			}
		
		s.x /= Length(tm.GetRow(0));
		s.y /= Length(tm.GetRow(1));
		s.z /= Length(tm.GetRow(2));
		
		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));
		obj->GetDeformBBox(t,box,&tm,TRUE);
		box.Scale(1.001f);  // prevent wrap-around on sides of boxes
		}		

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

	if (flags&CONTROL_HOLD) theHold.Accept(0);	
	if (flags&CONTROL_INIT) {
		ResumeAnimate();
		}

	flags = 0;
	}
#endif


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

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


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

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


Matrix3 MappingMod::CompMatrix(
		TimeValue t,ModContext *mc, Matrix3 *ntm,BOOL applySize, BOOL applyAxis)
	{
	Matrix3 tm(1);
	Interval valid;
	
	int type = GetMapType();

	if (tmControl) {
		tmControl->GetValue(t,&tm,valid,CTRL_RELATIVE);
		}
	
	// Rotate icon	
	if (applyAxis && TestAFlag(A_PLUGIN1)) {
		switch (type) {
			case MAP_BOX:
			case MAP_PLANAR:
				tm.PreRotateZ(PI);
				break;
			
			case MAP_BALL:
			case MAP_SPHERICAL:
			case MAP_CYLINDRICAL:
				tm.PreRotateZ(HALFPI);
				break;
			}
		}
	
	if (applyAxis) {
		switch (GetAxis()) {
			case 0:
				tm.PreRotateY(-HALFPI);
				break;
			case 1:			
				tm.PreRotateX(HALFPI);
				break;
			}
		}

	if (applySize) {
		Point3 s;
		s.x = GetWidth(t);
		s.y = GetLength(t);
		s.z = GetHeight(t);
		switch (type) {
			case MAP_CYLINDRICAL:			
			case MAP_PLANAR:
				s.x *= 0.5f;
				s.y *= 0.5f;
				break;
			
			case MAP_BALL:
			case MAP_SPHERICAL:
			case MAP_BOX:
				s *= 0.5f;				
				break;
			}
		tm.PreScale(s);
		}

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


void MappingMod::DoIcon(PolyLineProc& lp,BOOL sel)
	{
	int type = GetMapType();	
	switch (type) {
		case MAP_BOX: DoBoxIcon(sel,2.0f,lp); break;
		case MAP_PLANAR: DoPlanarMapIcon(sel,2.0f,2.0f,lp); break;
		case MAP_BALL:
		case MAP_SPHERICAL: DoSphericalMapIcon(sel,1.0f,lp); break;
		case MAP_CYLINDRICAL: DoCylindricalMapIcon(sel,1.0f,1.0f,lp); break;
		}
	}
 
int MappingMod::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);
		gw->setTransform(modmat);
		DoIcon(lp,FALSE);
		}
	
	gw->setRndLimits(savedLimits);	
	if (gw->checkHitCode()) {
		vpt->LogHit(inode, mc, gw->getHitDistance(), 0, NULL); 
		return 1;
		}
	return 0;
	}

int MappingMod::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);	
	gw->setTransform(modmat);	
	DoIcon(lp, ip&&ip->GetSubObjectLevel()==1);

	return 0;	
	}

void MappingMod::GetWorldBoundBox(
		TimeValue t,INode* inode, ViewExp *vpt, Box3& box, 
		ModContext *mc) 
	{
	Matrix3 modmat, ntm = inode->GetObjTMBeforeWSM(t);	
	modmat = CompMatrix(t,mc,&ntm);		
	BoxLineProc bproc(&modmat);
	DoIcon(bproc,FALSE);
	box = bproc.Box();	
	}

void MappingMod::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 MappingMod::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 FixDimsPLCB::proc(ILoad *iload)
	{
	if (mod->GetPBlockVersion()<mod->GetFirstParamVer()) {
		mod->flags |= CONTROL_INITPARAMS;
		}
	delete this;
	}
