/*==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 .
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
#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 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 normals;
hsTArray 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 normals;
hsTArray 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& 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 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);
}
}
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 = 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, 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 = 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, 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 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);
}