/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include "HeadSpin.h"
#include <windowsx.h>

#include "max.h"
#include "resource.h"
#include "plComponent.h"
#include "plComponentReg.h"
#include "plParticleComponents.h"
#include "plAnimComponent.h"
#include "plNotetrackAnim.h"

#include "../pnSceneObject/plSceneObject.h"
#include "../plScene/plSceneNode.h"
#include "plgDispatch.h"

#include "../MaxConvert/plConvert.h"
#include "../MaxConvert/hsConverterUtils.h"
#include "../MaxConvert/hsMaterialConverter.h"
#include "../MaxConvert/plMeshConverter.h"
#include "../MaxConvert/hsControlConverter.h"
#include "../MaxMain/plMaxNode.h"
#include "../MaxPlasmaMtls/Materials/plParticleMtl.h"

#include "../MaxExport/plErrorMsg.h"

#include "hsResMgr.h"

#include "../pnMessage/plObjRefMsg.h"
#include "../pnMessage/plNodeRefMsg.h"
#include "../plInterp/plController.h"
#include "../plInterp/hsInterp.h"
#include "../plInterp/plAnimEaseTypes.h"
#include "../MaxMain/plMaxNode.h"
#include "../pnKeyedObject/plKey.h"

#include "../plSurface/hsGMaterial.h"
#include "../plPipeline/plGBufferGroup.h"

#include "../plParticleSystem/plParticleSystem.h"
#include "../plParticleSystem/plParticleEmitter.h"
#include "../plParticleSystem/plParticleEffect.h"
#include "../plParticleSystem/plParticleGenerator.h"
#include "../plParticleSystem/plParticleApplicator.h"
#include "../plParticleSystem/plConvexVolume.h"
#include "../plParticleSystem/plBoundInterface.h"

#include "../plAvatar/plScalarChannel.h"
#include "../plAvatar/plAGAnim.h"

#include "../pnSceneObject/plDrawInterface.h"

#include "../plGLight/plLightInfo.h"
#include "plLightGrpComponent.h"

void DummyCodeIncludeFuncParticles()
{
}

//////////////////////////////////////////////////////////////////////////////////////////
//
// stuff for plParticleComponent

const char *plParticleCoreComponent::GenStrings[] = // line these up with the generation types enum.
{
	"Point Source",
	"Mesh",
	"One Per Vertex"
};

hsBool plParticleCoreComponent::IsParticleSystemComponent(plComponentBase *comp)
{
	if (comp->ClassID() == PARTICLE_SYSTEM_COMPONENT_CLASS_ID)
		return true;

	return false;
}

hsBool plParticleCoreComponent::NodeHasSystem(plMaxNode *pNode)
{
	int i;
	for (i = 0; i < pNode->NumAttachedComponents(); i++)
	{
		if (plParticleCoreComponent::IsParticleSystemComponent(pNode->GetAttachedComponent(i)))
			return true;
	}
	return false;
}

hsBool plParticleCoreComponent::PreConvert(plMaxNode *pNode, plErrorMsg *pErrMsg)
{
	GetParamVals( pNode );
	pNode->SetForceLocal(true);
	pNode->SetDrawable(false);
	pNode->SetParticleRelated(true);

	Mtl *maxMaterial = hsMaterialConverter::Instance().GetBaseMtl(pNode);
	plConvert &convert = plConvert::Instance();
	if (!hsMaterialConverter::IsParticleMat(maxMaterial))
	{
		maxMaterial = nil;
		pNode->SetMtl(NULL);
		if (pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedBadMaterialOnParticle), pNode->GetName(),
		"Only \"Plasma Particle\" materials (not in a multi-material) may be applied to particle system objects."
		" Using a default material for now.").CheckAskOrCancel())
		{
			convert.fWarned |= plConvert::kWarnedBadMaterialOnParticle;
		}
		pErrMsg->Set(false);
	}

	// Moving this from Convert so the DrawInterface will appear sooner. Other components expect
	// the interfaces to be fully set up by the Convert pass.
	plSceneNode	*sNode = plSceneNode::ConvertNoRef( pNode->GetRoomKey()->GetObjectPtr() );
	plDrawInterface *di = TRACKED_NEW plDrawInterface;
	hsgResMgr::ResMgr()->NewKey(IGetUniqueName(pNode), di, pNode->GetLocation(), pNode->GetLoadMask());
	hsgResMgr::ResMgr()->AddViaNotify( di->GetKey(), TRACKED_NEW plObjRefMsg(pNode->GetSceneObject()->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface ), plRefFlags::kActiveRef );
	pNode->SetDISceneNodeSpans(di, true);

	return true;
}

