|
|
|
/*==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 "hsTypes.h"
|
|
|
|
#include "plParticleSystem.h"
|
|
|
|
#include "plParticleEmitter.h"
|
|
|
|
#include "plParticleGenerator.h"
|
|
|
|
#include "plParticleEffect.h"
|
|
|
|
#include "plParticle.h"
|
|
|
|
#include "plParticleSDLMod.h"
|
|
|
|
#include "plgDispatch.h"
|
|
|
|
#include "hsResMgr.h"
|
|
|
|
|
|
|
|
#include "pnSceneObject/plSceneObject.h"
|
|
|
|
#include "pnSceneObject/plDrawInterface.h"
|
|
|
|
#include "pnSceneObject/plCoordinateInterface.h"
|
|
|
|
#include "pnMessage/plTimeMsg.h"
|
|
|
|
#include "plMessage/plRenderMsg.h"
|
|
|
|
#include "plMessage/plAgeLoadedMsg.h"
|
|
|
|
#include "plMessage/plParticleUpdateMsg.h"
|
|
|
|
#include "plInterp/plController.h"
|
|
|
|
#include "plSurface/hsGMaterial.h"
|
|
|
|
#include "plPipeline.h"
|
|
|
|
#include "hsTimer.h"
|
|
|
|
#include "plProfile.h"
|
|
|
|
#include "plTweak.h"
|
|
|
|
|
|
|
|
#include "plDrawable/plParticleFiller.h"
|
|
|
|
|
|
|
|
plProfile_CreateCounter("Num Particles", "Particles", NumParticles);
|
|
|
|
|
|
|
|
const hsScalar plParticleSystem::GRAVITY_ACCEL_FEET_PER_SEC2 = 32.0f;
|
|
|
|
|
|
|
|
plParticleSystem::plParticleSystem() : fParticleSDLMod(nil), fAttachedToAvatar(false)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
plParticleSystem::~plParticleSystem()
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fNumValidEmitters; i++)
|
|
|
|
{
|
|
|
|
delete fEmitters[i];
|
|
|
|
}
|
|
|
|
delete [] fEmitters;
|
|
|
|
|
|
|
|
delete fAmbientCtl;
|
|
|
|
delete fDiffuseCtl;
|
|
|
|
delete fOpacityCtl;
|
|
|
|
delete fWidthCtl;
|
|
|
|
delete fHeightCtl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::Init(UInt32 xTiles, UInt32 yTiles, UInt32 maxTotalParticles, UInt32 maxEmitters,
|
|
|
|
plController *ambientCtl, plController *diffuseCtl, plController *opacityCtl,
|
|
|
|
plController *widthCtl, plController *heightCtl)
|
|
|
|
{
|
|
|
|
fTarget = nil;
|
|
|
|
fLastTime = 0;
|
|
|
|
fCurrTime = 0;
|
|
|
|
SetGravity(1.0f);
|
|
|
|
fDrag = 0;
|
|
|
|
fWindMult = 1.f;
|
|
|
|
|
|
|
|
fNumValidEmitters = 0;
|
|
|
|
fNextEmitterToGo = 0;
|
|
|
|
fMiscFlags = 0;
|
|
|
|
|
|
|
|
fForces.Reset();
|
|
|
|
fEffects.Reset();
|
|
|
|
fConstraints.Reset();
|
|
|
|
|
|
|
|
fXTiles = xTiles;
|
|
|
|
fYTiles = yTiles;
|
|
|
|
|
|
|
|
fMaxTotalParticles = fMaxTotalParticlesLeft = maxTotalParticles;
|
|
|
|
fMaxEmitters = maxEmitters;
|
|
|
|
fEmitters = TRACKED_NEW plParticleEmitter *[fMaxEmitters];
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < maxEmitters; i++)
|
|
|
|
fEmitters[i] = nil;
|
|
|
|
|
|
|
|
fAmbientCtl = ambientCtl;
|
|
|
|
fDiffuseCtl = diffuseCtl;
|
|
|
|
fOpacityCtl = opacityCtl;
|
|
|
|
fWidthCtl = widthCtl;
|
|
|
|
fHeightCtl = heightCtl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::IAddEffect(plParticleEffect *effect, UInt32 type)
|
|
|
|
{
|
|
|
|
switch(type)
|
|
|
|
{
|
|
|
|
case kEffectForce:
|
|
|
|
fForces.Append(effect);
|
|
|
|
break;
|
|
|
|
case kEffectMisc:
|
|
|
|
default:
|
|
|
|
fEffects.Append(effect);
|
|
|
|
break;
|
|
|
|
case kEffectConstraint:
|
|
|
|
fConstraints.Append(effect);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
plParticleEmitter* plParticleSystem::GetAvailEmitter()
|
|
|
|
{
|
|
|
|
if( !fNumValidEmitters ) // got to start with at least one.
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
hsScalar minTTL = 1.e33;
|
|
|
|
int iMinTTL = -1;
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fNumValidEmitters; i++ )
|
|
|
|
{
|
|
|
|
if( fEmitters[i]->GetTimeToLive() < minTTL )
|
|
|
|
{
|
|
|
|
minTTL = fEmitters[i]->GetTimeToLive();
|
|
|
|
iMinTTL = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if( minTTL > 0 )
|
|
|
|
{
|
|
|
|
if( fNumValidEmitters < fMaxEmitters )
|
|
|
|
{
|
|
|
|
minTTL = 0;
|
|
|
|
iMinTTL = fNumValidEmitters++;
|
|
|
|
fEmitters[iMinTTL] = TRACKED_NEW plParticleEmitter();
|
|
|
|
fEmitters[iMinTTL]->Clone(fEmitters[0], iMinTTL);
|
|
|
|
|
|
|
|
fMaxTotalParticlesLeft -= fEmitters[iMinTTL]->fMaxParticles;
|
|
|
|
hsAssert(fMaxTotalParticlesLeft >= 0, "Should have planned better");
|
|
|
|
|
|
|
|
// Don't really use this. fEmitters[i]->GetSpanIndex() always == i.
|
|
|
|
fNextEmitterToGo = (fNextEmitterToGo + 1) % fMaxEmitters;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fEmitters[iMinTTL];
|
|
|
|
}
|
|
|
|
|
|
|
|
UInt32 plParticleSystem::AddEmitter(UInt32 maxParticles, plParticleGenerator *gen, UInt32 emitterFlags)
|
|
|
|
{
|
|
|
|
if (fMaxEmitters == 0) // silly rabbit, Trix are for kids!
|
|
|
|
return 0;
|
|
|
|
UInt32 currEmitter;
|
|
|
|
if (fNumValidEmitters == fMaxEmitters) // No more free spots, snag the next in line.
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fMaxEmitters; i++)
|
|
|
|
{
|
|
|
|
if (fEmitters[i]->GetSpanIndex() == fNextEmitterToGo)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
currEmitter = i;
|
|
|
|
|
|
|
|
fMaxTotalParticlesLeft += fEmitters[currEmitter]->fMaxParticles;
|
|
|
|
hsAssert(fMaxTotalParticlesLeft <= fMaxTotalParticles, "Particle system somehow has more particles than it started with.");
|
|
|
|
delete fEmitters[currEmitter];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
currEmitter = fNumValidEmitters;
|
|
|
|
fNumValidEmitters++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (maxParticles > fMaxTotalParticlesLeft)
|
|
|
|
maxParticles = fMaxTotalParticlesLeft;
|
|
|
|
if (maxParticles < 0)
|
|
|
|
maxParticles = 0;
|
|
|
|
|
|
|
|
fEmitters[currEmitter] = TRACKED_NEW plParticleEmitter();
|
|
|
|
fEmitters[currEmitter]->Init(this, maxParticles, fNextEmitterToGo, emitterFlags, gen);
|
|
|
|
|
|
|
|
fMaxTotalParticlesLeft -= fEmitters[currEmitter]->fMaxParticles;
|
|
|
|
fNextEmitterToGo = (fNextEmitterToGo + 1) % fMaxEmitters;
|
|
|
|
|
|
|
|
return maxParticles;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::AddParticle(hsPoint3 &pos, hsVector3 &velocity, UInt32 tileIndex,
|
|
|
|
hsScalar hSize, hsScalar vSize, hsScalar scale, hsScalar invMass, hsScalar life,
|
|
|
|
hsPoint3 &orientation, UInt32 miscFlags, hsScalar radsPerSec)
|
|
|
|
{
|
|
|
|
hsAssert(fNumValidEmitters > 0, "Trying to explicitly add particles to a system with no valid emitters.");
|
|
|
|
if (fNumValidEmitters == 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
fEmitters[0]->AddParticle(pos, velocity, tileIndex, hSize, vSize, scale, invMass, life, orientation, miscFlags, radsPerSec);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::GenerateParticles(UInt32 num, hsScalar dt /* = 0.f */)
|
|
|
|
{
|
|
|
|
if (num <= 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (fNumValidEmitters > 0 && fEmitters[0]->fGenerator)
|
|
|
|
fEmitters[0]->fGenerator->AddAutoParticles(fEmitters[0], dt, num);
|
|
|
|
|
|
|
|
GetTarget(0)->DirtySynchState(kSDLParticleSystem, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::WipeExistingParticles()
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fNumValidEmitters; i++)
|
|
|
|
fEmitters[i]->WipeExistingParticles();
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::KillParticles(hsScalar num, hsScalar timeToDie, UInt8 flags)
|
|
|
|
{
|
|
|
|
if (fEmitters[0])
|
|
|
|
fEmitters[0]->KillParticles(num, timeToDie, flags);
|
|
|
|
|
|
|
|
GetTarget(0)->DirtySynchState(kSDLParticleSystem, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::TranslateAllParticles(hsPoint3 &amount)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fNumValidEmitters; i++)
|
|
|
|
fEmitters[i]->TranslateAllParticles(amount);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::DisableGenerators()
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fNumValidEmitters; i++)
|
|
|
|
fEmitters[i]->UpdateGenerator(plParticleUpdateMsg::kParamEnabled, 0.f);
|
|
|
|
}
|
|
|
|
|
|
|
|
UInt16 plParticleSystem::StealParticlesFrom(plParticleSystem *victim, UInt16 num)
|
|
|
|
{
|
|
|
|
if (fNumValidEmitters <= 0)
|
|
|
|
return 0; // you just lose
|
|
|
|
|
|
|
|
if (victim)
|
|
|
|
{
|
|
|
|
UInt16 numStolen = fEmitters[0]->StealParticlesFrom(victim->fNumValidEmitters > 0 ? victim->fEmitters[0] : nil, num);
|
|
|
|
GetTarget(0)->DirtySynchState(kSDLParticleSystem, 0);
|
|
|
|
victim->GetTarget(0)->DirtySynchState(kSDLParticleSystem, 0);
|
|
|
|
return numStolen;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
plParticleGenerator *plParticleSystem::GetExportedGenerator() const
|
|
|
|
{
|
|
|
|
return (fNumValidEmitters > 0 ? fEmitters[0]->fGenerator : nil);
|
|
|
|
}
|
|
|
|
|
|
|
|
plParticleEffect *plParticleSystem::GetEffect(UInt16 type) const
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fForces.GetCount(); i++)
|
|
|
|
if (fForces[i]->ClassIndex() == type)
|
|
|
|
return fForces[i];
|
|
|
|
|
|
|
|
for (i = 0; i < fEffects.GetCount(); i++)
|
|
|
|
if (fEffects[i]->ClassIndex() == type)
|
|
|
|
return fEffects[i];
|
|
|
|
|
|
|
|
for (i = 0; i < fConstraints.GetCount(); i++)
|
|
|
|
if (fConstraints[i]->ClassIndex() == type)
|
|
|
|
return fConstraints[i];
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
UInt32 plParticleSystem::GetNumValidParticles(hsBool immortalOnly /* = false */) const
|
|
|
|
{
|
|
|
|
UInt32 count = 0;
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < fNumValidEmitters; i++)
|
|
|
|
{
|
|
|
|
if (immortalOnly)
|
|
|
|
{
|
|
|
|
for (j = 0; j < fEmitters[i]->fNumValidParticles; j++)
|
|
|
|
{
|
|
|
|
if (fEmitters[i]->fParticleExts[j].fMiscFlags & plParticleExt::kImmortal)
|
|
|
|
count++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
count += fEmitters[i]->fNumValidParticles;
|
|
|
|
}
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const hsMatrix44 &plParticleSystem::GetLocalToWorld() const
|
|
|
|
{
|
|
|
|
return fTarget->GetCoordinateInterface()->GetLocalToWorld();
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool plParticleSystem::IEval(double secs, hsScalar del, UInt32 dirty)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hsBool plParticleSystem::IShouldUpdate(plPipeline* pipe) const
|
|
|
|
{
|
|
|
|
|
|
|
|
if (fMiscFlags & kParticleSystemAlwaysUpdate)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if (IGetTargetDrawInterface(0) && IGetTargetDrawInterface(0)->GetProperty(plDrawInterface::kDisable))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// First, what are the cumulative bounds for this system.
|
|
|
|
hsBounds3Ext wBnd;
|
|
|
|
wBnd.MakeEmpty();
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < fNumValidEmitters; i++ )
|
|
|
|
{
|
|
|
|
if( fEmitters[i]->GetBoundingBox().GetType() == kBoundsNormal )
|
|
|
|
wBnd.Union(&fEmitters[i]->GetBoundingBox());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Always update if we are currently empty
|
|
|
|
if( wBnd.GetType() == kBoundsEmpty )
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, are we visible?
|
|
|
|
hsBool isVisible = pipe->TestVisibleWorld(wBnd);
|
|
|
|
|
|
|
|
hsScalar delta = fLastTime > 0 ? hsScalar(fCurrTime - fLastTime) : hsTimer::GetDelSysSeconds();
|
|
|
|
if( isVisible )
|
|
|
|
{
|
|
|
|
// If we know how fast the fastest particle is moving, then we can
|
|
|
|
// decide if the system is too far away to need to update every single frame.
|
|
|
|
// In fact, based on the speed of the fastest particle, we can determine an
|
|
|
|
// exact update rate for a given error (say 3 pixels?).
|
|
|
|
// But we don't currently know the speed of the fastest particle, so
|
|
|
|
// until we do, I'm commenting this out. Most of the speed gain from
|
|
|
|
// culling particle updates was from not updating systems that aren't visible
|
|
|
|
// anyway.
|
|
|
|
#if 0 // ALWAYS_IF_VISIBLE
|
|
|
|
// We're in view, but how close are we to the camera? Look at closest point.
|
|
|
|
hsPoint2 depth;
|
|
|
|
wBnd.TestPlane(pipe->GetViewDirWorld(), depth);
|
|
|
|
hsScalar eyeDist = pipe->GetViewDirWorld().InnerProduct(pipe->GetViewPositionWorld());
|
|
|
|
hsScalar dist = depth.fX - eyeDist;
|
|
|
|
|
|
|
|
static hsScalar kUpdateCutoffDist = 100.f;
|
|
|
|
if( dist > kUpdateCutoffDist )
|
|
|
|
{
|
|
|
|
static hsScalar kDistantUpdateSecs = 0.1f;
|
|
|
|
return delta >= kDistantUpdateSecs;
|
|
|
|
}
|
|
|
|
#endif // ALWAYS_IF_VISIBLE
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static hsScalar kOffscreenUpdateSecs = 1.f;
|
|
|
|
return delta >= kOffscreenUpdateSecs;
|
|
|
|
}
|
|
|
|
|
|
|
|
plDrawInterface* plParticleSystem::ICheckDrawInterface()
|
|
|
|
{
|
|
|
|
plDrawInterface* di = IGetTargetDrawInterface(0);
|
|
|
|
if( !di )
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
if( di->GetDrawableMeshIndex(0) == UInt32(-1) )
|
|
|
|
{
|
|
|
|
di->SetUpForParticleSystem( fMaxEmitters + 1, fMaxTotalParticles, fTexture, fPermaLights );
|
|
|
|
hsAssert(di->GetDrawableMeshIndex( 0 ) != (UInt32)-1, "SetUpForParticleSystem should never fail"); // still invalid, didn't fix it.
|
|
|
|
}
|
|
|
|
|
|
|
|
return di;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::IHandleRenderMsg(plPipeline* pipe)
|
|
|
|
{
|
|
|
|
fCurrTime = hsTimer::GetSysSeconds();
|
|
|
|
hsScalar delta = hsScalar(fCurrTime - fLastTime);
|
|
|
|
if (delta == 0)
|
|
|
|
return;
|
|
|
|
plConst(hsScalar) kMaxDelta(0.3f);
|
|
|
|
if( delta > kMaxDelta )
|
|
|
|
delta = kMaxDelta;
|
|
|
|
|
|
|
|
plDrawInterface* di = ICheckDrawInterface();
|
|
|
|
if( !di )
|
|
|
|
return;
|
|
|
|
|
|
|
|
hsBool disabled = di->GetProperty(plDrawInterface::kDisable);
|
|
|
|
if (!IShouldUpdate(pipe))
|
|
|
|
{
|
|
|
|
if (disabled)
|
|
|
|
di->ResetParticleSystem(); // Need to call this, otherwise particles get drawn, even though the DI is disabled.
|
|
|
|
// (Yes, it's lame.)
|
|
|
|
|
|
|
|
// Otherwise, we leave the DI alone, and the particles draw in the same place they were last frame.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
fContext.fPipeline = pipe;
|
|
|
|
fContext.fSystem = this;
|
|
|
|
fContext.fSecs = fCurrTime;
|
|
|
|
fContext.fDelSecs = delta;
|
|
|
|
|
|
|
|
di->ResetParticleSystem();
|
|
|
|
|
|
|
|
if (fPreSim > 0)
|
|
|
|
IPreSim();
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fNumValidEmitters; i++)
|
|
|
|
{
|
|
|
|
fEmitters[i]->IUpdate(delta);
|
|
|
|
plProfile_IncCount(NumParticles, fEmitters[i]->fNumValidParticles);
|
|
|
|
if (!disabled)
|
|
|
|
{
|
|
|
|
if( fEmitters[ i ]->GetParticleCount() > 0 )
|
|
|
|
di->AssignEmitterToParticleSystem( fEmitters[ i ] ); // Go make those polys!
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// plParticleFiller::FillParticlePolys(pipe, di);
|
|
|
|
|
|
|
|
fLastTime = fCurrTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#include "plProfile.h"
|
|
|
|
plProfile_CreateTimer("ParticleSys", "RenderSetup", ParticleSys);
|
|
|
|
|
|
|
|
|
|
|
|
hsBool plParticleSystem::MsgReceive(plMessage* msg)
|
|
|
|
{
|
|
|
|
plGenRefMsg* refMsg;
|
|
|
|
plParticleUpdateMsg *partMsg;
|
|
|
|
plParticleKillMsg *killMsg;
|
|
|
|
plSceneObject *scene;
|
|
|
|
plParticleEffect *effect;
|
|
|
|
hsGMaterial *mat;
|
|
|
|
plRenderMsg *rend;
|
|
|
|
plAgeLoadedMsg* ageLoaded;
|
|
|
|
|
|
|
|
if (rend = plRenderMsg::ConvertNoRef(msg))
|
|
|
|
{
|
|
|
|
plProfile_BeginLap(ParticleSys, this->GetKey()->GetUoid().GetObjectName());
|
|
|
|
IHandleRenderMsg(rend->Pipeline());
|
|
|
|
plProfile_EndLap(ParticleSys, this->GetKey()->GetUoid().GetObjectName());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (refMsg = plGenRefMsg::ConvertNoRef(msg))
|
|
|
|
{
|
|
|
|
if (scene = plSceneObject::ConvertNoRef(refMsg->GetRef()))
|
|
|
|
{
|
|
|
|
if (refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace))
|
|
|
|
AddTarget(scene);
|
|
|
|
else
|
|
|
|
RemoveTarget(scene);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (mat = hsGMaterial::ConvertNoRef(refMsg->GetRef()))
|
|
|
|
{
|
|
|
|
if (refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace))
|
|
|
|
fTexture = mat;
|
|
|
|
else
|
|
|
|
fTexture = nil;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (effect = plParticleEffect::ConvertNoRef(refMsg->GetRef()))
|
|
|
|
{
|
|
|
|
if (refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace))
|
|
|
|
IAddEffect(effect, refMsg->fType);
|
|
|
|
//else
|
|
|
|
// IRemoveEffect(effect, refMsg->fType);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (partMsg = plParticleUpdateMsg::ConvertNoRef(msg))
|
|
|
|
{
|
|
|
|
UpdateGenerator(partMsg->GetParamID(), partMsg->GetParamValue());
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (killMsg = plParticleKillMsg::ConvertNoRef(msg))
|
|
|
|
{
|
|
|
|
KillParticles(killMsg->fNumToKill, killMsg->fTimeLeft, killMsg->fFlags);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if( (ageLoaded = plAgeLoadedMsg::ConvertNoRef(msg)) && ageLoaded->fLoaded )
|
|
|
|
{
|
|
|
|
ICheckDrawInterface();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return plModifier::MsgReceive(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::UpdateGenerator(UInt32 paramID, hsScalar value)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fNumValidEmitters; i++)
|
|
|
|
fEmitters[i]->UpdateGenerator(paramID, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::AddTarget(plSceneObject *so)
|
|
|
|
{
|
|
|
|
if (fTarget != nil)
|
|
|
|
RemoveTarget(fTarget);
|
|
|
|
|
|
|
|
fTarget = so;
|
|
|
|
plgDispatch::Dispatch()->RegisterForExactType(plTimeMsg::Index(), GetKey());
|
|
|
|
plgDispatch::Dispatch()->RegisterForExactType(plRenderMsg::Index(), GetKey());
|
|
|
|
plgDispatch::Dispatch()->RegisterForExactType(plAgeLoadedMsg::Index(), GetKey());
|
|
|
|
|
|
|
|
delete fParticleSDLMod;
|
|
|
|
fParticleSDLMod = TRACKED_NEW plParticleSDLMod;
|
|
|
|
fParticleSDLMod->SetAttachedToAvatar(fAttachedToAvatar);
|
|
|
|
so->AddModifier(fParticleSDLMod);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::RemoveTarget(plSceneObject *so)
|
|
|
|
{
|
|
|
|
if (so == fTarget && so != nil)
|
|
|
|
{
|
|
|
|
if (fParticleSDLMod)
|
|
|
|
{
|
|
|
|
so->RemoveModifier(fParticleSDLMod);
|
|
|
|
delete fParticleSDLMod;
|
|
|
|
fParticleSDLMod = nil;
|
|
|
|
}
|
|
|
|
fTarget = nil;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// This can be done much faster, but it's only done on load, and very very clean as is. Saving optimization for
|
|
|
|
// when we observe that it's too slow.
|
|
|
|
void plParticleSystem::IPreSim()
|
|
|
|
{
|
|
|
|
const double PRESIM_UPDATE_TICK = 0.1;
|
|
|
|
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fNumValidEmitters; i++)
|
|
|
|
{
|
|
|
|
double secs = fPreSim;
|
|
|
|
while (secs > 0)
|
|
|
|
{
|
|
|
|
fEmitters[i]->IUpdateParticles((hsScalar)PRESIM_UPDATE_TICK);
|
|
|
|
secs -= PRESIM_UPDATE_TICK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fPreSim = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::IReadEffectsArray(hsTArray<plParticleEffect *> &effects, UInt32 type, hsStream *s, hsResMgr *mgr)
|
|
|
|
{
|
|
|
|
plGenRefMsg *msg;
|
|
|
|
effects.Reset();
|
|
|
|
UInt32 count = s->ReadSwap32();
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < count; i++)
|
|
|
|
{
|
|
|
|
msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, (Int8)type);
|
|
|
|
mgr->ReadKeyNotifyMe(s, msg, plRefFlags::kActiveRef);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::Read(hsStream *s, hsResMgr *mgr)
|
|
|
|
{
|
|
|
|
plModifier::Read(s, mgr);
|
|
|
|
|
|
|
|
plGenRefMsg* msg;
|
|
|
|
msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, 0); // Material
|
|
|
|
mgr->ReadKeyNotifyMe(s, msg, plRefFlags::kActiveRef);
|
|
|
|
|
|
|
|
fAmbientCtl = plController::ConvertNoRef(mgr->ReadCreatable(s));
|
|
|
|
fDiffuseCtl = plController::ConvertNoRef(mgr->ReadCreatable(s));
|
|
|
|
fOpacityCtl = plController::ConvertNoRef(mgr->ReadCreatable(s));
|
|
|
|
fWidthCtl = plController::ConvertNoRef(mgr->ReadCreatable(s));
|
|
|
|
fHeightCtl = plController::ConvertNoRef(mgr->ReadCreatable(s));
|
|
|
|
|
|
|
|
UInt32 xTiles = s->ReadSwap32();
|
|
|
|
UInt32 yTiles = s->ReadSwap32();
|
|
|
|
UInt32 maxTotal = s->ReadSwap32();
|
|
|
|
UInt32 maxEmitters = s->ReadSwap32();
|
|
|
|
Init(xTiles, yTiles, maxTotal, maxEmitters, fAmbientCtl, fDiffuseCtl, fOpacityCtl, fWidthCtl, fHeightCtl);
|
|
|
|
|
|
|
|
fPreSim = s->ReadSwapScalar();
|
|
|
|
fAccel.Read(s);
|
|
|
|
fDrag = s->ReadSwapScalar();
|
|
|
|
fWindMult = s->ReadSwapScalar();
|
|
|
|
|
|
|
|
fNumValidEmitters = s->ReadSwap32();
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < fNumValidEmitters; i++)
|
|
|
|
{
|
|
|
|
fEmitters[i] = plParticleEmitter::ConvertNoRef(mgr->ReadCreatable(s));
|
|
|
|
fEmitters[i]->ISetSystem(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
IReadEffectsArray(fForces, kEffectForce, s, mgr);
|
|
|
|
IReadEffectsArray(fEffects, kEffectMisc, s, mgr);
|
|
|
|
IReadEffectsArray(fConstraints, kEffectConstraint, s, mgr);
|
|
|
|
|
|
|
|
int count = s->ReadSwap32();
|
|
|
|
fPermaLights.SetCount(count);
|
|
|
|
for( i = 0; i < count; i++ )
|
|
|
|
{
|
|
|
|
fPermaLights[i] = mgr->ReadKey(s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::Write(hsStream *s, hsResMgr *mgr)
|
|
|
|
{
|
|
|
|
plModifier::Write(s, mgr);
|
|
|
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
mgr->WriteKey(s, fTexture);
|
|
|
|
|
|
|
|
// Arguments to the Init() function
|
|
|
|
mgr->WriteCreatable(s, fAmbientCtl);
|
|
|
|
mgr->WriteCreatable(s, fDiffuseCtl);
|
|
|
|
mgr->WriteCreatable(s, fOpacityCtl);
|
|
|
|
mgr->WriteCreatable(s, fWidthCtl);
|
|
|
|
mgr->WriteCreatable(s, fHeightCtl);
|
|
|
|
|
|
|
|
s->WriteSwap32(fXTiles);
|
|
|
|
s->WriteSwap32(fYTiles);
|
|
|
|
s->WriteSwap32(fMaxTotalParticles);
|
|
|
|
s->WriteSwap32(fMaxEmitters);
|
|
|
|
|
|
|
|
s->WriteSwapScalar(fPreSim);
|
|
|
|
fAccel.Write(s);
|
|
|
|
s->WriteSwapScalar(fDrag);
|
|
|
|
s->WriteSwapScalar(fWindMult);
|
|
|
|
|
|
|
|
s->WriteSwap32(fNumValidEmitters);
|
|
|
|
for (i = 0; i < fNumValidEmitters; i++)
|
|
|
|
{
|
|
|
|
mgr->WriteCreatable(s, fEmitters[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
int count;
|
|
|
|
count = fForces.GetCount();
|
|
|
|
s->WriteSwap32(count);
|
|
|
|
for (i = 0; i < count; i++)
|
|
|
|
mgr->WriteKey(s, fForces.Get(i));
|
|
|
|
|
|
|
|
count = fEffects.GetCount();
|
|
|
|
s->WriteSwap32(count);
|
|
|
|
for (i = 0; i < count; i++)
|
|
|
|
mgr->WriteKey(s, fEffects.Get(i));
|
|
|
|
|
|
|
|
count = fConstraints.GetCount();
|
|
|
|
s->WriteSwap32(count);
|
|
|
|
for (i = 0; i < count; i++)
|
|
|
|
mgr->WriteKey(s, fConstraints.Get(i));
|
|
|
|
|
|
|
|
count = fPermaLights.GetCount();
|
|
|
|
s->WriteSwap32(count);
|
|
|
|
for( i = 0; i < count; i++ )
|
|
|
|
mgr->WriteKey(s, fPermaLights[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::SetAttachedToAvatar(bool attached)
|
|
|
|
{
|
|
|
|
fAttachedToAvatar = attached;
|
|
|
|
if (fParticleSDLMod)
|
|
|
|
fParticleSDLMod->SetAttachedToAvatar(attached);
|
|
|
|
}
|
|
|
|
|
|
|
|
void plParticleSystem::AddLight(plKey liKey)
|
|
|
|
{
|
|
|
|
fPermaLights.Append(liKey);
|
|
|
|
}
|