You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1612 lines
54 KiB

/*==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);
}