hsBool plParticleCoreComponent::Convert(plMaxNode *node, plErrorMsg *pErrMsg)
{	
	Int32 i, j, k;

	plLocation nodeLoc = node->GetKey()->GetUoid().GetLocation();
	const char *objName = node->GetKey()->GetName();

	plSceneObject *sObj = node->GetSceneObject();
	plParticleSystem *sys = TRACKED_NEW plParticleSystem();

	hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), sys, nodeLoc, node->GetLoadMask());	

	// Add material and lifespan animated params.
	Mtl *maxMaterial = hsMaterialConverter::Instance().GetBaseMtl(node);
	hsTArray<hsGMaterial *> matArray;
	hsMaterialConverter::Instance().GetMaterialArray(maxMaterial, node, matArray);
	hsGMaterial* particleMat = matArray[0];
	
	plController *ambientCtl = nil;
	plController *diffuseCtl = nil;
	plController *opacityCtl = nil;	
	plController *widthCtl = nil;
	plController *heightCtl = nil;
	hsControlConverter& cc = hsControlConverter::Instance();

	if (hsMaterialConverter::IsParticleMat(maxMaterial)) // Exporter will yell if this is false, but we have to handle it
	{
		plParticleMtl *particleMtl = (plParticleMtl *)maxMaterial; // We check beforehand that this is ok
		SetParticleStats(particleMtl);
		ambientCtl = cc.MakeColorController(particleMtl->GetAmbColorController(), node);
		diffuseCtl = cc.MakeColorController(particleMtl->GetColorController(), node);
		opacityCtl = cc.MakeScalarController(particleMtl->GetOpacityController(), node);
		widthCtl = cc.MakeScalarController(particleMtl->GetWidthController(), node);
		heightCtl = cc.MakeScalarController(particleMtl->GetHeightController(), node);
	}

	hsScalar genLife		= -1;
	hsScalar partLifeMin, partLifeMax;
	hsScalar pps			= fUserInput.fPPS; 
	hsPoint3 pos(0, 0, 0);
	hsScalar pitch			= PI;
	hsScalar yaw			= 0; 
	hsScalar angleRange		= fUserInput.fConeAngle * PI / 180.f;
	hsScalar velMin			= fUserInput.fVelocityMin;
	hsScalar velMax			= fUserInput.fVelocityMax;
	hsScalar xSize			= fUserInput.fHSize;
	hsScalar ySize			= fUserInput.fVSize;
	hsScalar scaleMin		= fUserInput.fScaleMin / 100.0f;
	hsScalar scaleMax		= fUserInput.fScaleMax / 100.0f;
	hsScalar gravity		= fUserInput.fGravity / 100.0f;
	hsScalar drag			= fUserInput.fDrag / 100.f;
	hsScalar windMult		= fUserInput.fWindMult / 100.f;
	hsScalar massRange		= fUserInput.fMassRange;
	hsScalar rotRange		= fUserInput.fRotRange * PI / 180.f;

	UInt32 xTiles = fUserInput.fXTiles; 
	UInt32 yTiles = fUserInput.fYTiles; 
	UInt32 maxEmitters = 1 + GetEmitterReserve();
	UInt32 maxTotalParticles = 0;	

	// Need to do this even when immortal, so that maxTotalParticles is computed correctly.
	partLifeMin = fUserInput.fLifeMin;
	partLifeMax = fUserInput.fLifeMax;
	plLeafController *ppsCtl = cc.MakeScalarController(fCompPB->GetController(ParamID(kPPS)), node);
	if (ppsCtl != nil && ppsCtl->GetLength() > 0)
	{
		// Simulate just the birth across the curve and record the max
		hsScalar frameDelta = (1.f / MAX_FRAMES_PER_SEC); 
		hsScalar avgLife = (partLifeMax + partLifeMin) / 2;
		UInt32 count = node->NumAttachedComponents();
		UInt32 lifeTicks = avgLife / frameDelta;
		hsScalar *birth = TRACKED_NEW hsScalar[lifeTicks];

		// Find any anim components attached to the same node.
		for (i = 0; i < count; i++)
		{
			if (!plAnimComponentBase::IsAnimComponent(node->GetAttachedComponent(i)))
				continue;

			hsScalar maxAnimParticles = 0;

			plAnimComponentBase *comp = (plAnimComponentBase *)node->GetAttachedComponent(i);
			plATCAnim *anim = plATCAnim::ConvertNoRef(comp->fAnims[node]);

			// If it's an ATC anim, we can be aggressive in determining the max
			if (anim)
			{			
				hsScalar curAnimParticles = 0;

				hsScalar loopStart, loopEnd;

				for (j = -1; j < (Int32)anim->GetNumLoops(); j++)
				{
					// Initialize our birth counters
					for (k = 0; k < lifeTicks; k++)
						birth[k] = 0;

					if (j == -1)
					{
						loopStart = anim->GetStart();
						loopEnd = anim->GetEnd();
					}
					else
						anim->GetLoop(j, loopStart, loopEnd);

					hsScalar loopLength = loopEnd - loopStart;

					if (loopLength == 0) // It's the default "(Entire Animation)"
						loopLength = ppsCtl->GetLength();

					UInt32 loopTicks = loopLength * MAX_FRAMES_PER_SEC;

					UInt32 startTick = loopStart * MAX_FRAMES_PER_SEC;
					UInt32 tick;
					for (tick = 0; tick < loopTicks + lifeTicks; tick++)
					{
						curAnimParticles -= birth[tick % lifeTicks] * frameDelta;
						hsScalar birthStart = 0.f;
						hsScalar birthEnd = 0.f;
						ppsCtl->Interp(((tick % loopTicks) + startTick) * frameDelta, &birthStart);
						ppsCtl->Interp(((tick % loopTicks) + startTick + 1) * frameDelta, &birthEnd);
						birth[tick % lifeTicks] = (birthStart + birthEnd) / 2;
						curAnimParticles += birth[tick % lifeTicks] * frameDelta;
						if (curAnimParticles > maxAnimParticles)
							maxAnimParticles = curAnimParticles;
					}
				}
			}
			else // No info on the animation. Assume the worst.
			{
				hsScalar maxPps = 0;
				int i;
				for (i = 1; i < ppsCtl->GetNumKeys(); i++)
				{
					hsScalar curVal = 0;
					hsScalarKey *key = ppsCtl->GetScalarKey(i);
					if (key)
						curVal = key->fValue;
					
					hsBezScalarKey *bezKey = ppsCtl->GetBezScalarKey(i);
					if (bezKey)
						curVal = bezKey->fValue;

					if( curVal > maxPps )
						maxPps = curVal;
				}
				maxAnimParticles = maxPps * (partLifeMax - (partLifeMax - partLifeMin) / 2);
			}
			
			if (maxTotalParticles < maxAnimParticles)
				maxTotalParticles = (UInt32)maxAnimParticles;
		}
		delete [] birth;
	}
	else
	{
		maxTotalParticles = pps * (partLifeMax - (partLifeMax - partLifeMin) / 2);
	}
	maxTotalParticles *=  maxEmitters;

	delete ppsCtl;
	ppsCtl = nil;
	
	UInt32 maxAllowedParticles = plGBufferGroup::kMaxNumIndicesPerBuffer / 6;
	if (maxTotalParticles > maxAllowedParticles)
	{
		char text[512];
		sprintf(text, "This particle system requires a buffer for %d particles. "
					  "The max allowed for a single system is %d. Capping this system "
					  "at the max. If you need more, create a 2nd particle system "
					  "and balance out the birthrates.", 
					  maxTotalParticles, maxAllowedParticles);

		plConvert &convert = plConvert::Instance();
		if (pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedTooManyParticles), node->GetName(), text).CheckAskOrCancel())
		{
			convert.fWarned |= plConvert::kWarnedTooManyParticles;
		}
		pErrMsg->Set(false);
		maxTotalParticles = maxAllowedParticles;
	}
	
	if (fUserInput.fImmortal)
	{
		partLifeMin		= -1.0;
		partLifeMax		= -1.0;
	}

	// Figure out the appropriate generator to add
	plParticleGenerator *generator = nil;
	UInt32 sources;
	hsScalar *pitchArray;
	hsScalar *yawArray;
	hsPoint3 *pointArray;
	hsVector3 *dirArray;
	if (fUserInput.fGenType == kGenPoint)
	{
		sources = 1;
		pitchArray = TRACKED_NEW hsScalar[sources];
		yawArray = TRACKED_NEW hsScalar[sources];
		pointArray = TRACKED_NEW hsPoint3[sources];
		pitchArray[0] = pitch;
		yawArray[0] = yaw;
		pointArray[0].Set(0, 0, 0);
		plSimpleParticleGenerator *gen = TRACKED_NEW plSimpleParticleGenerator();
		gen->Init(genLife, partLifeMin, partLifeMax, pps, sources, pointArray, pitchArray, yawArray, angleRange, 
				  velMin, velMax, xSize, ySize, scaleMin, scaleMax, massRange, rotRange);
		generator = gen;
	}
	else if (fUserInput.fGenType == kGenMesh)
	{
		hsTArray<hsVector3> normals;
		hsTArray<hsPoint3> pos;
		plMeshConverter::Instance().StuffPositionsAndNormals(node, &pos, &normals);
		sources = normals.GetCount();
		pitchArray = TRACKED_NEW hsScalar[sources];
		yawArray = TRACKED_NEW hsScalar[sources];
		pointArray = TRACKED_NEW hsPoint3[sources];
		int i;
		for (i = 0; i < sources; i++)
		{
			plParticleGenerator::ComputePitchYaw(pitchArray[i], yawArray[i], normals.Get(i));
			pointArray[i] = pos.Get(i);
		}
		plSimpleParticleGenerator *gen = TRACKED_NEW plSimpleParticleGenerator();
		gen->Init(genLife, partLifeMin, partLifeMax, pps, sources, pointArray, pitchArray, yawArray, angleRange, 
				  velMin, velMax, xSize, ySize, scaleMin, scaleMax, massRange, rotRange);
		generator = gen;
	}
	else // One per vertex
	{
		hsTArray<hsVector3> normals;
		hsTArray<hsPoint3> pos;
		plMeshConverter::Instance().StuffPositionsAndNormals(node, &pos, &normals);
		sources = normals.GetCount();

		pointArray = TRACKED_NEW hsPoint3[sources];
		dirArray = TRACKED_NEW hsVector3[sources];
		int i;
		for (i = 0; i < sources; i++)
		{
			dirArray[i] = normals.Get(i);
			pointArray[i] = pos.Get(i);
		}

		plOneTimeParticleGenerator *gen = TRACKED_NEW plOneTimeParticleGenerator();
		gen->Init(sources, pointArray, dirArray, xSize, ySize, scaleMin, scaleMax, rotRange);
		generator = gen;
		maxTotalParticles = sources;
		gravity = 0.f;
	}

	// Init and attach to the scene object
	sys->Init(xTiles, yTiles, maxTotalParticles, maxEmitters, ambientCtl, diffuseCtl, opacityCtl, 
			  widthCtl, heightCtl);
	hsgResMgr::ResMgr()->AddViaNotify( particleMat->GetKey(), TRACKED_NEW plGenRefMsg( sys->GetKey(), plRefMsg::kOnCreate, 0, 0 ), plRefFlags::kActiveRef );
	hsgResMgr::ResMgr()->AddViaNotify( sys->GetKey(), TRACKED_NEW plObjRefMsg( sObj->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier ), plRefFlags::kActiveRef );

	// Set up normals and orientation
	UInt32 miscFlags = 0;
	switch(fUserInput.fNormal)
	{
	case plParticleMtl::kNormalViewFacing:
		miscFlags |= plParticleEmitter::kNormalViewFacing;
		break;
	case plParticleMtl::kNormalUp:
	case plParticleMtl::kEmissive: // For emissive, we don't care about the normal. This choice makes us
								   // not waste time computing one.
		miscFlags |= plParticleEmitter::kNormalUp;
		break;
	case plParticleMtl::kNormalNearestLight:
		miscFlags |= plParticleEmitter::kNormalNearestLight;
		break;
	case plParticleMtl::kNormalFromCenter:
		miscFlags |= plParticleEmitter::kNormalFromCenter;
		break;
	case plParticleMtl::kNormalVelUpVel:
		miscFlags |= plParticleEmitter::kNormalVelUpVel;
		break;
	}

	switch(fUserInput.fOrientation)
	{
	case plParticleMtl::kOrientVelocity:
		miscFlags |= plParticleEmitter::kOrientationVelocityBased;
		break;
	case plParticleMtl::kOrientUp:
		miscFlags |= plParticleEmitter::kOrientationUp;
		break;
	case plParticleMtl::kOrientVelStretch:
		miscFlags |= plParticleEmitter::kOrientationVelocityStretch;
		break;
	case plParticleMtl::kOrientVelFlow:
		miscFlags |= plParticleEmitter::kOrientationVelocityFlow;
		break;
	}
	
	if (fUserInput.fGenType == kGenOnePerVertex &&
		(miscFlags & plParticleEmitter::kOrientationVelocityMask))
	{
		char text[256];
		sprintf(text, "This particle system has an orientation that's based on velocity "
					  "(see the Particle Material), which doesn't work with OnePerVertex "
					  "generation. No particles from this system will be visible.");

		plConvert &convert = plConvert::Instance();
		if (pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedParticleVelAndOnePer), node->GetName(), text).CheckAskOrCancel())
		{
			convert.fWarned |= plConvert::kWarnedParticleVelAndOnePer;
		}
		pErrMsg->Set(false);
	}
	if( maxEmitters > 1 )
		miscFlags |= plParticleEmitter::kOnReserve;

	sys->AddEmitter( maxTotalParticles, generator, miscFlags );
	sys->SetGravity(gravity);
	sys->SetDrag(drag);
	sys->SetWindMult(windMult);
	sys->SetPreSim(fUserInput.fPreSim);

	// Finally, any attached effects.
	for (i = 0; i < node->NumAttachedComponents(); i++)
	{
		plComponentBase *comp = node->GetAttachedComponent(i);
		if (plParticleEffectComponent::IsParticleEffectComponent(comp))
			((plParticleEffectComponent *)comp)->AddToParticleSystem(sys, node);
		if (comp->ClassID() == LIGHTGRP_COMP_CID)
			IHandleLights((plLightGrpComponent*)comp, sys);
	}
	
	if (fCompPB->GetInt(ParamID(kFollowSystem)))
	{
		plParticleFollowSystemEffect *effect = TRACKED_NEW plParticleFollowSystemEffect;
		hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), effect, node->GetLocation(), node->GetLoadMask());
		hsgResMgr::ResMgr()->AddViaNotify( effect->GetKey(), TRACKED_NEW plGenRefMsg( sys->GetKey(), plRefMsg::kOnCreate, 0, plParticleSystem::kEffectMisc ), plRefFlags::kActiveRef );
	}

	return true;
}

