|
|
|
/*==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/>.
|
|
|
|
|
|
|
|
Additional permissions under GNU GPL version 3 section 7
|
|
|
|
|
|
|
|
If you modify this Program, or any covered work, by linking or
|
|
|
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
|
|
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
|
|
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
|
|
|
(or a modified version of those libraries),
|
|
|
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
|
|
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
|
|
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
|
|
|
licensors of this Program grant you additional
|
|
|
|
permission to convey the resulting work. Corresponding Source for a
|
|
|
|
non-source form of such a combination shall include the source code for
|
|
|
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
|
|
|
work.
|
|
|
|
|
|
|
|
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 "MaxMain/MaxCompat.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 = new plDrawInterface;
|
|
|
|
hsgResMgr::ResMgr()->NewKey(IGetUniqueName(pNode), di, pNode->GetLocation(), pNode->GetLoadMask());
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify( di->GetKey(), 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_t i, j, k;
|
|
|
|
|
|
|
|
plLocation nodeLoc = node->GetKey()->GetUoid().GetLocation();
|
|
|
|
const char *objName = node->GetKey()->GetName();
|
|
|
|
|
|
|
|
plSceneObject *sObj = node->GetSceneObject();
|
|
|
|
plParticleSystem *sys = 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
float genLife = -1;
|
|
|
|
float partLifeMin, partLifeMax;
|
|
|
|
float pps = fUserInput.fPPS;
|
|
|
|
hsPoint3 pos(0, 0, 0);
|
|
|
|
float pitch = PI;
|
|
|
|
float yaw = 0;
|
|
|
|
float angleRange = fUserInput.fConeAngle * PI / 180.f;
|
|
|
|
float velMin = fUserInput.fVelocityMin;
|
|
|
|
float velMax = fUserInput.fVelocityMax;
|
|
|
|
float xSize = fUserInput.fHSize;
|
|
|
|
float ySize = fUserInput.fVSize;
|
|
|
|
float scaleMin = fUserInput.fScaleMin / 100.0f;
|
|
|
|
float scaleMax = fUserInput.fScaleMax / 100.0f;
|
|
|
|
float gravity = fUserInput.fGravity / 100.0f;
|
|
|
|
float drag = fUserInput.fDrag / 100.f;
|
|
|
|
float windMult = fUserInput.fWindMult / 100.f;
|
|
|
|
float massRange = fUserInput.fMassRange;
|
|
|
|
float rotRange = fUserInput.fRotRange * PI / 180.f;
|
|
|
|
|
|
|
|
uint32_t xTiles = fUserInput.fXTiles;
|
|
|
|
uint32_t yTiles = fUserInput.fYTiles;
|
|
|
|
uint32_t maxEmitters = 1 + GetEmitterReserve();
|
|
|
|
uint32_t 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(GetParamBlock2Controller(fCompPB, ParamID(kPPS)), node);
|
|
|
|
if (ppsCtl != nil && ppsCtl->GetLength() > 0)
|
|
|
|
{
|
|
|
|
// Simulate just the birth across the curve and record the max
|
|
|
|
float frameDelta = (1.f / MAX_FRAMES_PER_SEC);
|
|
|
|
float avgLife = (partLifeMax + partLifeMin) / 2;
|
|
|
|
uint32_t count = node->NumAttachedComponents();
|
|
|
|
uint32_t lifeTicks = avgLife / frameDelta;
|
|
|
|
float *birth = new float[lifeTicks];
|
|
|
|
|
|
|
|
// Find any anim components attached to the same node.
|
|
|
|
for (i = 0; i < count; i++)
|
|
|
|
{
|
|
|
|
if (!plAnimComponentBase::IsAnimComponent(node->GetAttachedComponent(i)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
float 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)
|
|
|
|
{
|
|
|
|
float curAnimParticles = 0;
|
|
|
|
|
|
|
|
float loopStart, loopEnd;
|
|
|
|
|
|
|
|
for (j = -1; j < (int32_t)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);
|
|
|
|
|
|
|
|
float loopLength = loopEnd - loopStart;
|
|
|
|
|
|
|
|
if (loopLength == 0) // It's the default "(Entire Animation)"
|
|
|
|
loopLength = ppsCtl->GetLength();
|
|
|
|
|
|
|
|
uint32_t loopTicks = loopLength * MAX_FRAMES_PER_SEC;
|
|
|
|
|
|
|
|
uint32_t startTick = loopStart * MAX_FRAMES_PER_SEC;
|
|
|
|
uint32_t tick;
|
|
|
|
for (tick = 0; tick < loopTicks + lifeTicks; tick++)
|
|
|
|
{
|
|
|
|
curAnimParticles -= birth[tick % lifeTicks] * frameDelta;
|
|
|
|
float birthStart = 0.f;
|
|
|
|
float 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.
|
|
|
|
{
|
|
|
|
float maxPps = 0;
|
|
|
|
int i;
|
|
|
|
for (i = 1; i < ppsCtl->GetNumKeys(); i++)
|
|
|
|
{
|
|
|
|
float 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_t)maxAnimParticles;
|
|
|
|
}
|
|
|
|
delete [] birth;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
maxTotalParticles = pps * (partLifeMax - (partLifeMax - partLifeMin) / 2);
|
|
|
|
}
|
|
|
|
maxTotalParticles *= maxEmitters;
|
|
|
|
|
|
|
|
delete ppsCtl;
|
|
|
|
ppsCtl = nil;
|
|
|
|
|
|
|
|
uint32_t 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_t sources;
|
|
|
|
float *pitchArray;
|
|
|
|
float *yawArray;
|
|
|
|
hsPoint3 *pointArray;
|
|
|
|
hsVector3 *dirArray;
|
|
|
|
if (fUserInput.fGenType == kGenPoint)
|
|
|
|
{
|
|
|
|
sources = 1;
|
|
|
|
pitchArray = new float[sources];
|
|
|
|
yawArray = new float[sources];
|
|
|
|
pointArray = new hsPoint3[sources];
|
|
|
|
pitchArray[0] = pitch;
|
|
|
|
yawArray[0] = yaw;
|
|
|
|
pointArray[0].Set(0, 0, 0);
|
|
|
|
plSimpleParticleGenerator *gen = 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 = new float[sources];
|
|
|
|
yawArray = new float[sources];
|
|
|
|
pointArray = 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 = 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 = new hsPoint3[sources];
|
|
|
|
dirArray = new hsVector3[sources];
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < sources; i++)
|
|
|
|
{
|
|
|
|
dirArray[i] = normals.Get(i);
|
|
|
|
pointArray[i] = pos.Get(i);
|
|
|
|
}
|
|
|
|
|
|
|
|
plOneTimeParticleGenerator *gen = 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(), new plGenRefMsg( sys->GetKey(), plRefMsg::kOnCreate, 0, 0 ), plRefFlags::kActiveRef );
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify( sys->GetKey(), new plObjRefMsg( sObj->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier ), plRefFlags::kActiveRef );
|
|
|
|
|
|
|
|
// Set up normals and orientation
|
|
|
|
uint32_t 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 = new plParticleFollowSystemEffect;
|
|
|
|
hsgResMgr::ResMgr()->NewKey(IGetUniqueName(node), effect, node->GetLocation(), node->GetLoadMask());
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify( effect->GetKey(), 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();
|
|
|
|
|
|
|
|
float 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(GetParamBlock2Controller(fCompPB, ParamID(kLifeMin)), node, start, end);
|
|
|
|
if (ctl != nil)
|
|
|
|
{
|
|
|
|
plParticleLifeMinApplicator *app = new plParticleLifeMinApplicator();
|
|
|
|
app->SetChannelName(node->GetName());
|
|
|
|
plAnimComponentBase::SetupCtl(anim, ctl, app, node);
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctl = cc.MakeScalarController(GetParamBlock2Controller(fCompPB, ParamID(kLifeMax)), node, start, end);
|
|
|
|
if (ctl != nil)
|
|
|
|
{
|
|
|
|
plParticleLifeMaxApplicator *app = new plParticleLifeMaxApplicator();
|
|
|
|
app->SetChannelName(node->GetName());
|
|
|
|
plAnimComponentBase::SetupCtl(anim, ctl, app, node);
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctl = cc.MakeScalarController(GetParamBlock2Controller(fCompPB, ParamID(kPPS)), node, start, end);
|
|
|
|
if (ctl != nil)
|
|
|
|
{
|
|
|
|
plParticlePPSApplicator *app = new plParticlePPSApplicator();
|
|
|
|
app->SetChannelName(node->GetName());
|
|
|
|
plAnimComponentBase::SetupCtl(anim, ctl, app, node);
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctl = cc.MakeScalarController(GetParamBlock2Controller(fCompPB, ParamID(kConeAngle)), node, start, end);
|
|
|
|
if (ctl != nil)
|
|
|
|
{
|
|
|
|
plParticleAngleApplicator *app = new plParticleAngleApplicator();
|
|
|
|
app->SetChannelName(node->GetName());
|
|
|
|
plAnimComponentBase::SetupCtl(anim, ctl, app, node);
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctl = cc.MakeScalarController(GetParamBlock2Controller(fCompPB, ParamID(kVelocityMin)), node, start, end);
|
|
|
|
if (ctl != nil)
|
|
|
|
{
|
|
|
|
plParticleVelMinApplicator *app = new plParticleVelMinApplicator();
|
|
|
|
app->SetChannelName(node->GetName());
|
|
|
|
plAnimComponentBase::SetupCtl(anim, ctl, app, node);
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctl = cc.MakeScalarController(GetParamBlock2Controller(fCompPB, ParamID(kVelocityMax)), node, start, end);
|
|
|
|
if (ctl != nil)
|
|
|
|
{
|
|
|
|
plParticleVelMaxApplicator *app = new plParticleVelMaxApplicator();
|
|
|
|
app->SetChannelName(node->GetName());
|
|
|
|
plAnimComponentBase::SetupCtl(anim, ctl, app, node);
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
ctl = cc.MakeScalarController(GetParamBlock2Controller(fCompPB, ParamID(kGravity)), node, start, end);
|
|
|
|
if (ctl != nil)
|
|
|
|
{
|
|
|
|
plParticleGravityApplicator *app = new plParticleGravityApplicator();
|
|
|
|
plAnimComponentBase::SetupCtl(anim, ctl, app, node);
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctl = cc.MakeScalarController(GetParamBlock2Controller(fCompPB, ParamID(kDrag)), node, start, end);
|
|
|
|
if (ctl != nil)
|
|
|
|
{
|
|
|
|
plParticleDragApplicator *app = new plParticleDragApplicator();
|
|
|
|
plAnimComponentBase::SetupCtl(anim, ctl, app, node);
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
*/
|
|
|
|
}
|
|
|
|
|
|
|
|
ctl = cc.MakeScalarController(GetParamBlock2Controller(fCompPB, ParamID(kScaleMin)), node, start, end);
|
|
|
|
if (ctl != nil)
|
|
|
|
{
|
|
|
|
plParticleScaleMinApplicator *app = new plParticleScaleMinApplicator();
|
|
|
|
app->SetChannelName(node->GetName());
|
|
|
|
plAnimComponentBase::SetupCtl(anim, ctl, app, node);
|
|
|
|
result = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
ctl = cc.MakeScalarController(GetParamBlock2Controller(fCompPB, ParamID(kScaleMax)), node, start, end);
|
|
|
|
if (ctl != nil)
|
|
|
|
{
|
|
|
|
plParticleScaleMaxApplicator *app = 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 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_t id)
|
|
|
|
{
|
|
|
|
uint32_t 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virtual 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;
|
|
|
|
}
|
|
|
|
virtual void DeleteThis() {}
|
|
|
|
};
|
|
|
|
static ParticleCompDlgProc gParticleCompDlgProc;
|
|
|
|
|
|
|
|
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, 0, 0,
|
|
|
|
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, 0, 0,
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// 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 = 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(), 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 = new plBoundInterface;
|
|
|
|
hsgResMgr::ResMgr()->NewKey(node->GetName(), fBound, node->GetLocation(), node->GetLoadMask());
|
|
|
|
fBound->Init(plMeshConverter::Instance().CreateConvexVolume(node));
|
|
|
|
hsgResMgr::ResMgr()->AddViaNotify(fBound->GetKey(), 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 = new plParticleCollisionEffectBeat();
|
|
|
|
break;
|
|
|
|
case kImpDie:
|
|
|
|
effect = new plParticleCollisionEffectDie();
|
|
|
|
break;
|
|
|
|
case kImpBounce:
|
|
|
|
{
|
|
|
|
plParticleCollisionEffectBounce* bnc = 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(), 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(), 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 = hsDegreesToRadians(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 = 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(), 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, 0, 0,
|
|
|
|
p_default, FALSE,
|
|
|
|
p_ui, TYPE_SINGLECHEKBOX, IDC_COMP_PARTICLE_WIND_HORIZONTAL,
|
|
|
|
end,
|
|
|
|
|
|
|
|
plParticleWindComponent::kLocalize, _T("Localize"), TYPE_BOOL, 0, 0,
|
|
|
|
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 = 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(), 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, 0, 0,
|
|
|
|
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_t id)
|
|
|
|
{
|
|
|
|
uint32_t 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 = 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)));
|
|
|
|
|
|
|
|
float goalDist = fCompPB->GetFloat(ParamID(kGoalDist));
|
|
|
|
float 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(), 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);
|
|
|
|
}
|
|
|
|
|