/**********************************************************************
 *<
	FILE: tessmod.cpp

	DESCRIPTION:  A tessellation modifier

	CREATED BY: Rolf Berteig
	(Modified later, MNMesh stuff added, by Steve Anderson)

	HISTORY: 10/22/96
	(Modified 3/21/97)

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

#include "mods.h"
#include "MNMath.h" 
#include "iparamm.h"

#define DEF_FACE_TYPE 0	// triangle
#define DEF_TESS_TYPE 0 // edge
#define DEF_TENSION 25.0f
#define DEF_ITERS 0	// Really, 1.

class TessMod : public Modifier {	
public:
	IParamBlock *pblock;
	static int LastFaceType, LastType;

	static IObjParam *ip;
	static IParamMap *pmapParam;
	static TessMod *editMod;

	TessMod();

	// From Animatable
	void DeleteThis() { delete this; }
	void GetClassName(TSTR& s) {s = GetString(IDS_RB_TESSMOD);}  
	virtual Class_ID ClassID() { return Class_ID(TESSELLATE_CLASS_ID,0);}
	void BeginEditParams(IObjParam  *ip, ULONG flags,Animatable *prev);
	void EndEditParams(IObjParam *ip,ULONG flags,Animatable *next);
	RefTargetHandle Clone(RemapDir& remap = NoRemap());
	TCHAR *GetObjectName() {return GetString(IDS_RB_TESSMOD);}
	IOResult Load (ILoad *iload);

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

	// From BaseObject
	CreateMouseCallBack* GetCreateMouseCallBack() {return NULL;}
	IParamArray *GetParamBlock() {return pblock;}
	int GetParamBlockIndex(int id) {return id;}

	int NumRefs() {return 1;}
	RefTargetHandle GetReference(int i) {return pblock;}
	void SetReference(int i, RefTargetHandle rtarg) {pblock=(IParamBlock*)rtarg;}

	int NumSubs() {return 1;}
	Animatable* SubAnim(int i) {return GetReference(i);}
	TSTR SubAnimName(int i) {return _T("");}

	RefResult NotifyRefChanged( Interval changeInt,RefTargetHandle hTarget, 
	   PartID& partID, RefMessage message);
};


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

IObjParam *TessMod::ip = NULL;
IParamMap *TessMod::pmapParam = NULL;
TessMod   *TessMod::editMod = NULL;
int TessMod::LastFaceType = DEF_FACE_TYPE;
int TessMod::LastType = DEF_TESS_TYPE;

static HIMAGELIST hFaceImages = NULL;
static void LoadImages() {
	if (hFaceImages) return;
	HBITMAP hBitmap, hMask;
	hFaceImages = ImageList_Create(17, 15, ILC_MASK, 3, 0);	// "17" is kluge to get square centered -SA 9706
	hBitmap     = LoadBitmap (hInstance,MAKEINTRESOURCE(IDB_FACESELTYPES));
	hMask       = LoadBitmap (hInstance,MAKEINTRESOURCE(IDB_MASK_FACESELTYPES));
	ImageList_Add(hFaceImages, hBitmap, hMask);
	DeleteObject(hBitmap);
	DeleteObject(hMask);
}

class TessClassDesc:public ClassDesc {
public:
	int 			IsPublic() { return 1; }
	void *			Create(BOOL loading = FALSE) { return new TessMod; }
	const TCHAR *	ClassName() { return GetString(IDS_RB_TESSMOD); }
	SClass_ID		SuperClassID() { return OSM_CLASS_ID; }
	Class_ID		ClassID() { return Class_ID(TESSELLATE_CLASS_ID,0); }
	const TCHAR* 	Category() { return GetString(IDS_RB_DEFDEFORMATIONS);}
	void ResetClassParams (BOOL fileReset);
};

void TessClassDesc::ResetClassParams (BOOL fileReset) {
	TessMod::LastType = DEF_TESS_TYPE;
	TessMod::LastFaceType = DEF_FACE_TYPE;
}
static TessClassDesc tessDesc;
ClassDesc* GetTessModDesc() {return &tessDesc;}

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

#define PB_TYPE			0
#define PB_TENSION		1
#define PB_ITERATIONS	2
#define PB_FACE_TYPE	3

//
//
// Parameters

static int typeIDs[] = {IDC_TES_EDGE,IDC_TES_CENTER};
static int iterationsIDs[] = {IDC_ITERATIONS1,IDC_ITERATIONS2,IDC_ITERATIONS3,IDC_ITERATIONS4};

static ParamUIDesc descParam[] = {
	// Type
	ParamUIDesc(PB_TYPE,TYPE_RADIO,typeIDs,2),
	// Falloff
	ParamUIDesc(
		PB_TENSION,
		EDITTYPE_FLOAT,
		IDC_TENSION,IDC_TENSIONSPIN,
		-100.0f,100.0f,
		0.1f),

	// Iterations
	ParamUIDesc(PB_ITERATIONS,TYPE_RADIO,iterationsIDs,4),
};
#define PARAMDESC_LENGTH	3

static ParamBlockDescID descVer0[] = {
	{ TYPE_INT,   NULL, FALSE, 0 },		// Type
	{ TYPE_FLOAT, NULL, TRUE,  1 },		// Tension	
	{ TYPE_INT,   NULL, FALSE, 2 },		// Iterations
};
static ParamBlockDescID descVer1[] = {
	{ TYPE_INT,   NULL, FALSE, 0 },		// Type
	{ TYPE_FLOAT, NULL, TRUE,  1 },		// Tension	
	{ TYPE_INT,   NULL, FALSE, 2 },		// Iterations
	{ TYPE_INT,   NULL, FALSE, 3 }		// Face type
};
#define PBLOCK_LENGTH	4

// Array of old versions
static ParamVersionDesc versions[] = {
	ParamVersionDesc(descVer0,3,0),
};
#define NUM_OLDVERSIONS	1

#define CURRENT_VERSION 1
static ParamVersionDesc curVersion (descVer1, PBLOCK_LENGTH, CURRENT_VERSION);

// Undo/redo for tri/poly buttons:
class TriPolyRestore : public RestoreObj {
public:
	int was_poly;
	TessMod * tm;

	TriPolyRestore (TessMod *mod);
	void Restore (int isUndo);
	void Redo () { Restore (FALSE); }
	int Size () { return (sizeof(TessMod *) + sizeof (int)); }
	TSTR Description() { return TSTR(_T("TessMod Tri/Poly toggle")); }
};

TriPolyRestore::TriPolyRestore (TessMod *mod) {
	tm = mod;
	assert (tm);
	assert (tm->pblock);
	tm->pblock->GetValue (PB_FACE_TYPE, TimeValue(0), was_poly, FOREVER);
}

void TriPolyRestore::Restore (int isUndo) {
	if (!tm) return;
	if (!tm->pmapParam) return;
	ICustButton *iTri, *iPoly;
	iTri = GetICustButton (GetDlgItem (tm->pmapParam->GetHWnd(), IDC_TES_FACE_TRI));
	iPoly = GetICustButton (GetDlgItem (tm->pmapParam->GetHWnd(), IDC_TES_FACE_POLY));
	iTri->SetCheck (!was_poly);
	iPoly->SetCheck (was_poly);
	ReleaseICustButton (iTri);
	ReleaseICustButton (iPoly);
	was_poly = !was_poly;
}

// Face-type dlg user proc
class TessModDlgProc : public ParamMapUserDlgProc {
public:
	TessMod *mod;
	TessModDlgProc () { mod = NULL; }
	BOOL DlgProc (TimeValue t, IParamMap *map, HWND hWnd,
		UINT msg, WPARAM wParam, LPARAM lParam);
	void DeleteThis () { }
};

BOOL TessModDlgProc::DlgProc (TimeValue t, IParamMap *map, HWND hWnd,
							  UINT msg,WPARAM wParam,LPARAM lParam) {
	if (mod==NULL) return FALSE;
	if (msg != WM_COMMAND) return FALSE;

	ICustButton *iTri, *iPoly;
	switch (LOWORD (wParam)) {
	case IDC_TES_FACE_TRI:
		theHold.Begin ();
		theHold.Put (new TriPolyRestore (mod));
		iTri = GetICustButton (GetDlgItem (hWnd, IDC_TES_FACE_TRI));
		iPoly = GetICustButton (GetDlgItem (hWnd, IDC_TES_FACE_POLY));
		mod->pblock->SetValue (PB_FACE_TYPE, t, 0);
		iTri->SetCheck (1);
		iPoly->SetCheck (0);
		ReleaseICustButton (iTri);
		ReleaseICustButton (iPoly);
		theHold.Accept (GetString (IDS_SA_TRI_POLY_RESTORE));
		break;
	case IDC_TES_FACE_POLY:
		theHold.Begin ();
		theHold.Put (new TriPolyRestore (mod));
		iTri = GetICustButton (GetDlgItem (hWnd, IDC_TES_FACE_TRI));
		iPoly = GetICustButton (GetDlgItem (hWnd, IDC_TES_FACE_POLY));
		mod->pblock->SetValue (PB_FACE_TYPE, t, 1);
		iTri->SetCheck (0);
		iPoly->SetCheck (1);
		ReleaseICustButton (iTri);
		ReleaseICustButton (iPoly);
		theHold.Accept (GetString (IDS_SA_TRI_POLY_RESTORE));
		break;
	}
	return FALSE;
}

static TessModDlgProc theTessModDlgProc;

//--- tessellate mod methods -------------------------------

TessMod::TessMod()
{
	pblock = NULL;
	MakeRefByID (FOREVER, 0,
		CreateParameterBlock(descVer1, PBLOCK_LENGTH, CURRENT_VERSION));
	pblock->SetValue (PB_TENSION, 0, DEF_TENSION);
	pblock->SetValue (PB_TYPE, 0, LastType);
	pblock->SetValue (PB_ITERATIONS, 0, DEF_ITERS);		// "1" iteration
	pblock->SetValue (PB_FACE_TYPE, 0, LastFaceType);
	LoadImages();
}

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

	TimeValue t = ip->GetTime();
	NotifyDependents(Interval(t,t), PART_ALL, REFMSG_BEGIN_EDIT);
	SetAFlag(A_MOD_BEING_EDITED);

	pmapParam = CreateCPParamMap (descParam,PARAMDESC_LENGTH,
		pblock, ip, hInstance, MAKEINTRESOURCE(IDD_TESSPARAM),
		GetString(IDS_RB_PARAMETERS), 0);
	theTessModDlgProc.mod = this;
	pmapParam->SetUserDlgProc (&theTessModDlgProc);

	int ft;
	pblock->GetValue (PB_FACE_TYPE, TimeValue(0), ft, FOREVER);
	HWND hParam = pmapParam->GetHWnd();
	ICustButton *iFaceTri = GetICustButton (GetDlgItem (hParam, IDC_TES_FACE_TRI));
	iFaceTri->SetImage(hFaceImages, 0, 0, 0, 0, 16, 15);
	iFaceTri->SetType (CBT_CHECK);
	iFaceTri->SetCheck (!ft);
	iFaceTri->SetTooltip (TRUE, GetString (IDS_SA_TRIANGLE));
	ReleaseICustButton (iFaceTri);

	ICustButton *iFacePoly = GetICustButton (GetDlgItem (hParam, IDC_TES_FACE_POLY));
	iFacePoly->SetImage(hFaceImages, 1, 1, 1, 1, 16, 15);
	iFacePoly->SetType (CBT_CHECK);
	iFacePoly->SetCheck (ft);
	iFacePoly->SetTooltip (TRUE, GetString (IDS_SA_POLYGON));
	ReleaseICustButton (iFacePoly);
}

void TessMod::EndEditParams (IObjParam *ip,ULONG flags,Animatable *next) {
	this->ip = NULL;
	editMod  = NULL;

	TimeValue t = ip->GetTime();

	// NOTE: This flag must be cleared before sending the REFMSG_END_EDIT
	ClearAFlag(A_MOD_BEING_EDITED);
	NotifyDependents(Interval(t,t), PART_ALL, REFMSG_END_EDIT);	

	DestroyCPParamMap(pmapParam);
	theTessModDlgProc.mod = NULL;
	pblock->GetValue (PB_FACE_TYPE, t, LastFaceType, FOREVER);
	pblock->GetValue (PB_TYPE, t, LastType, FOREVER);
}

RefTargetHandle TessMod::Clone(RemapDir& remap) {
	TessMod *mod = new TessMod();
	mod->ReplaceReference(0,pblock->Clone(remap));
	return mod;
}

IOResult TessMod::Load (ILoad *iload) {
	Modifier::Load(iload);
	// Add following when there's a new pblock version.
	iload->RegisterPostLoadCallback(
		new ParamBlockPLCB(versions,NUM_OLDVERSIONS,&curVersion,this,0));
	return IO_OK;
}

void TessMod::ModifyObject (TimeValue t, ModContext &mc, ObjectState *os, INode *node) {
	if (!os->obj->IsSubClassOf (triObjectClassID)) return;

	int type, iterations, faceType;
	float tens;
	BOOL ignoreSel = FALSE;
	Interval iv = os->obj->ChannelValidity (t, GEOM_CHAN_NUM);
	iv &= os->obj->ChannelValidity (t, TOPO_CHAN_NUM);
	iv &= os->obj->ChannelValidity (t, SELECT_CHAN_NUM);
	iv &= os->obj->ChannelValidity (t, SUBSEL_TYPE_CHAN_NUM);
	iv &= os->obj->ChannelValidity (t, TEXMAP_CHAN_NUM);
	iv &= os->obj->ChannelValidity (t, VERT_COLOR_CHAN_NUM);

	pblock->GetValue(PB_TYPE,t,type,iv);
	pblock->GetValue(PB_ITERATIONS,t,iterations,iv);
	iterations++;
	pblock->GetValue (PB_FACE_TYPE, t, faceType, iv);

	TriObject *tobj = (TriObject*)os->obj;
	if (faceType == 0) {
		if (tobj->mesh.selLevel != MESH_FACE) ignoreSel = TRUE;
		if (type) {
			for (int i=0; i<iterations; i++) tobj->mesh.FaceCenterTessellate(ignoreSel);
		} else {
			pblock->GetValue(PB_TENSION,t,tens,iv);
			for (int i=0; i<iterations; i++) tobj->mesh.EdgeTessellate(tens/400.0f,ignoreSel);
		}
	} else {
		Mesh & msh = tobj->Mesh();
		MNMesh mm(msh);
		mm.MakePolyMesh ();
		mm.MakeConvex ();
		mm.TargetFacesBySelection (msh.selLevel);
		if (type) {
			for (int i=0; i<iterations; i++) mm.TessellateByCenters ();
		} else {
			pblock->GetValue(PB_TENSION,t,tens,iv);
			tens /= 400.0f;
			for (int i=0; i<iterations; i++) mm.TessellateByEdges (tens);
		}
		mm.OutToTri (msh);
	}
	os->obj->UpdateValidity (GEOM_CHAN_NUM, iv);
	os->obj->UpdateValidity (TOPO_CHAN_NUM, iv);
	os->obj->UpdateValidity (SELECT_CHAN_NUM, iv);
	os->obj->UpdateValidity (SUBSEL_TYPE_CHAN_NUM, iv);
	os->obj->UpdateValidity (TEXMAP_CHAN_NUM, iv);
	os->obj->UpdateValidity (VERT_COLOR_CHAN_NUM, iv);
}

Interval TessMod::LocalValidity(TimeValue t) {
	Interval iv = FOREVER;
	int type, iterations, faceType;
	float tens;
	pblock->GetValue(PB_TYPE,t,type,iv);	
	pblock->GetValue(PB_ITERATIONS,t,iterations,iv);
	pblock->GetValue (PB_FACE_TYPE, t, faceType, iv);
	if (!type) pblock->GetValue(PB_TENSION,t,tens,iv);
	return iv;
}

RefResult TessMod::NotifyRefChanged (Interval changeInt,RefTargetHandle hTarget,
									 PartID& partID, RefMessage message) {
	int type;
	switch (message) {
		case REFMSG_CHANGE:
			if (editMod==this && pmapParam) {
				pblock->GetValue (PB_TYPE, TimeValue(0), type, FOREVER);
				ISpinnerControl *spin;
				spin = GetISpinner (GetDlgItem (pmapParam->GetHWnd(), IDC_TENSIONSPIN));
				spin->Enable (!type);
				ReleaseISpinner (spin);
				pmapParam->Invalidate();
			}
			break;

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

		case REFMSG_GET_PARAM_NAME: {
			GetParamName *gpn = (GetParamName*)partID;			
			switch (gpn->index) {
				case PB_TENSION: gpn->name = GetString(IDS_TESS_TENSION); break;				
			}
			return REF_STOP; 
		}
	}
	return REF_SUCCEED;
}