void plParticleCoreComponent::IHandleLights(plLightGrpComponent* liGrp, plParticleSystem* sys)
{
	const hsTArray<plLightInfo*>& liInfo = liGrp->GetLightInfos();
	int i;
	for( i = 0; i < liInfo.GetCount(); i++ )
	{
		sys->AddLight(liInfo[i]->GetKey());
	}
}

hsBool plParticleCoreComponent::AddToAnim(plAGAnim *anim, plMaxNode *node)
{
	hsBool result = false;
	plController *ctl;
	hsControlConverter& cc = hsControlConverter::Instance();

	hsScalar start, end;
	if (!strcmp(anim->GetName(), ENTIRE_ANIMATION_NAME))
	{
		start = end = -1;
	}
	else
	{
		start = anim->GetStart();
		end = anim->GetEnd();
	}

	if (fCompPB->GetInt(kGenType) != kGenOnePerVertex)
	{
		ctl = cc.MakeScalarController(fCompPB->GetController(ParamID(kLifeMin)), node, start, end);
		if (ctl != nil)
		{
			plParticleLifeMinApplicator *app = TRACKED_NEW plParticleLifeMinApplicator();
			app->SetChannelName(node->GetName());
			plAnimComponentBase::SetupCtl(anim, ctl, app, node);
			result = true;		
		}

		ctl = cc.MakeScalarController(fCompPB->GetController(ParamID(kLifeMax)), node, start, end);
		if (ctl != nil)
		{
			plParticleLifeMaxApplicator *app = TRACKED_NEW plParticleLifeMaxApplicator();
			app->SetChannelName(node->GetName());
			plAnimComponentBase::SetupCtl(anim, ctl, app, node);
			result = true;		
		}

		ctl = cc.MakeScalarController(fCompPB->GetController(ParamID(kPPS)), node, start, end);
		if (ctl != nil)
		{
			plParticlePPSApplicator *app = TRACKED_NEW plParticlePPSApplicator();
			app->SetChannelName(node->GetName());
			plAnimComponentBase::SetupCtl(anim, ctl, app, node);
			result = true;		
		}

		ctl = cc.MakeScalarController(fCompPB->GetController(ParamID(kConeAngle)), node, start, end);
		if (ctl != nil)
		{
			plParticleAngleApplicator *app = TRACKED_NEW plParticleAngleApplicator();
			app->SetChannelName(node->GetName());
			plAnimComponentBase::SetupCtl(anim, ctl, app, node);
			result = true;		
		}

		ctl = cc.MakeScalarController(fCompPB->GetController(ParamID(kVelocityMin)), node, start, end);
		if (ctl != nil)
		{
			plParticleVelMinApplicator *app = TRACKED_NEW plParticleVelMinApplicator();
			app->SetChannelName(node->GetName());
			plAnimComponentBase::SetupCtl(anim, ctl, app, node);
			result = true;		
		}

		ctl = cc.MakeScalarController(fCompPB->GetController(ParamID(kVelocityMax)), node, start, end); 
		if (ctl != nil)
		{
			plParticleVelMaxApplicator *app = TRACKED_NEW plParticleVelMaxApplicator();
			app->SetChannelName(node->GetName());
			plAnimComponentBase::SetupCtl(anim, ctl, app, node);
			result = true;		
		}
		
		/*
		ctl = cc.MakeScalarController(fCompPB->GetController(ParamID(kGravity)), node, start, end);
		if (ctl != nil)
		{
			plParticleGravityApplicator *app = TRACKED_NEW plParticleGravityApplicator();
			plAnimComponentBase::SetupCtl(anim, ctl, app, node);
			result = true;		
		}

		ctl = cc.MakeScalarController(fCompPB->GetController(ParamID(kDrag)), node, start, end);
		if (ctl != nil)
		{
			plParticleDragApplicator *app = TRACKED_NEW plParticleDragApplicator();
			plAnimComponentBase::SetupCtl(anim, ctl, app, node);
			result = true;		
		}
		*/
	}

	ctl = cc.MakeScalarController(fCompPB->GetController(ParamID(kScaleMin)), node, start, end);
	if (ctl != nil)
	{
		plParticleScaleMinApplicator *app = TRACKED_NEW plParticleScaleMinApplicator();
		app->SetChannelName(node->GetName());
		plAnimComponentBase::SetupCtl(anim, ctl, app, node);
		result = true;		
	}

	ctl = cc.MakeScalarController(fCompPB->GetController(ParamID(kScaleMax)), node, start, end);
	if (ctl != nil)
	{
		plParticleScaleMaxApplicator *app = TRACKED_NEW plParticleScaleMaxApplicator();
		app->SetChannelName(node->GetName());
		plAnimComponentBase::SetupCtl(anim, ctl, app, node);
		result = true;		
	}

	return result;
}

void plParticleCoreComponent::SetParticleStats(plParticleMtl *mtl)
{
	IParamBlock2 *pb = mtl->GetParamBlockByID(plParticleMtl::kBlkBasic);
	fUserInput.fHSize = pb->GetFloat(plParticleMtl::kWidth);
	fUserInput.fVSize = pb->GetFloat(plParticleMtl::kHeight);
	fUserInput.fXTiles = pb->GetInt(plParticleMtl::kXTiles);
	fUserInput.fYTiles = pb->GetInt(plParticleMtl::kYTiles);
	fUserInput.fNormal = pb->GetInt(plParticleMtl::kNormal);
	fUserInput.fOrientation = pb->GetInt(plParticleMtl::kOrientation);
}

CLASS_DESC(plParticleComponent, gParticleDesc, "Particle System",  "ParticleSystem", COMP_TYPE_PARTICLE, PARTICLE_SYSTEM_COMPONENT_CLASS_ID)

