/**********************************************************************
 *<
	FILE: gravity.cpp

	DESCRIPTION:  Gravity and Wind World Space Modifier

	CREATED BY: Rolf Berteig

	HISTORY: 10-30-95

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

#define FORCE_PLANAR	0
#define FORCE_SPHERICAL	1


class ForceObject : public SimpleWSMObject {	
	public:		
		static IParamMap *pmapParam;
		static IObjParam *ip;
		static HWND hSot;
					
		ForceObject() {}
		BOOL SupportsDynamics() {return TRUE;}

		// From Animatable		
		void DeleteThis() {delete this;}		
		void BeginEditParams( IObjParam  *ip, ULONG flags,Animatable *prev );
		void EndEditParams( IObjParam *ip, ULONG flags,Animatable *next);		
				
		// from object		
		CreateMouseCallBack* GetCreateMouseCallBack();		
		
		// From SimpleWSMObject		
		void InvalidateUI();		
		void BuildMesh(TimeValue t);

		virtual int DialogID()=0;
		virtual ParamUIDesc *UIDesc()=0;
		virtual int UIDescLength()=0;
	};
IObjParam *ForceObject::ip        = NULL;
IParamMap *ForceObject::pmapParam = NULL;
HWND       ForceObject::hSot      = NULL;

class GravityObject : public ForceObject {	
	public:									
		GravityObject();		

		// From Animatable		
		void DeleteThis() {delete this;}		
		Class_ID ClassID() {return Class_ID(GRAVITYOBJECT_CLASS_ID,0);}		
		RefTargetHandle Clone(RemapDir& remap = NoRemap());
		TCHAR *GetObjectName() {return GetString(IDS_RB_GRAVITY);}
						
		// From WSMObject
		Modifier *CreateWSMMod(INode *node);
		ForceField *GetForceField(INode *node);

		// From SimpleWSMObject				
		ParamDimension *GetParameterDim(int pbIndex);
		TSTR GetParameterName(int pbIndex);

		int DialogID() {return IDD_GRAVITYPARAM;}
		ParamUIDesc *UIDesc();
		int UIDescLength();
	};

class WindObject : public ForceObject {	
	public:									
		WindObject();		

		// From Animatable		
		void DeleteThis() {delete this;}		
		Class_ID ClassID() {return Class_ID(WINDOBJECT_CLASS_ID,0);}		
		RefTargetHandle Clone(RemapDir& remap = NoRemap());
		TCHAR *GetObjectName() {return GetString(IDS_RB_WIND);}
						
		// From WSMObject
		Modifier *CreateWSMMod(INode *node);
		ForceField *GetForceField(INode *node);
		
		// From SimpleWSMObject				
		ParamDimension *GetParameterDim(int pbIndex);
		TSTR GetParameterName(int pbIndex);

		int DialogID() {return IDD_WINDPARAM;}
		ParamUIDesc *UIDesc();
		int UIDescLength();
	};

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

class GravityClassDesc:public ClassDesc {
	public:
	int 			IsPublic() {return 1;}
	void *			Create(BOOL loading = FALSE) { return new GravityObject;}
	const TCHAR *	ClassName() {return GetString(IDS_RB_GRAVITY_CLASS);}
	SClass_ID		SuperClassID() {return WSM_OBJECT_CLASS_ID; }
	Class_ID		ClassID() {return Class_ID(GRAVITYOBJECT_CLASS_ID,0);}
	const TCHAR* 	Category() {return GetSpaceWarpCatString(SPACEWARP_CAT_PARTICLE);}
	};

static GravityClassDesc gravityDesc;
ClassDesc* GetGravityObjDesc() {return &gravityDesc;}


class WindClassDesc:public ClassDesc {
	public:
	int 			IsPublic() {return 1;}
	void *			Create(BOOL loading = FALSE) { return new WindObject;}
	const TCHAR *	ClassName() {return GetString(IDS_RB_WIND_CLASS);}
	SClass_ID		SuperClassID() {return WSM_OBJECT_CLASS_ID; }
	Class_ID		ClassID() {return Class_ID(WINDOBJECT_CLASS_ID,0);}
	const TCHAR* 	Category() {return GetSpaceWarpCatString(SPACEWARP_CAT_PARTICLE);}
	};

static WindClassDesc windDesc;
ClassDesc* GetWindObjDesc() {return &windDesc;}


//--- GravityMod -----------------------------------------------------

class GravityMod;

class GravityField : public ForceField {
	public:
		GravityObject *obj;
		INode *node;
		Matrix3 tm;
		Interval tmValid;
		Point3 force;
		Interval fValid;
		int type;
		Point3 Force(TimeValue t,const Point3 &pos, const Point3 &vel,int index);
	};

class GravityMod : public SimpleWSMMod {
	public:				
		GravityField force;

		GravityMod() {}
		GravityMod(INode *node,GravityObject *obj);		

		// From Animatable
		void GetClassName(TSTR& s) {s= GetString(IDS_RB_GRAVITYMOD);}
		SClass_ID SuperClassID() {return WSM_CLASS_ID;}
		void DeleteThis() {delete this;}
		Class_ID ClassID() { return Class_ID(GRAVITYMOD_CLASS_ID,0);}
		RefTargetHandle Clone(RemapDir& remap = NoRemap());
		TCHAR *GetObjectName() {return GetString(IDS_RB_GRAVITYBINDING);}

		void ModifyObject(TimeValue t, ModContext &mc, ObjectState *os, INode *node);

		// From SimpleWSMMod		
		Interval GetValidity(TimeValue t);		
		Deformer& GetDeformer(TimeValue t,ModContext &mc,Matrix3& mat,Matrix3& invmat);
	};

//--- WindMod -----------------------------------------------------

class WindMod;

class WindField : public ForceField {
	public:
		WindObject *obj;
		INode *node;
		Matrix3 tm;
		Interval tmValid;
		Point3 force;
		Interval fValid;
		int type;
		Point3 Force(TimeValue t,const Point3 &pos, const Point3 &vel,int index);
	};

class WindMod : public SimpleWSMMod {
	public:				
		WindField force;

		WindMod() {}
		WindMod(INode *node,WindObject *obj);		

		// From Animatable
		void GetClassName(TSTR& s) {s= GetString(IDS_RB_WINDMOD);}
		SClass_ID SuperClassID() {return WSM_CLASS_ID;}
		void DeleteThis() {delete this;}
		Class_ID ClassID() { return Class_ID(WINDMOD_CLASS_ID,0);}
		RefTargetHandle Clone(RemapDir& remap = NoRemap());
		TCHAR *GetObjectName() {return GetString(IDS_RB_WINDBINDING);}

		void ModifyObject(TimeValue t, ModContext &mc, ObjectState *os, INode *node);

		// From SimpleWSMMod		
		Interval GetValidity(TimeValue t);		
		Deformer& GetDeformer(TimeValue t,ModContext &mc,Matrix3& mat,Matrix3& invmat);
	};

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

class GravityModClassDesc:public ClassDesc {
	public:
	int 			IsPublic() { return 0; }
	void *			Create(BOOL loading = FALSE) {return new GravityMod;}
	const TCHAR *	ClassName() { return GetString(IDS_RB_GRAVITYMOD);}
	SClass_ID		SuperClassID() {return WSM_CLASS_ID;}
	Class_ID		ClassID() {return Class_ID(GRAVITYMOD_CLASS_ID,0);}
	const TCHAR* 	Category() {return _T("");}
	};

static GravityModClassDesc gravityModDesc;
ClassDesc* GetGravityModDesc() {return &gravityModDesc;}

class WindModClassDesc:public ClassDesc {
	public:
	int 			IsPublic() { return 0; }
	void *			Create(BOOL loading = FALSE) {return new WindMod;}
	const TCHAR *	ClassName() { return GetString(IDS_RB_WINDMOD);}
	SClass_ID		SuperClassID() {return WSM_CLASS_ID;}
	Class_ID		ClassID() {return Class_ID(WINDMOD_CLASS_ID,0);}
	const TCHAR* 	Category() {return _T("");}
	};

static WindModClassDesc windModDesc;
ClassDesc* GetWindModDesc() {return &windModDesc;}


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

#define PB_STRENGTH		0
#define PB_DECAY		1
#define PB_TYPE			2
#define PB_DISPLENGTH	3

//
//
// Parameters

static int typeIDs[] = {IDC_FORCE_PLANAR,IDC_FORCE_SPHERICAL};

static ParamUIDesc descParamGrav[] = {
	// Strength
	ParamUIDesc(
		PB_STRENGTH,
		EDITTYPE_FLOAT,
		IDC_GRAV_STRENGTH,IDC_GRAV_STRENGTHSPIN,
		-9999999.0f, 9999999.0f,
		0.01f),

	// Decay
	ParamUIDesc(
		PB_DECAY,
		EDITTYPE_FLOAT,
		IDC_GRAV_DECAY,IDC_GRAV_DECAYSPIN,
		0.0f, 9999999.0f,
		0.001f),

	// Force type
	ParamUIDesc(PB_TYPE,TYPE_RADIO,typeIDs,2),

	// Display length
	ParamUIDesc(
		PB_DISPLENGTH,
		EDITTYPE_FLOAT,
		IDC_DISPLENGTH,IDC_DISPLENGTHSPIN,
		0.0f, 9999999.0f,
		SPIN_AUTOSCALE),
	};
#define GRAVPARAMDESC_LENGTH	4

ParamBlockDescID descGravVer0[] = {
	{ TYPE_FLOAT, NULL, TRUE, 0 },
	{ TYPE_FLOAT, NULL, TRUE, 1 },
	{ TYPE_INT, NULL, FALSE, 2 },
	{ TYPE_FLOAT, NULL, FALSE, 3 }};
#define GRAVPBLOCK_LENGTH	4

#define CURRENT_GRAVVERSION	0


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

#define PB_TURBULENCE	4
#define PB_FREQUENCY	5
#define PB_SCALE		6

//
//
// Parameters

static ParamUIDesc descParamWind[] = {
	// Strength
	ParamUIDesc(
		PB_STRENGTH,
		EDITTYPE_FLOAT,
		IDC_WIND_STRENGTH,IDC_WIND_STRENGTHSPIN,
		-9999999.0f, 9999999.0f,
		0.01f),

	// Decay
	ParamUIDesc(
		PB_DECAY,
		EDITTYPE_FLOAT,
		IDC_WIND_DECAY,IDC_WIND_DECAYSPIN,
		0.0f, 9999999.0f,
		0.001f),

	// Force type
	ParamUIDesc(PB_TYPE,TYPE_RADIO,typeIDs,2),

	// Turbulence
	ParamUIDesc(
		PB_TURBULENCE,
		EDITTYPE_FLOAT,
		IDC_WIND_TURB,IDC_WIND_TURBSPIN,
		0.0f, 9999999.0f,
		0.01f),

	// Frequency
	ParamUIDesc(
		PB_FREQUENCY,
		EDITTYPE_FLOAT,
		IDC_WIND_FREQ,IDC_WIND_FREQSPIN,
		0.0f, 9999999.0f,
		0.01f),

	// Scale
	ParamUIDesc(
		PB_SCALE,
		EDITTYPE_FLOAT,
		IDC_WIND_SCALE,IDC_WIND_SCALESPIN,
		0.0f, 9999999.0f,
		0.01f),

	// Display length
	ParamUIDesc(
		PB_DISPLENGTH,
		EDITTYPE_FLOAT,
		IDC_DISPLENGTH,IDC_DISPLENGTHSPIN,
		0.0f, 9999999.0f,
		SPIN_AUTOSCALE),
	};
#define WINDPARAMDESC_LENGTH	7

ParamBlockDescID descWindVer0[] = {
	{ TYPE_FLOAT, NULL, TRUE, 0 },
	{ TYPE_FLOAT, NULL, TRUE, 1 },
	{ TYPE_INT, NULL, FALSE, 2 },
	{ TYPE_FLOAT, NULL, FALSE, 3 },
	{ TYPE_FLOAT, NULL, TRUE, 4 },
	{ TYPE_FLOAT, NULL, TRUE, 5 },
	{ TYPE_FLOAT, NULL, TRUE, 6 }};
#define WINDPBLOCK_LENGTH	7

#define CURRENT_WINDVERSION	0


//--- ForceObject Methods ---------------------------------------------

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

	if (pmapParam) {		
		// Left over from last SinWave ceated
		pmapParam->SetParamBlock(pblock);
	} else {		
		hSot = ip->AddRollupPage( 
			hInstance, 
			MAKEINTRESOURCE(IDD_WINDGRAV_SOT),
			DefaultSOTProc,
			GetString(IDS_RB_SOT), 
			(LPARAM)ip,APPENDROLL_CLOSED);

		// Gotta make a new one.
		pmapParam = CreateCPParamMap(
			UIDesc(),UIDescLength(),
			pblock,
			ip,
			hInstance,
			MAKEINTRESOURCE(DialogID()),
			GetString(IDS_RB_PARAMETERS),
			0);
		}
	}

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

	if (flags&END_EDIT_REMOVEUI ) {		
		DestroyCPParamMap(pmapParam);
		ip->DeleteRollupPage(hSot);
		pmapParam = NULL;		
		}	
	}


void ForceObject::BuildMesh(TimeValue t)
	{
	ivalid = FOREVER;
	float length;
	int type;	
	pblock->GetValue(PB_DISPLENGTH,t,length,ivalid);
	pblock->GetValue(PB_TYPE,t,type,ivalid);

	if (type==FORCE_PLANAR) {
		mesh.setNumVerts(11);
		mesh.setNumFaces(5);
		mesh.setVert(0, Point3(-length,-length, 0.0f));
		mesh.setVert(1, Point3( length,-length, 0.0f));
		mesh.setVert(2, Point3( length, length, 0.0f));
		mesh.setVert(3, Point3(-length, length, 0.0f));
		mesh.setVert(4, Point3(   0.0f,   0.0f, 0.0f));
		mesh.setVert(5, Point3(   0.0f,   0.0f, 3*length));
		mesh.setVert(6, Point3(   0.0f,   0.0f, 0.0f));
		length *= 0.3f;
		mesh.setVert(7, Point3(-length,-length, 7.0f*length));
		mesh.setVert(8, Point3( length,-length, 7.0f*length));
		mesh.setVert(9, Point3( length, length, 7.0f*length));
		mesh.setVert(10, Point3(-length, length, 7.0f*length));

		mesh.faces[0].setEdgeVisFlags(1,0,1);
		mesh.faces[0].setSmGroup(1);
		mesh.faces[0].setVerts(0,1,3);

		mesh.faces[1].setEdgeVisFlags(1,1,0);
		mesh.faces[1].setSmGroup(1);
		mesh.faces[1].setVerts(1,2,3);

		mesh.faces[2].setEdgeVisFlags(1,1,0);
		mesh.faces[2].setSmGroup(1);
		mesh.faces[2].setVerts(4,5,6);

		mesh.faces[3].setEdgeVisFlags(0,1,1);
		mesh.faces[3].setSmGroup(1);
		mesh.faces[3].setVerts(7,9,5);

		mesh.faces[4].setEdgeVisFlags(0,1,1);
		mesh.faces[4].setSmGroup(1);
		mesh.faces[4].setVerts(10,8,5);
	} else {
		float u;
		#define NUM_SEGS	16

		mesh.setNumVerts(3*NUM_SEGS+1);
		mesh.setNumFaces(3*NUM_SEGS);

		for (int i=0; i<NUM_SEGS; i++) {
			u = float(i)/float(NUM_SEGS) * TWOPI;
			mesh.setVert(i, Point3((float)cos(u) * length, (float)sin(u) * length, 0.0f));
			}
		for (i=0; i<NUM_SEGS; i++) {
			u = float(i)/float(NUM_SEGS) * TWOPI;
			mesh.setVert(i+NUM_SEGS, Point3(0.0f, (float)cos(u) * length, (float)sin(u) * length));
			}
		for (i=0; i<NUM_SEGS; i++) {
			u = float(i)/float(NUM_SEGS) * TWOPI;
			mesh.setVert(i+2*NUM_SEGS, Point3((float)cos(u) * length, 0.0f, (float)sin(u) * length));
			}		
		mesh.setVert(3*NUM_SEGS, Point3(0.0f, 0.0f, 0.0f));
		
		for (i=0; i<3*NUM_SEGS; i++) {
			int i1 = i+1;
			if (i1%NUM_SEGS==0) i1 -= NUM_SEGS;
			mesh.faces[i].setEdgeVisFlags(1,0,0);
			mesh.faces[i].setSmGroup(1);
			mesh.faces[i].setVerts(i,i1,3*NUM_SEGS);
			}
		}

	mesh.InvalidateGeomCache();
	}

class ForceObjCreateCallback : public CreateMouseCallBack {
	public:
		ForceObject *ob;	
		Point3 p0, p1;
		IPoint2 sp0;
		int proc( ViewExp *vpt,int msg, int point, int flags, IPoint2 m, Matrix3& mat);
	};

int ForceObjCreateCallback::proc(
		ViewExp *vpt,int msg, int point, int flags, IPoint2 m, Matrix3& mat)
	{
	if (msg==MOUSE_POINT||msg==MOUSE_MOVE) {
		switch(point) {
			case 0:								
				sp0 = m;
				p0  = vpt->GetPointOnCP(m);
				mat.SetTrans(p0);
				break;
			case 1:
				if (ob->ClassID()==Class_ID(GRAVITYOBJECT_CLASS_ID,0)) {
					mat.IdentityMatrix();
					mat.RotateX(PI);
					mat.SetTrans(p0);
					}
				p1  = vpt->GetPointOnCP(m);
				ob->pblock->SetValue(PB_DISPLENGTH,0,Length(p1-p0)/2.0f);
				ob->pmapParam->Invalidate();
				if (msg==MOUSE_POINT) {
					if (Length(m-sp0)<3) return CREATE_ABORT;
					else return CREATE_STOP;
					}
				break;
			}
	} else {
		if (msg == MOUSE_ABORT)
			return CREATE_ABORT;
		}
	
	return TRUE;
	}
static ForceObjCreateCallback forceCreateCB;

CreateMouseCallBack* ForceObject::GetCreateMouseCallBack()
	{
	forceCreateCB.ob = this;
	return &forceCreateCB;
	}

void ForceObject::InvalidateUI() 
	{
	if (pmapParam) pmapParam->Invalidate();
	}




//--- GravityObject methods ---------------------------------------


GravityObject::GravityObject()
	{
	MakeRefByID(FOREVER, 0, 
		CreateParameterBlock(descGravVer0, GRAVPBLOCK_LENGTH, CURRENT_GRAVVERSION));
	assert(pblock);	

	pblock->SetValue(PB_STRENGTH,0,1.0f);
	}

Modifier *GravityObject::CreateWSMMod(INode *node)
	{
	return new GravityMod(node,this);
	}

ForceField *GravityObject::GetForceField(INode *node)
	{
	GravityField *gf = new GravityField;	
	gf->obj  = this;
	gf->node = node;
	gf->tmValid.SetEmpty();
	gf->fValid.SetEmpty();
	gf->obj->pblock->GetValue(PB_TYPE,0,gf->type,FOREVER);
	return gf;
	}

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

ParamDimension *GravityObject::GetParameterDim(int pbIndex) 
	{
	switch (pbIndex) {		
		case 0:
		default: return defaultDim;
		}
	}


TSTR GravityObject::GetParameterName(int pbIndex) 
	{
	switch (pbIndex) {				
		case PB_STRENGTH: 	return GetString(IDS_RB_STRENGTH2);
		case PB_DECAY:		return GetString(IDS_RB_DECAY);
		default: 			return TSTR(_T(""));
		}
	}

ParamUIDesc *GravityObject::UIDesc()
	{
	return descParamGrav;
	}

int GravityObject::UIDescLength()
	{
	return GRAVPARAMDESC_LENGTH;
	}


//--- WindObject methods ---------------------------------------


WindObject::WindObject()
	{
	MakeRefByID(FOREVER, 0, 
		CreateParameterBlock(descWindVer0, WINDPBLOCK_LENGTH, CURRENT_WINDVERSION));
	assert(pblock);	

	pblock->SetValue(PB_STRENGTH,0,1.0f);
	pblock->SetValue(PB_SCALE,0,1.0f);
	}

Modifier *WindObject::CreateWSMMod(INode *node)
	{
	return new WindMod(node,this);
	}

ForceField *WindObject::GetForceField(INode *node)
	{
	WindField *wf = new WindField;	
	wf->obj  = this;
	wf->node = node;
	wf->tmValid.SetEmpty();
	wf->fValid.SetEmpty();
	wf->obj->pblock->GetValue(PB_TYPE,0,wf->type,FOREVER);
	return wf;
	}

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

ParamDimension *WindObject::GetParameterDim(int pbIndex) 
	{
	switch (pbIndex) {		
		case 0:
		default: return defaultDim;
		}
	}

TSTR WindObject::GetParameterName(int pbIndex) 
	{
	switch (pbIndex) {		
		case PB_STRENGTH: 	return GetString(IDS_RB_STRENGTH2);
		case PB_DECAY:		return GetString(IDS_RB_DECAY);
		case PB_TURBULENCE:	return GetString(IDS_RB_TURBULENCE);
		case PB_FREQUENCY:	return GetString(IDS_RB_FREQUENCY);
		case PB_SCALE:		return GetString(IDS_RB_SCALE);
		default: 			return TSTR(_T(""));
		}
	}

ParamUIDesc *WindObject::UIDesc()
	{
	return descParamWind;
	}

int WindObject::UIDescLength()
	{
	return WINDPARAMDESC_LENGTH;
	}

//--- GravityMod methods -----------------------------------------


GravityMod::GravityMod(INode *node,GravityObject *obj)
	{	
	//MakeRefByID(FOREVER,SIMPWSMMOD_OBREF,obj);
	MakeRefByID(FOREVER,SIMPWSMMOD_NODEREF,node);	
	pblock = NULL;
	obRef = NULL;
	}

Interval GravityMod::GetValidity(TimeValue t) 
	{
	if (nodeRef) {
		Interval valid = FOREVER;
		Matrix3 tm;
		float f;		
		((GravityObject*)GetWSMObject(t))->pblock->GetValue(PB_STRENGTH,t,f,valid);
		tm = nodeRef->GetObjectTM(t,&valid);
		return valid;
	} else {
		return FOREVER;
		}
	}

class GravityDeformer : public Deformer {
	public:		
		Point3 Map(int i, Point3 p) {return p;}
	};
static GravityDeformer gdeformer;

Deformer& GravityMod::GetDeformer(
		TimeValue t,ModContext &mc,Matrix3& mat,Matrix3& invmat)
	{
	return gdeformer;
	}

RefTargetHandle GravityMod::Clone(RemapDir& remap) 
	{
	GravityMod *newob = new GravityMod(nodeRef,(GravityObject*)obRef);	
	newob->SimpleWSMModClone(this);
	return newob;
	}

void GravityMod::ModifyObject(
		TimeValue t, ModContext &mc, ObjectState *os, INode *node)
	{
	ParticleObject *obj = GetParticleInterface(os->obj);
	if (obj) {
		force.obj  = (GravityObject*)GetWSMObject(t);
		force.node = nodeRef;
		force.tmValid.SetEmpty();
		force.fValid.SetEmpty();
		force.obj->pblock->GetValue(PB_TYPE,t,force.type,FOREVER);
		
		obj->ApplyForceField(&force);
		}
	}

// This is an adjustment to forces to make the independent of time scale.
// They were previously dependent on the old 1200 ticks per second constant.
// Note that the constants are being squared because we are dealing with acceleration not velocity.
static float forceScaleFactor = float(1200*1200)/float(TIME_TICKSPERSEC*TIME_TICKSPERSEC);

Point3 GravityField::Force(
		TimeValue t,const Point3 &pos, const Point3 &vel,int index)
	{	
	float strength, decay;
	obj->pblock->GetValue(PB_DECAY,t,decay,fValid);
	if (decay <0.0f) decay = 0.0f;

	if (!fValid.InInterval(t) || type==FORCE_SPHERICAL || decay!=0.0f) {
		fValid = FOREVER;		
		if (!tmValid.InInterval(t)) {
			tmValid = FOREVER;
			tm = node->GetObjectTM(t,&tmValid);
			}
		fValid &= tmValid;
		obj->pblock->GetValue(PB_STRENGTH,t,strength,fValid);
		
		if (type==FORCE_PLANAR) {
			force = Normalize(tm.GetRow(2));
			if (decay!=0.0f) {
				float dist = DotProd(force,pos-tm.GetTrans());
				strength *= (float)exp(-decay*dist);
				}			
			force *= strength * 0.0001f * forceScaleFactor;
		} else {
			float dist;
			force = tm.GetTrans()-pos;
			dist  = Length(force);
			if (dist!=0.0f) force /= dist;
			if (decay!=0.0f) {				
				strength *= (float)exp(-decay*dist);
				}			
			force *= strength * 0.0001f * forceScaleFactor;
			}
		}	
	return force;
	}


//--- WindMod methods -----------------------------------------


WindMod::WindMod(INode *node,WindObject *obj)
	{	
	//MakeRefByID(FOREVER,SIMPWSMMOD_OBREF,obj);
	MakeRefByID(FOREVER,SIMPWSMMOD_NODEREF,node);	
	pblock = NULL;
	obRef = NULL;
	}

Interval WindMod::GetValidity(TimeValue t) 
	{
	if (nodeRef) {
		Interval valid = FOREVER;
		Matrix3 tm;
		float f;		
		((WindObject*)GetWSMObject(t))->pblock->GetValue(PB_STRENGTH,t,f,valid);		
		tm = nodeRef->GetObjectTM(t,&valid);
		return valid;
	} else {
		return FOREVER;
		}
	}

class WindDeformer : public Deformer {
	public:		
		Point3 Map(int i, Point3 p) {return p;}
	};
static WindDeformer wdeformer;

Deformer& WindMod::GetDeformer(
		TimeValue t,ModContext &mc,Matrix3& mat,Matrix3& invmat)
	{
	return wdeformer;
	}

RefTargetHandle WindMod::Clone(RemapDir& remap) 
	{
	WindMod *newob = new WindMod(nodeRef,(WindObject*)obRef);	
	newob->SimpleWSMModClone(this);
	return newob;
	}

void WindMod::ModifyObject(
		TimeValue t, ModContext &mc, ObjectState *os, INode *node)
	{
	ParticleObject *obj = GetParticleInterface(os->obj);
	if (obj) {
		force.obj  = (WindObject*)GetWSMObject(t);
		force.node = nodeRef;
		force.tmValid.SetEmpty();
		force.fValid.SetEmpty();
		force.obj->pblock->GetValue(PB_TYPE,t,force.type,FOREVER);
		obj->ApplyForceField(&force);
		}
	}

static float RTurbulence(Point3 p,float freq)
	{
	return noise3(p*freq);
	}


Point3 WindField::Force(
		TimeValue t,const Point3 &pos, const Point3 &vel,int index)
	{	
	float strength, decay, turb;
	obj->pblock->GetValue(PB_DECAY,t,decay,fValid);
	obj->pblock->GetValue(PB_TURBULENCE,t,turb,FOREVER);
	if (decay <0.0f) decay = 0.0f;

	if (!fValid.InInterval(t) || type==FORCE_SPHERICAL || decay!=0.0f) {
		fValid = FOREVER;		
		if (!tmValid.InInterval(t)) {
			tmValid = FOREVER;
			tm = node->GetObjectTM(t,&tmValid);
			}
		fValid &= tmValid;
		obj->pblock->GetValue(PB_STRENGTH,t,strength,fValid);
		
		if (type==FORCE_PLANAR) {
			force = Normalize(tm.GetRow(2));
			if (decay!=0.0f) {
				float dist = (float)fabs(DotProd(force,pos-tm.GetTrans()));
				strength *= (float)exp(-decay*dist);
				}			
			force *= strength * 0.0001f * forceScaleFactor;
		} else {
			float dist;
			force = pos-tm.GetTrans();
			dist  = Length(force);
			if (dist!=0.0f) force /= dist;
			if (decay!=0.0f) {				
				strength *= (float)exp(-decay*dist);
				}			
			force *= strength * 0.0001f * forceScaleFactor;
			}
		}	
	if (turb!=0.0f) {
		float freq, scale;
		Point3 tf, pt = pos-tm.GetTrans(), p;
		obj->pblock->GetValue(PB_FREQUENCY,t,freq,FOREVER);
		obj->pblock->GetValue(PB_SCALE,t,scale,FOREVER);
		freq *= 0.01f;
		turb *= 0.0001f * forceScaleFactor;

		p    = pt;
		p.x  = freq * float(t);
		tf.x = RTurbulence(p,scale);
		p    = pt;
		p.y  = freq * float(t);
		tf.y = RTurbulence(p,scale);
		p    = pt;
		p.z  = freq * float(t);
		tf.z = RTurbulence(p,scale);

		return force + (turb*tf);
	} else {
		return force;
		}
	}