ParamBlockDesc2 gParticleBk
(	

	plComponent::kBlkComp, _T("Particle"), 0, &gParticleDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

	//Roll out
	IDD_COMP_PARTICLE, IDS_COMP_PARTICLE_ROLL, 0, 0, &gParticleCompDlgProc,

	//Particle Properties....

	plParticleCoreComponent::kGenType,		_T("Generation"),	TYPE_INT, 0, 0,
		p_default, 0,
		end,

	plParticleCoreComponent::kConeAngle,		_T("ConeAngle"),	TYPE_FLOAT, 	P_ANIMATABLE, IDS_PARTICLE_CONE_ANGLE,	
		p_default, 45.0,
		p_range, 0.0, 180.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_CONE, IDC_COMP_PARTICLE_CONE_SPIN, 1.0,
		end,

	plParticleCoreComponent::kVelocityMin,	_T("VelocityMin"),	TYPE_FLOAT, 	P_ANIMATABLE, IDS_PARTICLE_VELOCITY_MIN,	
		p_default, 50.0,
		p_range, 0.0, 500.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_VELMIN, IDC_COMP_PARTICLE_VELMIN_SPIN, 1.0,
		end,

	plParticleCoreComponent::kVelocityMax,	_T("VelocityMax"),	TYPE_FLOAT, 	P_ANIMATABLE, IDS_PARTICLE_VELOCITY_MAX,	
		p_default, 50.0,
		p_range, 0.0, 500.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_VELMAX, IDC_COMP_PARTICLE_VELMAX_SPIN, 1.0,
		end,

	plParticleCoreComponent::kLifeMin,		_T("LifeMin"),		TYPE_FLOAT, 	P_ANIMATABLE, IDS_PARTICLE_LIFE_MIN,	
		p_default, 10.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_LIFEMIN, IDC_COMP_PARTICLE_LIFEMIN_SPIN, 1.0,
		end,

	plParticleCoreComponent::kLifeMax,		_T("LifeMax"),		TYPE_FLOAT, 	P_ANIMATABLE, IDS_PARTICLE_LIFE_MAX,	
		p_default, 5.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_LIFEMAX, IDC_COMP_PARTICLE_LIFEMAX_SPIN, 1.0,
		end,

	plParticleCoreComponent::kImmortal, _T("Immortal"),		TYPE_BOOL,	
		p_default, FALSE,
		p_ui,	TYPE_SINGLECHEKBOX, IDC_COMP_PARTICLE_NODIE,
		end,

	plParticleCoreComponent::kPPS,	_T("PPS"),	TYPE_FLOAT, 	P_ANIMATABLE, IDS_PARTICLE_PPS,	
		p_default, 20.0,
		p_range, 0.0, 5000.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_PPS, IDC_COMP_PARTICLE_PPS_SPIN, 1.0,
		end,

	plParticleCoreComponent::kScaleMin,	_T("ScaleMin"),	TYPE_INT, 	P_ANIMATABLE, IDS_PARTICLE_SCALE_MIN,	
		p_default, 100,
		p_range, 1, 1000,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_INT,	
		IDC_COMP_PARTICLE_SCALEMIN, IDC_COMP_PARTICLE_SCALEMIN_SPIN, 1.0,
		end,

	plParticleCoreComponent::kScaleMax,	_T("ScaleMax"),	TYPE_INT, 	P_ANIMATABLE, IDS_PARTICLE_SCALE_MAX,	
		p_default, 100,
		p_range, 1, 1000,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_INT,	
		IDC_COMP_PARTICLE_SCALEMAX, IDC_COMP_PARTICLE_SCALEMAX_SPIN, 1.0,
		end,

	plParticleCoreComponent::kGravity,	_T("Gravity"),	TYPE_INT, 	0, 0,	
		p_default, 100,
		p_range, -100, 100,
		p_ui,	TYPE_SPINNER,	EDITTYPE_INT,	
		IDC_COMP_PARTICLE_GRAVITY, IDC_COMP_PARTICLE_GRAVITY_SPIN, 1.0,
		end,

	plParticleCoreComponent::kDrag,		_T("Drag"),		TYPE_INT, 	0, 0,	
		p_default, 0,
		p_range, 0, 1000,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_INT,	
		IDC_COMP_PARTICLE_DRAG, IDC_COMP_PARTICLE_DRAG_SPIN, 1.0,
		end,

	plParticleCoreComponent::kPreSim,	_T("PreSim"),	TYPE_INT, 	0, 0,	
		p_default, 0,
		p_range, 0, 100,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_INT,	
		IDC_COMP_PARTICLE_PRESIM, IDC_COMP_PARTICLE_PRESIM_SPIN, 1.0,
		end,

	plParticleCoreComponent::kWindMult,		_T("WindMult"),		TYPE_INT, 	0, 0,	
		p_default, 100,
		p_range, 0, 1000,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_INT,	
		IDC_COMP_PARTICLE_WIND_MULT, IDC_COMP_PARTICLE_WIND_MULT_SPIN, 1.0,
		end,

	plParticleCoreComponent::kMassRange,		_T("MassRange"),		TYPE_FLOAT, 	0, 0,	
		p_default, 0.f,
		p_range, 0.f, 1000.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_MASS_RANGE, IDC_COMP_PARTICLE_MASS_RANGE_SPIN, 1.0,
		end,

	plParticleCoreComponent::kRotRange,		_T("RotRange"),		TYPE_FLOAT, 	0, 0,	
		p_default, 0.f,
		p_range, 0.f, 180.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_ROT_RANGE, IDC_COMP_PARTICLE_ROT_RANGE_SPIN, 1.0,
		end,
		
	plParticleCoreComponent::kFollowSystem, _T("FollowSystem"),		TYPE_BOOL,	
		p_default, FALSE,
		p_ui,	TYPE_SINGLECHEKBOX, IDC_COMP_PARTICLE_FOLLOW_SYSTEM,
		end,		

	end
);

bool plParticleComponent::fAllowUnhide = false;

plParticleComponent::plParticleComponent()
:	fEmitterReserve(0)
{
	fClassDesc = &gParticleDesc;
	fClassDesc->MakeAutoParamBlocks(this);
}

// Gets values from the ParamBlock and passes them to the Convert
hsBool plParticleComponent::GetParamVals(plMaxNode *pNode)
{
	fUserInput.fGenType			= fCompPB->GetInt(kGenType);
	fUserInput.fConeAngle		= fCompPB->GetFloat(kConeAngle);
	fUserInput.fVelocityMin		= fCompPB->GetFloat(kVelocityMin);
	fUserInput.fVelocityMax		= fCompPB->GetFloat(kVelocityMax);
	fUserInput.fLifeMin			= fCompPB->GetFloat(kLifeMin);
	fUserInput.fLifeMax			= fCompPB->GetFloat(kLifeMax);
	fUserInput.fImmortal		= fCompPB->GetInt(kImmortal);
	fUserInput.fPPS				= fCompPB->GetFloat(kPPS);
	fUserInput.fScaleMin		= fCompPB->GetInt(kScaleMin);
	fUserInput.fScaleMax		= fCompPB->GetInt(kScaleMax);

	fUserInput.fGravity			= fCompPB->GetInt(kGravity);
	fUserInput.fDrag			= fCompPB->GetInt(kDrag);
	fUserInput.fWindMult		= fCompPB->GetInt(kWindMult);
	fUserInput.fMassRange		= fCompPB->GetFloat(kMassRange);
	fUserInput.fPreSim			= fCompPB->GetInt(kPreSim);
	fUserInput.fRotRange		= fCompPB->GetFloat(kRotRange);

	return true;
}

class ParticleCompDlgProc : public ParamMap2UserDlgProc
{
protected:
	void EnableDynGenParams(IParamMap2 *pm, bool enabled)
	{
		pm->Enable(plParticleComponent::kConeAngle, enabled);
		pm->Enable(plParticleComponent::kVelocityMin, enabled);
		pm->Enable(plParticleComponent::kVelocityMax, enabled);
		pm->Enable(plParticleComponent::kLifeMin, enabled);
		pm->Enable(plParticleComponent::kLifeMax, enabled);
		pm->Enable(plParticleComponent::kImmortal, enabled);
		pm->Enable(plParticleComponent::kPPS, enabled);
		pm->Enable(plParticleComponent::kGravity, enabled);
		pm->Enable(plParticleComponent::kPreSim, enabled);
		pm->Enable(plParticleComponent::kDrag, enabled);
	}

public:
	ParticleCompDlgProc() {}
	~ParticleCompDlgProc() {}

	void IValidateSpinners(TimeValue t, IParamBlock2 *pb, IParamMap2 *map, UInt32 id)
	{
		UInt32 minIndex, maxIndex;
		hsBool adjustMin;
		switch(id)
		{
		case IDC_COMP_PARTICLE_VELMIN:
		case IDC_COMP_PARTICLE_VELMIN_SPIN:
			minIndex = plParticleCoreComponent::kVelocityMin; maxIndex = plParticleCoreComponent::kVelocityMax; adjustMin = false;
			break;
		case IDC_COMP_PARTICLE_VELMAX:
		case IDC_COMP_PARTICLE_VELMAX_SPIN:
			minIndex = plParticleCoreComponent::kVelocityMin; maxIndex = plParticleCoreComponent::kVelocityMax; adjustMin = true;
			break;
		case IDC_COMP_PARTICLE_LIFEMIN:
		case IDC_COMP_PARTICLE_LIFEMIN_SPIN:
			minIndex = plParticleCoreComponent::kLifeMin; maxIndex = plParticleCoreComponent::kLifeMax; adjustMin = false;
			break;
		case IDC_COMP_PARTICLE_LIFEMAX:
		case IDC_COMP_PARTICLE_LIFEMAX_SPIN:
			minIndex = plParticleCoreComponent::kLifeMin; maxIndex = plParticleCoreComponent::kLifeMax; adjustMin = true;
			break;
		default:
			return;
		}

		float min, max;
		min = pb->GetFloat(minIndex, t);
		max = pb->GetFloat(maxIndex, t);

		if (min > max)
		{
			if (adjustMin)
				pb->SetValue(minIndex, t, max);
			else
				pb->SetValue(maxIndex, t, min);

			map->Invalidate(minIndex);
			map->Invalidate(maxIndex);
		}
	}

	BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
		int id = LOWORD(wParam);
		int code = HIWORD(wParam);

		IParamBlock2 *pb = map->GetParamBlock();
		HWND cbox = NULL;

		int selection;
		switch (msg)
		{
		case WM_INITDIALOG:
			int j;
			for (j = 0; j < plParticleCoreComponent::kGenNumOptions; j++)
			{
				cbox = GetDlgItem(hWnd, IDC_GEN_TYPE);
				SendMessage(cbox, CB_ADDSTRING, 0, (LPARAM)plParticleCoreComponent::GenStrings[j]);
			}
			selection = pb->GetInt(plParticleCoreComponent::kGenType);
			SendMessage(cbox, CB_SETCURSEL, selection, 0);
			EnableDynGenParams(map, selection != plParticleCoreComponent::kGenOnePerVertex);

			CheckDlgButton(hWnd, IDC_TRACKVIEW_SHOW, plParticleComponent::fAllowUnhide ? BST_CHECKED : BST_UNCHECKED);
			return TRUE;

		case WM_COMMAND:  
        	if (id == IDC_GEN_TYPE)
			{
				selection = SendMessage(GetDlgItem(hWnd, id), CB_GETCURSEL, 0, 0);
				pb->SetValue(plParticleCoreComponent::kGenType, t, selection);
				EnableDynGenParams(map, selection != plParticleCoreComponent::kGenOnePerVertex);
				return TRUE;
			}
			else if (id == IDC_COMP_PARTICLE_VELMIN || id == IDC_COMP_PARTICLE_VELMAX ||
				id == IDC_COMP_PARTICLE_LIFEMIN || id == IDC_COMP_PARTICLE_LIFEMAX)
			{
				IValidateSpinners(t, pb, map, id);
				return TRUE;
			}
			else if (id == IDC_TRACKVIEW_SHOW && code == BN_CLICKED)
			{
				plParticleComponent::fAllowUnhide = (IsDlgButtonChecked(hWnd, IDC_TRACKVIEW_SHOW) == BST_CHECKED);
				plComponentShow::Update();
				return TRUE;
			}
			break;
		case CC_SPINNER_CHANGE:
			if (id == IDC_COMP_PARTICLE_VELMIN_SPIN || id == IDC_COMP_PARTICLE_VELMAX_SPIN ||
				id == IDC_COMP_PARTICLE_LIFEMIN_SPIN || id == IDC_COMP_PARTICLE_LIFEMAX_SPIN)
			{
				IValidateSpinners(t, pb, map, id);
				return TRUE;
			}
			break;
		}
		return FALSE;
	}
	void DeleteThis() {}
};
static ParticleCompDlgProc gParticleCompDlgProc;

//////////////////////////////////////////////////////////////////////////////////////////
//
// Particle Effects Base class

// Make sure any new Effect you add is accounted for here, or it won't get converted.
hsBool plParticleEffectComponent::IsParticleEffectComponent(plComponentBase *comp)
{
	if (comp->ClassID() == PARTICLE_FADE_COMPONENT_CLASS_ID ||
		comp->ClassID() == PARTICLE_VOLUME_COMPONENT_CLASS_ID ||
		comp->ClassID() == PARTICLE_WIND_COMPONENT_CLASS_ID ||
		comp->ClassID() == PARTICLE_UNIWIND_COMPONENT_CLASS_ID ||
		comp->ClassID() == PARTICLE_FLOCK_COMPONENT_CLASS_ID)
		return true;

	return false;
}

hsBool plParticleEffectComponent::SetupProperties(plMaxNode *node, plErrorMsg *pErrMsg)
{
	fEffect = nil;
	return true;
}

hsBool plParticleEffectComponent::PreConvert(plMaxNode *node, plErrorMsg *pErrMsg)
{
	hsBool valid = plParticleCoreComponent::NodeHasSystem(node);
	if (!valid)
	{
		pErrMsg->Set(true, node->GetName(), "Node has a particle effect component, "
					 "but no particle system to apply it to. Ignoring component.").Show();
		pErrMsg->Set(false);
	}

	return valid;
}

//////////////////////////////////////////////////////////////////////////////////////////
//
// plParticleFadeComponent

void plParticleFadeComponent::AddToParticleSystem(plParticleSystem *sys, plMaxNode *node)
{
	plParticleFadeVolumeEffect *effect = nil;
	if( !fEffect )
	{
		effect = TRACKED_NEW plParticleFadeVolumeEffect();
		hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), effect, node->GetLocation(), node->GetLoadMask());
		effect->fLength = (float)fCompPB->GetInt(kFadeDistance);
		if (fCompPB->GetInt(kFadeZ))
			effect->fIgnoreZ = false;
		
		fEffect = effect;
	}
	else
	{
		effect = plParticleFadeVolumeEffect::ConvertNoRef(fEffect);
	}
	hsAssert(effect, "Our effect pointer was wrong type?");
	hsgResMgr::ResMgr()->AddViaNotify( effect->GetKey(), TRACKED_NEW plGenRefMsg( sys->GetKey(), plRefMsg::kOnCreate, 0, plParticleSystem::kEffectMisc ), plRefFlags::kActiveRef );
}

CLASS_DESC(plParticleFadeComponent, gParticleFadeDesc, "Fade Volume Effect",  "Fade Volume Effect", COMP_TYPE_PARTICLE, PARTICLE_FADE_COMPONENT_CLASS_ID)

ParamBlockDesc2 gParticleFadeBk
(	

	plComponent::kBlkComp, _T("ParticleFade"), 0, &gParticleFadeDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

	//Roll out
	IDD_COMP_PARTICLE_FADE, IDS_COMP_PARTICLE_FADE_ROLL, 0, 0, NULL,

	plParticleFadeComponent::kFadeDistance,	_T("FadeDistance"),	TYPE_INT, 	P_ANIMATABLE, 0,	
		p_default, 100,
		p_range, 0, 10000,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_INT,	
		IDC_COMP_PARTICLE_FADE_DIST, IDC_COMP_PARTICLE_FADE_DIST_SPIN, 1.0,
		end,

	plParticleFadeComponent::kFadeZ,		_T("FadeInZ"),		TYPE_BOOL, 	P_ANIMATABLE, 0,	
		p_default, FALSE,
		p_ui,	TYPE_SINGLECHEKBOX, IDC_COMP_PARTICLE_FADEZ,
		end,

	end
);

plParticleFadeComponent::plParticleFadeComponent()
{
	fClassDesc = &gParticleFadeDesc;
	fClassDesc->MakeAutoParamBlocks(this);
}

////////////////////////////////////////////////////////////////////////////
//
// Convex Volume Component

void plParticleVolumeComponent::CollectNonDrawables(INodeTab& nonDrawables)
{
	INode* source = fCompPB->GetINode(kSourceNode);
	if( source )
		nonDrawables.Append(1, &source);
}

hsBool plParticleVolumeComponent::PreConvert(plMaxNode *pNode, plErrorMsg *pErrMsg)
{
	if (!plParticleEffectComponent::PreConvert(pNode, pErrMsg))
		return false;

	fBound = nil;

	plMaxNode *source = (plMaxNode *)fCompPB->GetINode(kSourceNode);
	if (source == nil || !source->CanConvert())
	{
		pErrMsg->Set(true, pNode->GetName(), "Particle Convex Volume component has not been assigned a "
					 "node to build itself from or Volume has Ignore component on it.. Ignoring component.").Show();
		pErrMsg->Set(false);
		return false; // No source selected
	}

	source->SetForceLocal(true);
	source->SetDrawable(false);
	source->SetParticleRelated(true);

	return true;
}

void plParticleVolumeComponent::BuildVolume(plMaxNode *node)
{
	if (fBound != nil)
		return; // already converted it

	fBound = TRACKED_NEW plBoundInterface;
	hsgResMgr::ResMgr()->NewKey(node->GetName(), fBound, node->GetLocation(), node->GetLoadMask());
	fBound->Init(plMeshConverter::Instance().CreateConvexVolume(node));
	hsgResMgr::ResMgr()->AddViaNotify(fBound->GetKey(), TRACKED_NEW plObjRefMsg(node->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
}


void plParticleVolumeComponent::AddToParticleSystem(plParticleSystem *sys, plMaxNode *node)
{
	plParticleCollisionEffect *effect = nil;
	if( !fEffect )
	{
		plMaxNode *source = (plMaxNode *)fCompPB->GetINode(kSourceNode);

		if (source == nil || !source->CanConvert())
			return; // No source selected, user has already been warned.

		BuildVolume(source);
		switch( fCompPB->GetInt(kOnImpact) )
		{
		default:
		case kImpDefault:
			effect = TRACKED_NEW plParticleCollisionEffectBeat();
			break;
		case kImpDie:
			effect = TRACKED_NEW plParticleCollisionEffectDie();
			break;
		case kImpBounce:
			{
				plParticleCollisionEffectBounce* bnc = TRACKED_NEW plParticleCollisionEffectBounce();
				bnc->SetBounce(fCompPB->GetFloat(kBounceAmt) * 1.e-2f);
				bnc->SetFriction(fCompPB->GetFloat(kFrictionAmt) * 1.e-2f);
				effect = bnc;
			}
			break;
		};

		hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), effect, node->GetLocation(), node->GetLoadMask());	
		plSceneObject *sObj = source->GetSceneObject();
		hsgResMgr::ResMgr()->AddViaNotify( sObj->GetKey(), TRACKED_NEW plGenRefMsg( effect->GetKey(), plRefMsg::kOnCreate, 0, 0 ), plRefFlags::kPassiveRef );

		fEffect = effect;
	}
	else
	{
		effect = plParticleCollisionEffect::ConvertNoRef(fEffect);
	}
	hsAssert(effect, "Our effect pointer was wrong type?");
	hsgResMgr::ResMgr()->AddViaNotify( effect->GetKey(), TRACKED_NEW plGenRefMsg( sys->GetKey(), plRefMsg::kOnCreate, 0, plParticleSystem::kEffectConstraint ), plRefFlags::kActiveRef );
}

CLASS_DESC(plParticleVolumeComponent, gParticleVolumeDesc, "Collision Volume Effect",  "Collision Volume Effect", COMP_TYPE_PARTICLE, PARTICLE_VOLUME_COMPONENT_CLASS_ID)

ParamBlockDesc2 gParticleVolumeBk
(	
	plComponent::kBlkComp, _T("ParticleVolume"), 0, &gParticleVolumeDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

	//Roll out
	IDD_COMP_PARTICLE_VOLUME, IDS_COMP_PARTICLE_VOLUME_ROLL, 0, 0, NULL,

	plParticleVolumeComponent::kSourceNode, _T("SourceINode"),	TYPE_INODE,		0, 0,
		p_ui,	TYPE_PICKNODEBUTTON, IDC_COMP_PARTICLE_VOLUME_NODE,
		p_sclassID,	GEOMOBJECT_CLASS_ID,
		//p_prompt, IDS_COMP_ONESHOT_STARTS,
		//p_accessor, &gOneShotAccessor,
		end,

	plParticleVolumeComponent::kOnImpact, _T("OnImpact"),		TYPE_INT, 		0, 0,
		p_ui,		TYPE_RADIO, 3,	IDC_COMP_PARTICLE_VOL_DEFAULT,					IDC_COMP_PARTICLE_VOL_DIE,				IDC_COMP_PARTICLE_VOL_BOUNCE,	
		p_vals,						plParticleVolumeComponent::kImpDefault,	plParticleVolumeComponent::kImpDie,	plParticleVolumeComponent::kImpBounce,		
		p_default, plParticleVolumeComponent::kImpDefault,
		end,

	plParticleVolumeComponent::kBounceAmt, _T("BounceAmt"), TYPE_FLOAT, 	0, 0,	
		p_default, 100.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_VOL_BOUNCEAMT, IDC_COMP_PARTICLE_VOL_BOUNCEAMT_SPIN, 1.0,
		end,	
	
	plParticleVolumeComponent::kFrictionAmt, _T("FrictionAmt"), TYPE_FLOAT, 	0, 0,	
		p_default, 0.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_VOL_FRICTIONAMT, IDC_COMP_PARTICLE_VOL_FRICTIONAMT_SPIN, 1.0,
		end,	
	
	end
);

plParticleVolumeComponent::plParticleVolumeComponent()
{
	fClassDesc = &gParticleVolumeDesc;
	fClassDesc->MakeAutoParamBlocks(this);
}

//////////////////////////////////////////////////////////////////////////////////////////
//
// plParticleWindComponent

static hsVector3 IGetRefDir(plMaxNode* node, INode* refNode, float clampAngDeg)
{
	clampAngDeg *= 0.5f;
	float vecLen = 100.f;
	if( clampAngDeg > 1.f )
	{
		float rads = hsScalarDegToRad(clampAngDeg);
		float sinAng = sinf(rads);

		hsAssert(sinAng > 0.01, "Trig confusion?");

#if 0 
		float cosAng = cosf(rads);
		if( cosAng < 0.01f )
			vecLen = 0;
		else
			vecLen = cosAng / sinAng;
#else
		vecLen = 1.f / sinAng;
#endif

	}
	if( !refNode )
		refNode = node;

	Matrix3 nodeTM = refNode->GetNodeTM(TimeValue(0));
	Point3 dir = nodeTM.GetRow(1);
	dir = FNormalize(dir);

	hsVector3 refDir(dir.x * vecLen, dir.y * vecLen, dir.z * vecLen);

	return refDir;
}


void plParticleWindComponent::AddToParticleSystem(plParticleSystem *sys, plMaxNode *node)
{
	plParticleLocalWind* effect = nil;
	if( !fEffect )
	{
		effect = TRACKED_NEW plParticleLocalWind();
		effect->SetScale(hsVector3(fCompPB->GetFloat(kScaleX), fCompPB->GetFloat(kScaleY), fCompPB->GetFloat(kScaleZ)));
		effect->SetSpeed(fCompPB->GetFloat(kSpeed));

		hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), effect, node->GetLocation(), node->GetLoadMask());

		effect->SetStrength(fCompPB->GetFloat(kStrength));
		effect->SetSwirl(fCompPB->GetFloat(kSwirl) * 1.e-2f);
		effect->SetHorizontal(fCompPB->GetInt(kHorizontal));
		effect->SetConstancy(fCompPB->GetFloat(kConstancy) * 1.e-2f);

		effect->SetRefDirection(IGetRefDir(node, fCompPB->GetINode(kRefObject), fCompPB->GetFloat(kClampAngle)));

		fEffect = effect;
	}
	else
	{
		effect = plParticleLocalWind::ConvertNoRef(fEffect);
	}
	hsAssert(effect, "Our effect pointer was wrong type?");
	hsgResMgr::ResMgr()->AddViaNotify( effect->GetKey(), TRACKED_NEW plGenRefMsg( sys->GetKey(), plRefMsg::kOnCreate, 0, plParticleSystem::kEffectForce ), plRefFlags::kActiveRef );
}

CLASS_DESC(plParticleWindComponent, gParticleWindDesc, "Wind Effect",  "WindEffect", COMP_TYPE_PARTICLE, PARTICLE_WIND_COMPONENT_CLASS_ID)

ParamBlockDesc2 gParticleWindBk
(	

	plComponent::kBlkComp, _T("ParticleWind"), 0, &gParticleWindDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

	//Roll out
	IDD_COMP_PARTICLE_WIND, IDS_COMP_PARTICLE_WIND, 0, 0, NULL,

	plParticleWindComponent::kScaleX,	_T("ScaleX"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 25.f,
		p_range, 0.f, 1000.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_SCALEX, IDC_COMP_PARTICLE_WIND_SCALEX_SPIN, 1.0,
		end,

	plParticleWindComponent::kScaleY,	_T("ScaleY"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 25.f,
		p_range, 0.f, 1000.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_SCALEY, IDC_COMP_PARTICLE_WIND_SCALEY_SPIN, 1.0,
		end,

	plParticleWindComponent::kScaleZ,	_T("ScaleZ"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 0.f,
		p_range, 0.f, 1000.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_SCALEZ, IDC_COMP_PARTICLE_WIND_SCALEZ_SPIN, 1.0,
		end,

	plParticleWindComponent::kSpeed,	_T("Speed"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 32.f,
		p_range, -100.f, 100.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_FLOAT,	
		IDC_COMP_PARTICLE_WIND_SPEED, IDC_COMP_PARTICLE_WIND_SPEED_SPIN, 1.0,
		end,

	plParticleWindComponent::kStrength,	_T("Strength"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 30.f,
		p_range, 0.f, 100.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_STRENGTH, IDC_COMP_PARTICLE_WIND_STRENGTH_SPIN, 1.0,
		end,

	plParticleWindComponent::kConstancy,	_T("Constancy"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 0.f,
		p_range, -75.f, 300.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_FLOAT,	
		IDC_COMP_PARTICLE_WIND_CONSTANCY, IDC_COMP_PARTICLE_WIND_CONSTANCY_SPIN, 1.0,
		end,

	plParticleWindComponent::kSwirl,	_T("Swirl"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 100.f,
		p_range, 0.f, 100.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_SWIRL, IDC_COMP_PARTICLE_WIND_SWIRL_SPIN, 1.0,
		end,

	plParticleWindComponent::kHorizontal, _T("Horizontal"),		TYPE_BOOL,	
		p_default, FALSE,
		p_ui,	TYPE_SINGLECHEKBOX, IDC_COMP_PARTICLE_WIND_HORIZONTAL,
		end,

	plParticleWindComponent::kLocalize, _T("Localize"),		TYPE_BOOL,	
		p_default, TRUE,
		end,

	plParticleWindComponent::kClampAngle, _T("ClampAngle"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 180.f,
		p_range, 0.f, 180.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_CLAMPANG, IDC_COMP_PARTICLE_WIND_CLAMPANG_SPIN, 1.0,
		end,

	plParticleWindComponent::kRefObject, _T("RefObject"),	TYPE_INODE,		0, 0,
		p_ui,	TYPE_PICKNODEBUTTON, IDC_COMP_PARTICLE_WIND_REFOBJECT,
		p_prompt, IDS_COMP_CHOOSE_OBJECT,
		end,

	end
);

plParticleWindComponent::plParticleWindComponent()
{
	fClassDesc = &gParticleWindDesc;
	fClassDesc->MakeAutoParamBlocks(this);
}

//////////////////////////////////////////////////////////////////////////////////////////
//
// plParticleUniWindComponent

void plParticleUniWindComponent::AddToParticleSystem(plParticleSystem *sys, plMaxNode *node)
{
	plParticleUniformWind* effect = nil;
	if( !fEffect )
	{
		effect = TRACKED_NEW plParticleUniformWind();
		hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), effect, node->GetLocation(), node->GetLoadMask());

		effect->SetStrength(fCompPB->GetFloat(kStrength));
		effect->SetSwirl(fCompPB->GetFloat(kSwirl) * 1.e-2f);
		effect->SetHorizontal(fCompPB->GetInt(kHorizontal));
		effect->SetConstancy(fCompPB->GetFloat(kConstancy) * 1.e-2f);

		effect->SetFrequencyRange(fCompPB->GetFloat(kMinSecs), fCompPB->GetFloat(kMaxSecs));
		effect->SetFrequencyRate(fCompPB->GetFloat(kRate));

		effect->SetRefDirection(IGetRefDir(node, fCompPB->GetINode(kRefObject), fCompPB->GetFloat(kClampAngle)));

		fEffect = effect;
	}
	else
	{
		effect = plParticleUniformWind::ConvertNoRef(fEffect);
	}
	hsAssert(effect, "Our effect pointer was wrong type?");
	hsgResMgr::ResMgr()->AddViaNotify( effect->GetKey(), TRACKED_NEW plGenRefMsg( sys->GetKey(), plRefMsg::kOnCreate, 0, plParticleSystem::kEffectForce ), plRefFlags::kActiveRef );
}

CLASS_DESC(plParticleUniWindComponent, gParticleUniWindDesc, "Uniform Wind",  "UniWind", COMP_TYPE_PARTICLE, PARTICLE_UNIWIND_COMPONENT_CLASS_ID)

ParamBlockDesc2 gParticleUniWindBk
(	

	plComponent::kBlkComp, _T("ParticleUniWind"), 0, &gParticleUniWindDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,

	//Roll out
	IDD_COMP_PARTICLE_UNIWIND, IDS_COMP_PARTICLE_UNIWIND, 0, 0, NULL,

	plParticleUniWindComponent::kStrength,	_T("Strength"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 30.f,
		p_range, 0.f, 100.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_STRENGTH, IDC_COMP_PARTICLE_WIND_STRENGTH_SPIN, 1.0,
		end,

	plParticleUniWindComponent::kConstancy,	_T("Constancy"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 0.f,
		p_range, -75.f, 300.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_FLOAT,	
		IDC_COMP_PARTICLE_WIND_CONSTANCY, IDC_COMP_PARTICLE_WIND_CONSTANCY_SPIN, 1.0,
		end,

	plParticleUniWindComponent::kSwirl,	_T("Swirl"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 100.f,
		p_range, 0.f, 100.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_SWIRL, IDC_COMP_PARTICLE_WIND_SWIRL_SPIN, 1.0,
		end,

	plParticleUniWindComponent::kHorizontal, _T("Horizontal"),		TYPE_BOOL,	
		p_default, FALSE,
		p_ui,	TYPE_SINGLECHEKBOX, IDC_COMP_PARTICLE_WIND_HORIZONTAL,
		end,

	plParticleUniWindComponent::kMinSecs,	_T("MinSecs"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 1.f,
		p_range, 0.1f, 20.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_MINSECS, IDC_COMP_PARTICLE_WIND_MINSECS_SPIN, 1.0,
		end,

	plParticleUniWindComponent::kMaxSecs,	_T("MaxSecs"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 10.f,
		p_range, 0.1f, 30.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_MAXSECS, IDC_COMP_PARTICLE_WIND_MAXSECS_SPIN, 1.0,
		end,

	plParticleUniWindComponent::kRate,	_T("Rate"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 10.f,
		p_range, 0.1f, 50.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_RATE, IDC_COMP_PARTICLE_WIND_RATE_SPIN, 1.0,
		end,

	plParticleUniWindComponent::kClampAngle, _T("ClampAngle"),	TYPE_FLOAT, 	P_ANIMATABLE, 0,	
		p_default, 180.f,
		p_range, 0.f, 180.f,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_COMP_PARTICLE_WIND_CLAMPANG2, IDC_COMP_PARTICLE_WIND_CLAMPANG_SPIN2, 1.0,
		end,

	plParticleUniWindComponent::kRefObject, _T("RefObject"),	TYPE_INODE,		0, 0,
		p_ui,	TYPE_PICKNODEBUTTON, IDC_COMP_PARTICLE_WIND_REFOBJECT,
		p_prompt, IDS_COMP_CHOOSE_OBJECT,
		end,

	end
);

plParticleUniWindComponent::plParticleUniWindComponent()
{
	fClassDesc = &gParticleUniWindDesc;
	fClassDesc->MakeAutoParamBlocks(this);
}

//////////////////////////////////////////////////////////////////////////////////////////
//
// plParticleFlockComponent

class ParticleFlockEffectDlgProc : public ParamMap2UserDlgProc
{
public:
	ParticleFlockEffectDlgProc() {}
	~ParticleFlockEffectDlgProc() {}

	void IValidateSpinners(TimeValue t, IParamBlock2 *pb, IParamMap2 *map, UInt32 id)
	{
		UInt32 minIndex, maxIndex;
		hsBool adjustMin;
		switch(id)
		{
		case IDC_FLOCK_GOAL_DIST:
		case IDC_FLOCK_GOAL_DIST_SPIN:
			minIndex = plParticleFlockComponent::kGoalDist; maxIndex = plParticleFlockComponent::kFullChaseDist; adjustMin = false;
			break;
		case IDC_FLOCK_FULL_CHASE_DIST:
		case IDC_FLOCK_FULL_CHASE_DIST_SPIN:
			minIndex = plParticleFlockComponent::kGoalDist; maxIndex = plParticleFlockComponent::kFullChaseDist; adjustMin = true;
			break;
		default:
			return;
		}

		float min, max;
		min = pb->GetFloat(minIndex, t);
		max = pb->GetFloat(maxIndex, t);

		if (min > max)
		{
			if (adjustMin)
				pb->SetValue(minIndex, t, max);
			else
				pb->SetValue(maxIndex, t, min);

			map->Invalidate(minIndex);
			map->Invalidate(maxIndex);
		}
	}

	BOOL DlgProc(TimeValue t, IParamMap2 *map, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
		int id = LOWORD(wParam);

		IParamBlock2 *pb = map->GetParamBlock();

		switch (msg)
		{
		case WM_COMMAND:  
		case CC_SPINNER_CHANGE:
			IValidateSpinners(t, pb, map, id);
			return TRUE;
		}
		return FALSE;
	}
	void DeleteThis() {}
};
static ParticleFlockEffectDlgProc gParticleFlockEffectDlgProc;



void plParticleFlockComponent::AddToParticleSystem(plParticleSystem *sys, plMaxNode *node)
{
	plParticleFlockEffect* effect = nil;
	if( !fEffect )
	{
		effect = TRACKED_NEW plParticleFlockEffect();
		hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), effect, node->GetLocation(), node->GetLoadMask());

		hsPoint3 offset(fCompPB->GetFloat(ParamID(kOffsetX)),
						fCompPB->GetFloat(ParamID(kOffsetY)),
						fCompPB->GetFloat(ParamID(kOffsetZ)));
		effect->SetTargetOffset(offset);
		effect->SetInfluenceAvgRadius(fCompPB->GetFloat(ParamID(kInfAvgDist)));
		effect->SetInfluenceRepelRadius(fCompPB->GetFloat(ParamID(kInfRepDist)));

		hsScalar goalDist = fCompPB->GetFloat(ParamID(kGoalDist));
		hsScalar fcDist = fCompPB->GetFloat(ParamID(kFullChaseDist));	
		effect->SetGoalRadius(goalDist);
		effect->SetFullChaseRadius(goalDist > fcDist ? goalDist : fcDist); // Fix old data

		effect->SetConformStr(fCompPB->GetFloat(ParamID(kInfAvgStr)));
		effect->SetRepelStr(fCompPB->GetFloat(ParamID(kInfRepStr)));
		effect->SetGoalOrbitStr(fCompPB->GetFloat(ParamID(kGoalOrbitStr)));
		effect->SetGoalChaseStr(fCompPB->GetFloat(ParamID(kGoalChaseStr)));
		effect->SetMaxChaseSpeed(fCompPB->GetFloat(ParamID(kMaxChaseSpeed)));
		effect->SetMaxOrbitSpeed(fCompPB->GetFloat(ParamID(kMaxOrbitSpeed)));
		effect->SetMaxParticles(sys->GetMaxTotalParticles());

		fEffect = effect;
	}
	else
	{
		effect = plParticleFlockEffect::ConvertNoRef(fEffect);
	}
	hsAssert(effect, "Our effect pointer was wrong type?");
	hsgResMgr::ResMgr()->AddViaNotify( effect->GetKey(), TRACKED_NEW plGenRefMsg( sys->GetKey(), plRefMsg::kOnCreate, 0, plParticleSystem::kEffectForce ), plRefFlags::kActiveRef );
}

CLASS_DESC(plParticleFlockComponent, gParticleFlockDesc, "Particle Flock",  "Flock", COMP_TYPE_PARTICLE, PARTICLE_FLOCK_COMPONENT_CLASS_ID)

ParamBlockDesc2 gParticleFlockBk
(	
 
	plComponent::kBlkComp, _T("ParticleFlock"), 0, &gParticleFlockDesc, P_AUTO_CONSTRUCT + P_AUTO_UI, plComponent::kRefComp,
	
	//Roll out
	IDD_COMP_PARTICLE_FLOCK, IDS_COMP_PARTICLE_FLOCK, 0, 0, &gParticleFlockEffectDlgProc,
	
	plParticleFlockComponent::kOffsetX,	_T("OffsetX"),	TYPE_FLOAT, 0, 0,	
		p_default, 0.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_TARGET_OFFSETX, IDC_FLOCK_TARGET_OFFSETX_SPIN, 1.0,
		end,
	
	plParticleFlockComponent::kOffsetY,	_T("OffsetY"),	TYPE_FLOAT, 0, 0,	
		p_default, 0.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_TARGET_OFFSETY, IDC_FLOCK_TARGET_OFFSETY_SPIN, 1.0,
		end,

	plParticleFlockComponent::kOffsetZ,	_T("OffsetZ"),	TYPE_FLOAT, 0, 0,	
		p_default, 0.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_TARGET_OFFSETZ, IDC_FLOCK_TARGET_OFFSETZ_SPIN, 1.0,
		end,

	plParticleFlockComponent::kInfAvgDist,	_T("InfAvgDist"),	TYPE_FLOAT, 0, 0,	
		p_default, 1.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_CONFORM_DIST, IDC_FLOCK_CONFORM_DIST_SPIN, 1.0,
		end,

	plParticleFlockComponent::kInfRepDist,	_T("InfRepDist"),	TYPE_FLOAT, 0, 0,	
		p_default, 1.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_REPEL_DIST, IDC_FLOCK_REPEL_DIST_SPIN, 1.0,
		end,

	plParticleFlockComponent::kGoalDist,	_T("GoalDist"),	TYPE_FLOAT, 0, 0,	
		p_default, 1.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_GOAL_DIST, IDC_FLOCK_GOAL_DIST_SPIN, 1.0,
		end,

	plParticleFlockComponent::kInfAvgStr,	_T("InfAvgStr"),	TYPE_FLOAT, 0, 0,	
		p_default, 1.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_CONFORM_STR, IDC_FLOCK_CONFORM_STR_SPIN, 1.0,
		end,

	plParticleFlockComponent::kInfRepStr,	_T("InfRepStr"),	TYPE_FLOAT, 0, 0,	
		p_default, 1.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_REPEL_STR, IDC_FLOCK_REPEL_STR_SPIN, 1.0,
		end,

	plParticleFlockComponent::kGoalOrbitStr,	_T("GoalStr"),	TYPE_FLOAT, 0, 0,	
		p_default, 1.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_GOAL_STR, IDC_FLOCK_GOAL_STR_SPIN, 1.0,
		end,

	plParticleFlockComponent::kMaxChaseSpeed, _T("MaxSpeed"),	TYPE_FLOAT, 0, 0,	
		p_default, 100.0,
		p_range, 0.0, 999.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_MAX_SPEED, IDC_FLOCK_MAX_SPEED_SPIN, 1.0,
		end,		
	
	plParticleFlockComponent::kGoalChaseStr,	_T("GoalChaseStr"),	TYPE_FLOAT, 0, 0,	
		p_default, 1.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_GOAL_CHASE_STR, IDC_FLOCK_GOAL_CHASE_STR_SPIN, 1.0,
		end,
	
	plParticleFlockComponent::kMaxOrbitSpeed, _T("MaxOrbitSpeed"),	TYPE_FLOAT, 0, 0,	
		p_default, 20.0,
		p_range, 0.0, 999.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_MAX_ORBIT_SPEED, IDC_FLOCK_MAX_ORBIT_SPEED_SPIN, 1.0,
		end,		
		
	plParticleFlockComponent::kFullChaseDist,	_T("FullChaseDist"),	TYPE_FLOAT, 0, 0,	
		p_default, 1.0,
		p_range, 0.0, 100.0,
		p_ui,	TYPE_SPINNER,	EDITTYPE_POS_FLOAT,	
		IDC_FLOCK_FULL_CHASE_DIST, IDC_FLOCK_FULL_CHASE_DIST_SPIN, 1.0,
		end,
		
	end
);

plParticleFlockComponent::plParticleFlockComponent()
{
	fClassDesc = &gParticleFlockDesc;
	fClassDesc->MakeAutoParamBlocks(this);
}