1
0
mirror of https://foundry.openuru.org/gitblit/r/CWE-ou-minkata.git synced 2025-07-18 19:29:09 +00:00

Initial Commit of CyanWorlds.com Engine Open Source Client/Plugin

This commit is contained in:
jwplatt
2011-03-12 12:34:52 -05:00
commit b970ae4bad
3976 changed files with 1301355 additions and 0 deletions

View File

@ -0,0 +1,76 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "hsGeometry3.h"
#include "plBoundInterface.h"
#include "plConvexVolume.h"
#include "hsResMgr.h"
plBoundInterface::plBoundInterface() : fBounds(nil)
{
}
plBoundInterface::~plBoundInterface()
{
ReleaseData();
}
void plBoundInterface::ReleaseData()
{
delete fBounds;
fBounds = nil;
}
void plBoundInterface::Init(plConvexVolume *bounds)
{
ReleaseData();
fBounds = bounds;
}
// Right now, this is ignoring the enabled property of ObjInterface, since I'm not aware that
// anything ever makes use of it (and if nothing does, this saves us on some needless matrix
// copying). Should we make use of the disabled prop, this function should just store the l2w
// matrix, but not send an update to fBounds.
void plBoundInterface::SetTransform(const hsMatrix44 &l2w, const hsMatrix44&w2l)
{
if (fBounds != nil)
fBounds->Update(l2w);
}
void plBoundInterface::Read(hsStream* s, hsResMgr* mgr)
{
plObjInterface::Read(s, mgr);
fBounds = plConvexVolume::ConvertNoRef(mgr->ReadCreatable(s));
//mgr->ReadKeyNotifyMe(s, new plIntRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plIntRefMsg::kOwner), plRefFlags::kPassiveRef);
}
void plBoundInterface::Write(hsStream* s, hsResMgr* mgr)
{
plObjInterface::Write(s, mgr);
mgr->WriteCreatable(s, fBounds);
//mgr->WriteKey(s, fBounds);
}
// No need to save/load. The coordinate interface on our sceneObject will update us.

View File

@ -0,0 +1,67 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plBoundInterface_inc
#define plBoundInterface_inc
#include "../pnSceneObject/plObjInterface.h"
class plConvexVolume;
struct hsMatrix44;
class plBoundInterface : public plObjInterface
{
enum {
kDisable = 0x0,
kNumProps // last
};
protected:
//hsMatrix44 fLocalToWorld;
plConvexVolume *fBounds;
public:
plBoundInterface();
~plBoundInterface();
void Init(plConvexVolume *bounds);
plConvexVolume *GetVolume() { return fBounds; }
CLASSNAME_REGISTER( plBoundInterface );
GETINTERFACE_ANY( plBoundInterface, plObjInterface );
virtual Int32 GetNumProperties() const { return kNumProps; }
virtual void SetTransform(const hsMatrix44& l2w, const hsMatrix44& w2l);
virtual void Read(hsStream* stream, hsResMgr* mgr);
virtual void Write(hsStream* stream, hsResMgr* mgr);
virtual void ReleaseData();
};
#endif // plBoundInterface_inc

View File

@ -0,0 +1,193 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "hsTypes.h"
#include "hsGeometry3.h"
#include "hsMatrix44.h"
#include "plConvexVolume.h"
#include "hsStream.h"
plConvexVolume::plConvexVolume()
{
//fFlags = nil;
fLocalPlanes = nil;
fWorldPlanes = nil;
fNumPlanes = 0;
}
plConvexVolume::~plConvexVolume()
{
IClear();
}
void plConvexVolume::IClear()
{
//delete [] fFlags;
delete [] fLocalPlanes;
delete [] fWorldPlanes;
}
hsBool plConvexVolume::AddPlane(const hsPlane3 &plane)
{
// First check for a redundant plane (since we're convex, a comparison of normals should do)
int i;
// Start the comparison with the most recently added plane, it's most likely to match
for (i = fNumPlanes - 1; i >= 0; i--)
{
const float MIN_COS_THETA = 0.99999f; // translates to < 0.25 degree angle
// If the angle betwen the normals is close enough, count them as equal.
if (fLocalPlanes[i].fN.InnerProduct(plane.fN) >= MIN_COS_THETA)
return false; // no need to add it
}
fNumPlanes++;
//delete [] fFlags;
//fFlags = TRACKED_NEW UInt32[fNumPlanes];
hsPlane3 *tempPlanes = TRACKED_NEW hsPlane3[fNumPlanes];
for (i = 0; i < fNumPlanes - 1; i++)
{
tempPlanes[i] = fLocalPlanes[i];
}
tempPlanes[fNumPlanes - 1] = plane;
delete [] fLocalPlanes;
fLocalPlanes = tempPlanes;
delete [] fWorldPlanes;
fWorldPlanes = TRACKED_NEW hsPlane3[fNumPlanes];
return true;
}
void plConvexVolume::Update(const hsMatrix44 &l2w)
{
int i;
hsPoint3 planePt;
for (i = 0; i < fNumPlanes; i++)
{
// Since fN is an hsVector3, it will only apply the rotational aspect of the transform...
fWorldPlanes[i].fN = l2w * fLocalPlanes[i].fN;
planePt.Set(&(fLocalPlanes[i].fN * fLocalPlanes[i].fD));
fWorldPlanes[i].fD = -(l2w * planePt).InnerProduct(fWorldPlanes[i].fN);
}
}
void plConvexVolume::SetNumPlanesAndClear(const UInt32 num)
{
IClear();
//fFlags = TRACKED_NEW UInt32[num];
fLocalPlanes = TRACKED_NEW hsPlane3[num];
fWorldPlanes = TRACKED_NEW hsPlane3[num];
fNumPlanes = num;
}
void plConvexVolume::SetPlane(const hsPlane3 &plane, const UInt32 index)
{
fLocalPlanes[index] = plane;
}
hsBool plConvexVolume::IsInside(const hsPoint3 &pos) const
{
int i;
for( i = 0; i < fNumPlanes; i++ )
{
if (!TestPlane(pos, fWorldPlanes[i]))
return false;
}
return true;
}
hsBool plConvexVolume::ResolvePoint(hsPoint3 &pos) const
{
hsScalar minDist = 1.e33f;
Int32 minIndex = -1;
hsScalar currDist;
int i;
for (i = 0; i < fNumPlanes; i++)
{
currDist = -fWorldPlanes[i].fD - fWorldPlanes[i].fN.InnerProduct(pos);
if (currDist < 0)
return false; // We're not inside this plane, and thus outside the volume
if (currDist < minDist)
{
minDist = currDist;
minIndex = i;
}
}
pos += (-fWorldPlanes[minIndex].fD - fWorldPlanes[minIndex].fN.InnerProduct(pos)) * fWorldPlanes[minIndex].fN;
return true;
}
hsBool plConvexVolume::BouncePoint(hsPoint3 &pos, hsVector3 &velocity, hsScalar bounce, hsScalar friction) const
{
hsScalar minDist = 1.e33f;
Int32 minIndex = -1;
hsScalar currDist;
int i;
for (i = 0; i < fNumPlanes; i++)
{
currDist = -fWorldPlanes[i].fD - fWorldPlanes[i].fN.InnerProduct(pos);
if (currDist < 0)
return false; // We're not inside this plane, and thus outside the volume
if (currDist < minDist)
{
minDist = currDist;
minIndex = i;
}
}
pos += (-fWorldPlanes[minIndex].fD - fWorldPlanes[minIndex].fN.InnerProduct(pos)) * fWorldPlanes[minIndex].fN;
hsVector3 bnc = -velocity.InnerProduct(fWorldPlanes[minIndex].fN) * fWorldPlanes[minIndex].fN;
velocity += bnc;
velocity *= 1.f - friction;
velocity += bnc * bounce;
// velocity += (velocity.InnerProduct(fWorldPlanes[minIndex].fN) * -(1.f + bounce)) * fWorldPlanes[minIndex].fN;
return true;
}
void plConvexVolume::Read(hsStream* s, hsResMgr *mgr)
{
SetNumPlanesAndClear(s->ReadSwap32());
int i;
for (i = 0; i < fNumPlanes; i++)
{
fLocalPlanes[i].Read(s);
//fFlags[i] = s->ReadSwap32();
}
}
void plConvexVolume::Write(hsStream* s, hsResMgr *mgr)
{
s->WriteSwap32(fNumPlanes);
int i;
for (i = 0; i < fNumPlanes; i++)
{
fLocalPlanes[i].Write(s);
//s->WriteSwap32(fFlags[i]);
}
}

View File

@ -0,0 +1,87 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plConvexVolume_inc
#define plConvexVolume_inc
#include "../pnSceneObject/plObjInterface.h"
struct hsPlane3;
struct hsPoint3;
struct hsMatrix44;
class hsResMgr;
// A convex volume defined by several boundary planes
// For now it assumes the user won't add planes that make it concave
class plConvexVolume : public plCreatable
{
public:
plConvexVolume();
~plConvexVolume();
CLASSNAME_REGISTER( plConvexVolume );
GETINTERFACE_ANY( plConvexVolume, plCreatable );
void Update(const hsMatrix44 &l2w);
hsBool AddPlane(const hsPlane3 &plane);
void SetNumPlanesAndClear(const UInt32 num);
void SetPlane(const hsPlane3 &plane, const UInt32 index);
// If you only care about the test, call this. Otherwise call ResolvePoint.
hsBool IsInside(const hsPoint3 &pos) const;
// returns true if the point was inside the volume, and thus moved outward.
hsBool ResolvePoint(hsPoint3 &pos) const;
// returns true if the point was inside and pos and velocity updated to bounce off offending plane.
// input bounce==1.f for perfect bounce, bounce==0 to slide.
hsBool BouncePoint(hsPoint3 &pos, hsVector3 &velocity, hsScalar bounce, hsScalar friction) const;
inline hsBool TestPlane(const hsPoint3 &pos, const hsPlane3 &plane) const; // Is the point inside the plane?
virtual void Read(hsStream* s, hsResMgr *mgr);
virtual void Write(hsStream* s, hsResMgr *mgr);
//virtual hsBool MsgReceive(plMessage* msg);
protected:
void IClear();
hsPlane3 *fLocalPlanes;
hsPlane3 *fWorldPlanes;
UInt32 fNumPlanes;
};
inline hsBool plConvexVolume::TestPlane(const hsPoint3 &pos, const hsPlane3 &plane) const
{
hsScalar dis = plane.fN.InnerProduct(pos);
dis += plane.fD;
if( dis >= 0.f )
return false;
return true;
}
#endif

View File

@ -0,0 +1,82 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plEffectTargetInfo_inc
#define plEffectTargetInfo_inc
#include "hsTypes.h"
struct hsPoint3;
class plPipeline;
class plParticleSystem;
// This is the rendering context passed into an effect to let it cache up
// anything it needs to compute that will be the same for all particles.
// Not a lot of context to go on to begin with, but this will let that
// expand without any interface changes.
class plParticleContext
{
public:
plPipeline* fPipeline;
plParticleSystem* fSystem;
double fSecs;
hsScalar fDelSecs;
};
// This is just a collection of arrays and strides that a plParticleEffect object will reference and modify
// in the course of doing its job.
class plEffectTargetInfo
{
public:
// Byte arrays. Declared as type UInt8 so that adding the stride to the pointer is guaranteed to advance
// the exact number of bytes.
UInt8 *fPos;
UInt8 *fVelocity;
UInt8 *fInvMass;
UInt8 *fAcceleration;
UInt8 *fColor;
UInt8 *fRadsPerSec;
UInt8 *fMiscFlags;
UInt32 fPosStride;
UInt32 fVelocityStride;
UInt32 fInvMassStride;
UInt32 fAccelerationStride;
UInt32 fColorStride;
UInt32 fRadsPerSecStride;
UInt32 fMiscFlagsStride;
plParticleContext fContext;
UInt32 fNumValidParticles;
UInt32 fFirstNewParticle;
// We're going to need some sort of connectivity data for constraint satisfaction, but at least we have
// a system that allows that to be added in smoothly when it's needed, so for now, let's get the main
// goop working.
};
#endif

View File

@ -0,0 +1,98 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plParticle_inc
#define plParticle_inc
#include "hsGeometry3.h"
#include "../CoreLib/hsColorRGBA.h"
// The meat of the particle. These classes, in combination with the plParticleEmitter that spawned it,
// should contain everything specific to a particle, necessary to build a renderable poly to represent a
// particular particle. (The emitter is necessary for properties (like texture) that are common among all
// particles that originated from the same emitter.
// For any reference in this object to a particle's poly vertices, the structure is as follows:
/*
|---| "HSize"
V3-----V2 -
| / | | "VSize"
| / | |
| P | -
| / |
| / | ("P" is the current position of the particle)
V0-----V1
So the vertices are arranged counter-clockwise, starting in the lower-left corner. Order all other attributes
accordingly.
*/
// The class plParticleCore should ONLY contain data necessary for the Drawable to create renderable polys
// Everything else goes into plParticleExt.
// plParticleEmitter is depending on the order that member variables appear in these classes, so
// DON'T MODIFY THEM WITHOUT MAKING SURE THE CONSTRUCTOR TO plParticleEmitter PROPERLY COMPUTES
// BASE ADDRESSES AND STRIDES!
// No initialization on construct. In nearly all cases, a default value won't be appropriate
// so there's no sense doing extra memory writes
class plParticleCore
{
public:
hsPoint3 fPos;
UInt32 fColor; // Particle opacity goes into the color's alpha.
hsPoint3 fOrientation; // fMiscFlags determines how this should be used.
hsVector3 fNormal;
hsScalar fHSize, fVSize; // distance from the heart of the particle to the borders of its poly.
hsPoint3 fUVCoords[4];
};
class plParticleExt
{
public:
//hsPoint3 fOldPos;
hsVector3 fVelocity;
hsScalar fInvMass; // The inverse (1 / mass) is what we actually need for calculations. Storing it this
// way allows us to make an object immovable with an inverse mass of 0 (and save a divide).
hsVector3 fAcceleration; // Accumulated from multiple forces.
hsScalar fLife; // how many seconds before we recycle this? (My particle has more of a life than I do...)
hsScalar fStartLife;
hsScalar fScale;
hsScalar fRadsPerSec;
//UInt32 fOrigColor;
enum // Miscellaneous flags for particles
{
kImmortal = 0x00000001,
};
UInt32 fMiscFlags; // I know... 32 bits for a single flag...
// Feel free to change this if you've got something to pack it against.
};
#endif

View File

@ -0,0 +1,119 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "plParticleSystem.h"
#include "plParticleGenerator.h"
#include "plParticleApplicator.h"
#include "../plAvatar/plScalarChannel.h"
#include "../plAvatar/plAGModifier.h"
#include "../plMessage/plParticleUpdateMsg.h"
#include "../pnSceneObject/plSceneObject.h"
#define PI 3.14159
plParticleGenerator *plParticleApplicator::IGetParticleGen(plSceneObject *so)
{
UInt32 numMods = so->GetNumModifiers();
int i;
for (i = 0; i < numMods; i++)
{
const plParticleSystem *result = plParticleSystem::ConvertNoRef(so->GetModifier(i));
if (result != nil)
return result->GetExportedGenerator();
}
return nil;
}
void plParticleLifeMinApplicator::IApply(const plAGModifier *mod, double time)
{
plScalarChannel *chan = plScalarChannel::ConvertNoRef(fChannel);
IGetParticleGen(mod->GetTarget(0))->UpdateParam(plParticleUpdateMsg::kParamPartLifeMin,
chan->Value(time));
}
void plParticleLifeMaxApplicator::IApply(const plAGModifier *mod, double time)
{
plScalarChannel *chan = plScalarChannel::ConvertNoRef(fChannel);
IGetParticleGen(mod->GetTarget(0))->UpdateParam(plParticleUpdateMsg::kParamPartLifeMax,
chan->Value(time));
}
void plParticlePPSApplicator::IApply(const plAGModifier *mod, double time)
{
plScalarChannel *chan = plScalarChannel::ConvertNoRef(fChannel);
IGetParticleGen(mod->GetTarget(0))->UpdateParam(plParticleUpdateMsg::kParamParticlesPerSecond,
chan->Value(time));
}
void plParticleAngleApplicator::IApply(const plAGModifier *mod, double time)
{
plScalarChannel *chan = plScalarChannel::ConvertNoRef(fChannel);
IGetParticleGen(mod->GetTarget(0))->UpdateParam(plParticleUpdateMsg::kParamInitPitchRange,
(hsScalar)(chan->Value(time) * PI / 180.f));
}
void plParticleVelMinApplicator::IApply(const plAGModifier *mod, double time)
{
plScalarChannel *chan = plScalarChannel::ConvertNoRef(fChannel);
IGetParticleGen(mod->GetTarget(0))->UpdateParam(plParticleUpdateMsg::kParamVelMin,
chan->Value(time));
}
void plParticleVelMaxApplicator::IApply(const plAGModifier *mod, double time)
{
plScalarChannel *chan = plScalarChannel::ConvertNoRef(fChannel);
IGetParticleGen(mod->GetTarget(0))->UpdateParam(plParticleUpdateMsg::kParamVelMax,
chan->Value(time));
}
void plParticleScaleMinApplicator::IApply(const plAGModifier *mod, double time)
{
plScalarChannel *chan = plScalarChannel::ConvertNoRef(fChannel);
IGetParticleGen(mod->GetTarget(0))->UpdateParam(plParticleUpdateMsg::kParamScaleMin,
chan->Value(time) / 100.f);
}
void plParticleScaleMaxApplicator::IApply(const plAGModifier *mod, double time)
{
plScalarChannel *chan = plScalarChannel::ConvertNoRef(fChannel);
IGetParticleGen(mod->GetTarget(0))->UpdateParam(plParticleUpdateMsg::kParamScaleMax,
chan->Value(time) / 100.f);
}
void plParticleGravityApplicator::IApply(const plAGModifier *mod, double time)
{
plScalarChannel *chan = plScalarChannel::ConvertNoRef(fChannel);
// IGetParticleGen(mod->GetTarget(0))->UpdateParam(plParticleUpdateMsg::kParamParticlesPerSecond,
// chan->Value(time));
}
void plParticleDragApplicator::IApply(const plAGModifier *mod, double time)
{
plScalarChannel *chan = plScalarChannel::ConvertNoRef(fChannel);
// IGetParticleGen(mod->GetTarget(0))->UpdateParam(plParticleUpdateMsg::kParamParticlesPerSecond,
// chan->Value(time));
}

View File

@ -0,0 +1,145 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef PLPARTICLEAPPLICATOR_INC
#define PLPARTICLEAPPLICATOR_INC
#include "../plAvatar/plAGChannel.h"
#include "../plAvatar/plAGApplicator.h"
class plParticleSystem;
class plParticleApplicator : public plAGApplicator
{
protected:
plParticleGenerator *IGetParticleGen(plSceneObject *so);
virtual void IApply(const plAGModifier *mod, double time) = 0;
public:
CLASSNAME_REGISTER( plParticleApplicator );
GETINTERFACE_ANY( plParticleApplicator, plAGApplicator );
};
class plParticleLifeMinApplicator : public plParticleApplicator
{
protected:
virtual void IApply(const plAGModifier *mod, double time);
public:
CLASSNAME_REGISTER( plParticleLifeMinApplicator );
GETINTERFACE_ANY( plParticleLifeMinApplicator, plAGApplicator );
};
class plParticleLifeMaxApplicator : public plParticleApplicator
{
protected:
virtual void IApply(const plAGModifier *mod, double time);
public:
CLASSNAME_REGISTER( plParticleLifeMaxApplicator );
GETINTERFACE_ANY( plParticleLifeMaxApplicator, plAGApplicator );
};
class plParticlePPSApplicator : public plParticleApplicator
{
protected:
virtual void IApply(const plAGModifier *mod, double time);
public:
CLASSNAME_REGISTER( plParticlePPSApplicator );
GETINTERFACE_ANY( plParticlePPSApplicator, plAGApplicator );
};
class plParticleAngleApplicator : public plParticleApplicator
{
protected:
virtual void IApply(const plAGModifier *mod, double time);
public:
CLASSNAME_REGISTER( plParticleAngleApplicator );
GETINTERFACE_ANY( plParticleAngleApplicator, plAGApplicator );
};
class plParticleVelMinApplicator : public plParticleApplicator
{
protected:
virtual void IApply(const plAGModifier *mod, double time);
public:
CLASSNAME_REGISTER( plParticleVelMinApplicator );
GETINTERFACE_ANY( plParticleVelMinApplicator, plAGApplicator );
};
class plParticleVelMaxApplicator : public plParticleApplicator
{
protected:
virtual void IApply(const plAGModifier *mod, double time);
public:
CLASSNAME_REGISTER( plParticleVelMaxApplicator );
GETINTERFACE_ANY( plParticleVelMaxApplicator, plAGApplicator );
};
class plParticleScaleMinApplicator : public plParticleApplicator
{
protected:
virtual void IApply(const plAGModifier *mod, double time);
public:
CLASSNAME_REGISTER( plParticleScaleMinApplicator );
GETINTERFACE_ANY( plParticleScaleMinApplicator, plAGApplicator );
};
class plParticleScaleMaxApplicator : public plParticleApplicator
{
protected:
virtual void IApply(const plAGModifier *mod, double time);
public:
CLASSNAME_REGISTER( plParticleScaleMaxApplicator );
GETINTERFACE_ANY( plParticleScaleMaxApplicator, plAGApplicator );
};
class plParticleGravityApplicator : public plParticleApplicator
{
protected:
virtual void IApply(const plAGModifier *mod, double time);
public:
CLASSNAME_REGISTER( plParticleGravityApplicator );
GETINTERFACE_ANY( plParticleGravityApplicator, plAGApplicator );
};
class plParticleDragApplicator : public plParticleApplicator
{
protected:
virtual void IApply(const plAGModifier *mod, double time);
public:
CLASSNAME_REGISTER( plParticleDragApplicator );
GETINTERFACE_ANY( plParticleDragApplicator, plAGApplicator );
};
#endif

View File

@ -0,0 +1,73 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plParticleCreatable_inc
#define plParticleCreatable_inc
#include "../pnFactory/plCreator.h"
#include "plParticleSystem.h"
#include "plParticleEffect.h"
#include "plParticleEmitter.h"
#include "plParticleGenerator.h"
#include "plParticleSystem.h"
#include "plParticleApplicator.h"
#include "plParticleSDLMod.h"
#include "plConvexVolume.h"
#include "plBoundInterface.h"
REGISTER_CREATABLE( plParticleSystem );
REGISTER_NONCREATABLE( plParticleEffect );
REGISTER_NONCREATABLE( plParticleCollisionEffect );
REGISTER_CREATABLE( plParticleCollisionEffectBeat );
REGISTER_CREATABLE( plParticleCollisionEffectDie );
REGISTER_CREATABLE( plParticleCollisionEffectBounce );
REGISTER_CREATABLE( plParticleFadeVolumeEffect );
REGISTER_NONCREATABLE( plParticleGenerator );
REGISTER_CREATABLE( plSimpleParticleGenerator );
REGISTER_CREATABLE( plOneTimeParticleGenerator );
REGISTER_CREATABLE( plParticleEmitter );
REGISTER_CREATABLE( plConvexVolume );
REGISTER_CREATABLE( plBoundInterface );
REGISTER_NONCREATABLE( plParticleApplicator );
REGISTER_CREATABLE( plParticleLifeMinApplicator );
REGISTER_CREATABLE( plParticleLifeMaxApplicator );
REGISTER_CREATABLE( plParticlePPSApplicator );
REGISTER_CREATABLE( plParticleAngleApplicator );
REGISTER_CREATABLE( plParticleVelMinApplicator );
REGISTER_CREATABLE( plParticleVelMaxApplicator );
REGISTER_CREATABLE( plParticleScaleMinApplicator );
REGISTER_CREATABLE( plParticleScaleMaxApplicator );
//REGISTER_CREATABLE( plParticleGravityApplicator );
//REGISTER_CREATABLE( plParticleDragApplicator );
REGISTER_NONCREATABLE( plParticleWindEffect );
REGISTER_CREATABLE( plParticleLocalWind );
REGISTER_CREATABLE( plParticleUniformWind );
REGISTER_CREATABLE( plParticleFlockEffect );
REGISTER_CREATABLE( plParticleFollowSystemEffect );
REGISTER_CREATABLE( plParticleSDLMod );
#endif // plParticleCreatable_inc

View File

@ -0,0 +1,895 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "hsTypes.h"
#include "hsGeometry3.h"
#include "plParticle.h"
#include "plParticleEffect.h"
#include "plEffectTargetInfo.h"
#include "plConvexVolume.h"
#include "plBoundInterface.h"
#include "hsResMgr.h"
#include "plPipeline.h"
#include "hsFastMath.h"
#include "../plMath/plRandom.h"
#include "plParticleSystem.h"
#include "../plMessage/plParticleUpdateMsg.h"
///////////////////////////////////////////////////////////////////////////////////////////
plParticleCollisionEffect::plParticleCollisionEffect()
{
fBounds = nil;
fSceneObj = nil;
}
plParticleCollisionEffect::~plParticleCollisionEffect()
{
}
void plParticleCollisionEffect::PrepareEffect(const plEffectTargetInfo &target)
{
if (fBounds == nil)
{
plBoundInterface *bi = plBoundInterface::ConvertNoRef(fSceneObj->GetGenericInterface(plBoundInterface::Index()));
if (bi == nil)
return;
fBounds = bi->GetVolume();
}
}
hsBool plParticleCollisionEffect::MsgReceive(plMessage* msg)
{
plRefMsg* refMsg = plRefMsg::ConvertNoRef(msg);
plSceneObject *so;
if (refMsg)
{
if (so = plSceneObject::ConvertNoRef(refMsg->GetRef()))
{
if( refMsg->GetContext() & (plRefMsg::kOnCreate|plRefMsg::kOnRequest|plRefMsg::kOnReplace) )
fSceneObj = so;
else
fSceneObj = nil;
return true;
}
}
return hsKeyedObject::MsgReceive(msg);
}
void plParticleCollisionEffect::Read(hsStream *s, hsResMgr *mgr)
{
hsKeyedObject::Read(s, mgr);
plGenRefMsg* msg;
msg = TRACKED_NEW plGenRefMsg(GetKey(), plRefMsg::kOnCreate, 0, 0); // SceneObject
mgr->ReadKeyNotifyMe(s, msg, plRefFlags::kActiveRef);
fBounds = nil;
}
void plParticleCollisionEffect::Write(hsStream *s, hsResMgr *mgr)
{
hsKeyedObject::Write(s, mgr);
mgr->WriteKey(s, fSceneObj);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Some permutations on the CollisionEffect follow
///////////////////////////////////////////////////////////////////////////////////////////
plParticleCollisionEffectBeat::plParticleCollisionEffectBeat()
{
}
hsBool plParticleCollisionEffectBeat::ApplyEffect(const plEffectTargetInfo &target, Int32 i)
{
hsAssert(i >= 0, "Use of default argument doesn't make sense for plParticleCollisionEffect");
if( !fBounds )
return false;
hsPoint3 *currPos = (hsPoint3 *)(target.fPos + i * target.fPosStride);
fBounds->ResolvePoint(*currPos);
return false;
}
///////////////////////////////////////////////////////////////////////////////////////////
plParticleCollisionEffectDie::plParticleCollisionEffectDie()
{
}
hsBool plParticleCollisionEffectDie::ApplyEffect(const plEffectTargetInfo &target, Int32 i)
{
hsAssert(i >= 0, "Use of default argument doesn't make sense for plParticleCollisionEffect");
if( !fBounds )
return false;
hsPoint3 *currPos = (hsPoint3 *)(target.fPos + i * target.fPosStride);
return fBounds->IsInside(*currPos);
}
///////////////////////////////////////////////////////////////////////////////////////////
plParticleCollisionEffectBounce::plParticleCollisionEffectBounce()
: fBounce(1.f),
fFriction(0.f)
{
}
hsBool plParticleCollisionEffectBounce::ApplyEffect(const plEffectTargetInfo &target, Int32 i)
{
hsAssert(i >= 0, "Use of default argument doesn't make sense for plParticleCollisionEffect");
if( !fBounds )
return false;
hsPoint3* currPos = (hsPoint3 *)(target.fPos + i * target.fPosStride);
hsVector3* currVel = (hsVector3*)(target.fVelocity + i * target.fVelocityStride);
fBounds->BouncePoint(*currPos, *currVel, fBounce, fFriction);
return false;
}
void plParticleCollisionEffectBounce::Read(hsStream *s, hsResMgr *mgr)
{
plParticleCollisionEffect::Read(s, mgr);
fBounce = s->ReadSwapScalar();
fFriction = s->ReadSwapScalar();
}
void plParticleCollisionEffectBounce::Write(hsStream *s, hsResMgr *mgr)
{
plParticleCollisionEffect::Write(s, mgr);
s->WriteSwapScalar(fBounce);
s->WriteSwapScalar(fFriction);
}
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////
plParticleFadeVolumeEffect::plParticleFadeVolumeEffect() : fLength(100.0f), fIgnoreZ(true)
{
}
plParticleFadeVolumeEffect::~plParticleFadeVolumeEffect()
{
}
//
// It's not really clear looking at the math here what's actually going on,
// but once you visualize it, it's pretty easy to follow. So the camera position,
// view direction, and length of the fade volume define a sphere, where the camera
// position is a point on the sphere, the view direction points from that surface
// point toward the center, and the length is the sphere's radius. Since the view
// direction points straight through the sphere, that sphere is the sweet zone for
// putting our particles to pile them up in front of the camera. But we'd like to
// do this independently for each axis (for efficiency, rather than true 3D calculations),
// so we put an axis aligned box around the sphere, and that's the volume we restrict
// our particles to.
// Now we could fade all around the box, but that's not really what we want, because
// that means fading particles that are behind us. And in the case where we're looking
// along an axis, the camera is pushed up against a face of the box (where the axis
// aligned box is tangent to the inscribed sphere), so we'd actually be fading
// particles just in front of the camera. Because of this non-symmetry, we're going to
// define the Max in a given dimension as the world space value for that dimension
// FARTHEST from the camera (NOT largest in value). So if the camera is looking
// in a negative direction in one dimension, the Max will be less than the Min for
// that dimension.
// So we set up our Max's and Min's for each dimension in PrepareEffect(), and then
// at runtime we calculate the parameter value of the particle ranging from 0 where
// particleLoc == Min to 1 where particleLoc == Max. If the parameter is outside
// [0..1], then we can move it into the box using the fractional part of the parameter.
// Finally, if the (possibly relocated) parameter value says the particle is approaching
// the Max value, we can calclulate its faded opacity from the parameter.
//
// Need to experiment to minimize this fade distance. The greater
// the fade distance, the more faded out (wasted) particles we're drawing.
// The shorter the distance, the more noticable the fade out.
// Note the wierdness between the fractions, because kFadeFrac is fraction
// of fLength, but kFadeParm and kInvFadeFrac are fraction of 2.f*fLength. Sorry.
const hsScalar kFadeFrac = 0.5f;
const hsScalar kFadeParm = 1.f - kFadeFrac * 0.5f;
const hsScalar kInvFadeFrac = 1.f / (kFadeFrac * 0.5f);
void plParticleFadeVolumeEffect::PrepareEffect(const plEffectTargetInfo &target)
{
hsPoint3 viewLoc = target.fContext.fPipeline->GetViewPositionWorld();
hsVector3 viewDir = target.fContext.fPipeline->GetViewDirWorld();
// Nuking out the setting of viewDir.fZ to 0 when we aren't centering
// about Z (fIgnoreZ == true), because we still want to center our
// volume about the camera (rather than push the camera to the edge of
// the cylinder) in that case, so we don't get artifacts when we look
// straight up or down. mf
hsPoint3 signs(viewDir.fX >= 0 ? 1.f : -1.f, viewDir.fY >= 0 ? 1.f : -1.f, viewDir.fZ >= 0 ? 1.f : -1.f);
fMax.fX = viewLoc.fX + (viewDir.fX + signs.fX) * fLength;
fMin.fX = fMax.fX - 2.f * signs.fX * fLength;
fMax.fY = viewLoc.fY + (viewDir.fY + signs.fY) * fLength;
fMin.fY = fMax.fY - 2.f * signs.fY * fLength;
fMax.fZ = viewLoc.fZ + (viewDir.fZ + signs.fZ) * fLength;
fMin.fZ = fMax.fZ - 2.f * signs.fZ * fLength;
fNorm.fX = 1.f / (fMax.fX - fMin.fX);
fNorm.fY = 1.f / (fMax.fY - fMin.fY);
fNorm.fZ = 1.f / (fMax.fZ - fMin.fZ);
}
hsBool plParticleFadeVolumeEffect::ApplyEffect(const plEffectTargetInfo& target, Int32 i)
{
hsPoint3 *currPos = (hsPoint3 *)(target.fPos + i * target.fPosStride);
hsScalar parm;
hsScalar fade = 1.f;
parm = (currPos->fX - fMin.fX) * fNorm.fX;
if( parm < 0 )
{
parm -= int(parm);
currPos->fX = fMax.fX + parm * (fMax.fX - fMin.fX);
parm += 1.f;
}
else if( parm > 1.f )
{
parm -= int(parm);
currPos->fX = fMin.fX + parm * (fMax.fX - fMin.fX);
}
if( parm > kFadeParm )
{
parm = 1.f - parm;
parm *= kInvFadeFrac;
if( parm < fade )
fade = parm;
}
parm = (currPos->fY - fMin.fY) * fNorm.fY;
if( parm < 0 )
{
parm -= int(parm);
currPos->fY = fMax.fY + parm * (fMax.fY - fMin.fY);
parm += 1.f;
}
else if( parm > 1.f )
{
parm -= int(parm);
currPos->fY = fMin.fY + parm * (fMax.fY - fMin.fY);
}
if( parm > kFadeParm )
{
parm = 1.f - parm;
parm *= kInvFadeFrac;
if( parm < fade )
fade = parm;
}
if( !fIgnoreZ )
{
parm = (currPos->fZ - fMin.fZ) * fNorm.fZ;
if( parm < 0 )
{
parm -= int(parm);
currPos->fZ = fMax.fZ + parm * (fMax.fZ - fMin.fZ);
parm += 1.f;
}
else if( parm > 1.f )
{
parm -= int(parm);
currPos->fZ = fMin.fZ + parm * (fMax.fZ - fMin.fZ);
}
if( parm > kFadeParm )
{
parm = 1.f - parm;
parm *= kInvFadeFrac;
if( parm < fade )
fade = parm;
}
}
if( fade < 1.f )
{
UInt32 *color = (UInt32 *)(target.fColor + i * target.fColorStride);
UInt32 alpha = (UInt32)((*color >> 24) * fade);
*color = (*color & 0x00ffffff) | (alpha << 24);
}
return false;
}
void plParticleFadeVolumeEffect::Read(hsStream *s, hsResMgr *mgr)
{
hsKeyedObject::Read(s, mgr);
fLength = s->ReadSwapScalar();
fIgnoreZ = s->ReadBool();
}
void plParticleFadeVolumeEffect::Write(hsStream *s, hsResMgr *mgr)
{
hsKeyedObject::Write(s, mgr);
s->WriteSwapScalar(fLength);
s->WriteBool(fIgnoreZ);
}
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Particle wind - Base class first
plParticleWindEffect::plParticleWindEffect()
: fWindVec(0,0,0),
fDir(1.f,0,0),
fSwirl(0.1f),
fConstancy(0),
fHorizontal(0),
fLastDirSecs(-1.f),
fRefDir(0.f,0.f,0.f),
fRandDir(1.f,0.f,0.f)
{
}
plParticleWindEffect::~plParticleWindEffect()
{
}
void plParticleWindEffect::Read(hsStream *s, hsResMgr *mgr)
{
hsKeyedObject::Read(s, mgr);
fStrength = s->ReadSwapScalar();
fConstancy = s->ReadSwapScalar();
fSwirl = s->ReadSwapScalar();
fHorizontal = s->ReadBool();
fRefDir.Read(s);
fDir.Read(s);
fRandDir = fDir;
}
void plParticleWindEffect::Write(hsStream *s, hsResMgr *mgr)
{
hsKeyedObject::Write(s, mgr);
s->WriteSwapScalar(fStrength);
s->WriteSwapScalar(fConstancy);
s->WriteSwapScalar(fSwirl);
s->WriteBool(fHorizontal);
fRefDir.Write(s);
fDir.Write(s);
}
void plParticleWindEffect::SetRefDirection(const hsVector3& v)
{
fRefDir = v;
hsScalar lenSq = fRefDir.MagnitudeSquared();
if( lenSq > 1.e-1f )
{
fDir = fRefDir * hsFastMath::InvSqrtAppr(lenSq);
fRandDir = fDir;
}
}
void plParticleWindEffect::PrepareEffect(const plEffectTargetInfo& target)
{
if( fLastDirSecs != target.fContext.fSecs )
{
static plRandom random;
fRandDir.fX += random.RandMinusOneToOne() * fSwirl;
fRandDir.fY += random.RandMinusOneToOne() * fSwirl;
if( !GetHorizontal() )
fRandDir.fZ += random.RandMinusOneToOne() * fSwirl;
hsFastMath::NormalizeAppr(fRandDir);
fDir = fRandDir + fRefDir;
hsFastMath::NormalizeAppr(fDir);
fWindVec = fDir * (fStrength * target.fContext.fSystem->GetWindMult() * target.fContext.fDelSecs);
fLastDirSecs = target.fContext.fSecs;
}
}
////////////////////////////////////////////////////////////////////////
// Localized wind (how much wind you're getting depends on where you are
plParticleLocalWind::plParticleLocalWind()
: fScale(0, 0, 0),
fSpeed(0),
fPhase(0,0,0),
fInvScale(0,0,0),
fLastPhaseSecs(-1.f)
{
}
plParticleLocalWind::~plParticleLocalWind()
{
}
void plParticleLocalWind::Read(hsStream *s, hsResMgr *mgr)
{
plParticleWindEffect::Read(s, mgr);
fScale.Read(s);
fSpeed = s->ReadSwapScalar();
}
void plParticleLocalWind::Write(hsStream *s, hsResMgr *mgr)
{
plParticleWindEffect::Write(s, mgr);
fScale.Write(s);
s->WriteSwapScalar(fSpeed);
}
void plParticleLocalWind::PrepareEffect(const plEffectTargetInfo& target)
{
if( fLastPhaseSecs != target.fContext.fSecs )
{
plParticleWindEffect::PrepareEffect(target);
fPhase += fDir * fSpeed * target.fContext.fDelSecs;
fInvScale.fX = fScale.fX > 0 ? 1.f / fScale.fX : 0;
fInvScale.fY = fScale.fY > 0 ? 1.f / fScale.fY : 0;
fInvScale.fZ = fScale.fZ > 0 ? 1.f / fScale.fZ : 0;
fLastPhaseSecs = target.fContext.fSecs;
}
}
hsBool plParticleLocalWind::ApplyEffect(const plEffectTargetInfo& target, Int32 i)
{
const hsPoint3& pos = *(hsPoint3 *)(target.fPos + i * target.fPosStride);
hsVector3& vel = *(hsVector3*)(target.fVelocity + i * target.fVelocityStride);
const hsScalar kMinToBother = 0;
float strength = 1.f / ( (1.f + fConstancy) * (1.f + fConstancy) );
float s, c, t;
t = (pos[0] - fPhase[0]) * fInvScale[0];
hsFastMath::SinCosAppr(t, s, c);
c += fConstancy;
if( c <= kMinToBother )
return false;
strength *= c;
t = (pos[1] - fPhase[1]) * fInvScale[1];
hsFastMath::SinCosAppr(t, s, c);
c += fConstancy;
if( c <= kMinToBother )
return false;
strength *= c;
#if 0 // if you turn this back on, strength needs to drop by another factor of (1.f + fConstancy)
t = (pos[2] - fPhase[2]) * fInvScale[2];
hsFastMath::SinCosAppr(t, s, c);
c += fConstancy;
if( c <= kMinToBother )
return false;
strength *= c;
#endif
const hsScalar& invMass = *(hsScalar*)(target.fInvMass + i * target.fInvMassStride);
strength *= invMass;
vel += fWindVec * strength;
return false;
}
////////////////////////////////////////////////////////////////////////
// Uniform wind - wind changes over time, but not space
plParticleUniformWind::plParticleUniformWind()
: fFreqMin(0.1f),
fFreqMax(0.2f),
fFreqCurr(0.1f),
fFreqRate(0.05f),
fCurrPhase(0),
fLastFreqSecs(-1.f),
fCurrentStrength(0)
{
}
plParticleUniformWind::~plParticleUniformWind()
{
}
void plParticleUniformWind::Read(hsStream *s, hsResMgr *mgr)
{
plParticleWindEffect::Read(s, mgr);
fFreqMin = s->ReadSwapScalar();
fFreqMax = s->ReadSwapScalar();
fFreqRate = s->ReadSwapScalar();
#if 0
fFreqMin = 1.f / 6.f;
fFreqMax = 1.f / 1.f;
fConstancy = -0.5f;
fSwirl = 0.05f;
#endif
fFreqCurr = fFreqMin;
}
void plParticleUniformWind::Write(hsStream *s, hsResMgr *mgr)
{
plParticleWindEffect::Write(s, mgr);
s->WriteSwapScalar(fFreqMin);
s->WriteSwapScalar(fFreqMax);
s->WriteSwapScalar(fFreqRate);
}
void plParticleUniformWind::SetFrequencyRange(hsScalar minSecsPerCycle, hsScalar maxSecsPerCycle)
{
const hsScalar kMinSecsPerCycle = 1.f;
if( minSecsPerCycle < kMinSecsPerCycle )
minSecsPerCycle = kMinSecsPerCycle;
if( minSecsPerCycle < kMinSecsPerCycle )
minSecsPerCycle = kMinSecsPerCycle;
if( minSecsPerCycle < maxSecsPerCycle )
{
fFreqMin = 1.f / maxSecsPerCycle;
fFreqMax = 1.f / minSecsPerCycle;
}
else
{
fFreqMin = 1.f / minSecsPerCycle;
fFreqMax = 1.f / maxSecsPerCycle;
}
}
void plParticleUniformWind::SetFrequencyRate(hsScalar secsPerCycle)
{
const hsScalar kMinSecsPerCycle = 1.f;
if( secsPerCycle < kMinSecsPerCycle )
secsPerCycle = kMinSecsPerCycle;
fFreqRate = 1.f / secsPerCycle;
}
void plParticleUniformWind::PrepareEffect(const plEffectTargetInfo& target)
{
plParticleWindEffect::PrepareEffect(target);
if( fLastFreqSecs != target.fContext.fSecs )
{
static plRandom random;
const double kTwoPi = hsScalarPI * 2.0;
double t0 = fFreqCurr * fLastFreqSecs + fCurrPhase;
hsScalar t1 = (hsScalar)fmod(t0, kTwoPi);
fCurrPhase -= t0 - t1;
fFreqCurr += fFreqRate * target.fContext.fDelSecs * random.RandZeroToOne();
if( fFreqCurr > fFreqMax )
{
fFreqCurr = fFreqMax;
fFreqRate = -fFreqRate;
}
else if( fFreqCurr < fFreqMin )
{
fFreqCurr = fFreqMin;
fFreqRate = -fFreqRate;
}
hsScalar phaseDel = (hsScalar)(t1 - (fFreqCurr * fLastFreqSecs + fCurrPhase));
fCurrPhase += phaseDel;
hsScalar t = hsScalar(fFreqCurr * target.fContext.fSecs + fCurrPhase);
hsScalar s;
hsFastMath::SinCosAppr(t, s, fCurrentStrength);
fCurrentStrength += fConstancy;
fCurrentStrength /= (1.f + fConstancy);
if( fCurrentStrength < 0 )
fCurrentStrength = 0;
fLastFreqSecs = target.fContext.fSecs;
}
}
hsBool plParticleUniformWind::ApplyEffect(const plEffectTargetInfo& target, Int32 i)
{
hsVector3& vel = *(hsVector3*)(target.fVelocity + i * target.fVelocityStride);
const hsScalar& invMass = *(hsScalar*)(target.fInvMass + i * target.fInvMassStride);
vel += fWindVec * (invMass * fCurrentStrength);
return false;
}
////////////////////////////////////////////////////////////////////////
// Simplified flocking.
plParticleFlockEffect::plParticleFlockEffect() :
fInfAvgRadSq(1),
fInfRepRadSq(1),
fAvgVelStr(1),
fRepDirStr(1),
fGoalOrbitStr(1),
fGoalChaseStr(1),
fGoalDistSq(1),
fFullChaseDistSq(1),
fMaxOrbitSpeed(1),
fMaxChaseSpeed(1),
fMaxParticles(0),
fDistSq(nil),
fInfluences(nil)
{
fTargetOffset.Set(0.f, 0.f, 0.f);
fDissenterTarget.Set(0.f, 0.f, 0.f);
}
plParticleFlockEffect::~plParticleFlockEffect()
{
SetMaxParticles(0);
}
void plParticleFlockEffect::IUpdateDistances(const plEffectTargetInfo& target)
{
int i, j;
int numParticles = hsMinimum(fMaxParticles, target.fNumValidParticles);
for (i = 0; i < numParticles; i++)
{
for (j = i + 1; j < numParticles; j++)
{
hsVector3 diff((hsPoint3*)(target.fPos + i * target.fPosStride), (hsPoint3*)(target.fPos + j * target.fPosStride));
fDistSq[i * fMaxParticles + j] = fDistSq[j * fMaxParticles + i] = diff.MagnitudeSquared();
}
}
}
void plParticleFlockEffect::IUpdateInfluences(const plEffectTargetInfo &target)
{
int i, j;
int numParticles = hsMinimum(fMaxParticles, target.fNumValidParticles);
for (i = 0; i < numParticles; i++)
{
int numAvg = 0;
int numRep = 0;
fInfluences[i].fAvgVel.Set(0.f, 0.f, 0.f);
fInfluences[i].fRepDir.Set(0.f, 0.f, 0.f);
for (j = 0; j < numParticles; j++)
{
if (i == j)
continue;
const int distIdx = i * fMaxParticles + j;
if (fDistSq[distIdx] > fInfAvgRadSq)
{
numAvg++;
fInfluences[i].fAvgVel += *(hsVector3*)(target.fVelocity + j * target.fVelocityStride);
}
if (fDistSq[distIdx] > fInfRepRadSq)
{
numRep++;
hsVector3 repDir((hsPoint3*)(target.fPos + i * target.fPosStride), (hsPoint3*)(target.fPos + j * target.fPosStride));
repDir.Normalize();
fInfluences[i].fRepDir += repDir;
}
}
if (numAvg > 0)
fInfluences[i].fAvgVel /= (hsScalar)numAvg;
if (numRep > 0)
fInfluences[i].fRepDir /= (hsScalar)numRep;
}
}
void plParticleFlockEffect::PrepareEffect(const plEffectTargetInfo& target)
{
IUpdateDistances(target);
IUpdateInfluences(target);
}
// Some of this is the same for every particle and should be cached in PrepareEffect().
// Holding off on that until I like the behavior.
hsBool plParticleFlockEffect::ApplyEffect(const plEffectTargetInfo& target, Int32 i)
{
if (i >= fMaxParticles)
return false; // Don't have the memory to deal with you. Good luck kid...
const hsPoint3 &pos = *(hsPoint3*)(target.fPos + i * target.fPosStride);
hsVector3 &vel = *(hsVector3*)(target.fVelocity + i * target.fVelocityStride);
hsScalar curSpeed = vel.Magnitude();
hsPoint3 goal;
if (*(UInt32*)(target.fMiscFlags + i * target.fMiscFlagsStride) & plParticleExt::kImmortal)
goal = target.fContext.fSystem->GetTarget(0)->GetLocalToWorld().GetTranslate() + fTargetOffset;
else
goal = fDissenterTarget;
hsVector3 goalDir;
goalDir.Set(&(goal - pos));
hsScalar distSq = goalDir.MagnitudeSquared();
goalDir.Normalize();
hsScalar goalStr;
hsScalar maxSpeed;
hsScalar maxSpeedSq;
if (distSq <= fGoalDistSq)
{
goalStr = fGoalOrbitStr;
if (i & 0x1)
goalDir.Set(goalDir.fY, -goalDir.fX, goalDir.fZ);
else
goalDir.Set(-goalDir.fY, goalDir.fX, goalDir.fZ);
maxSpeed = fMaxOrbitSpeed;
}
else if (distSq >= fFullChaseDistSq)
{
goalStr = fGoalChaseStr;
maxSpeed = fMaxChaseSpeed;
}
else
{
hsScalar pct = (distSq - fGoalDistSq) / (fFullChaseDistSq - fGoalDistSq);
goalStr = fGoalOrbitStr + (fGoalChaseStr - fGoalOrbitStr) * pct;
maxSpeed = fMaxOrbitSpeed + (fMaxChaseSpeed - fMaxOrbitSpeed) * pct;
}
maxSpeedSq = maxSpeed * maxSpeed;
vel += (fInfluences[i].fAvgVel - vel) * (fAvgVelStr * target.fContext.fDelSecs);
vel += goalDir * (curSpeed * goalStr * target.fContext.fDelSecs);
vel += fInfluences[i].fRepDir * (curSpeed * fRepDirStr * target.fContext.fDelSecs);
if (vel.MagnitudeSquared() > maxSpeedSq)
{
vel.Normalize();
vel *= maxSpeed;
}
return false;
}
void plParticleFlockEffect::SetMaxParticles(const UInt16 num)
{
delete [] fDistSq;
delete [] fInfluences;
fMaxParticles = num;
if (num > 0)
{
fDistSq = TRACKED_NEW hsScalar[num * num];
fInfluences = TRACKED_NEW plParticleInfluenceInfo[num];
}
}
void plParticleFlockEffect::Read(hsStream *s, hsResMgr *mgr)
{
plParticleEffect::Read(s, mgr);
fTargetOffset.Read(s);
fDissenterTarget.Read(s);
fInfAvgRadSq = s->ReadSwapScalar();
fInfRepRadSq = s->ReadSwapScalar();
fGoalDistSq = s->ReadSwapScalar();
fFullChaseDistSq = s->ReadSwapScalar();
fAvgVelStr = s->ReadSwapScalar();
fRepDirStr = s->ReadSwapScalar();
fGoalOrbitStr = s->ReadSwapScalar();
fGoalChaseStr = s->ReadSwapScalar();
SetMaxOrbitSpeed(s->ReadSwapScalar());
SetMaxChaseSpeed(s->ReadSwapScalar());
SetMaxParticles((UInt16)s->ReadSwapScalar());
}
void plParticleFlockEffect::Write(hsStream *s, hsResMgr *mgr)
{
plParticleEffect::Write(s, mgr);
fTargetOffset.Write(s);
fDissenterTarget.Write(s);
s->WriteSwapScalar(fInfAvgRadSq);
s->WriteSwapScalar(fInfRepRadSq);
s->WriteSwapScalar(fGoalDistSq);
s->WriteSwapScalar(fFullChaseDistSq);
s->WriteSwapScalar(fAvgVelStr);
s->WriteSwapScalar(fRepDirStr);
s->WriteSwapScalar(fGoalOrbitStr);
s->WriteSwapScalar(fGoalChaseStr);
s->WriteSwapScalar(fMaxOrbitSpeed);
s->WriteSwapScalar(fMaxChaseSpeed);
s->WriteSwapScalar(fMaxParticles);
}
hsBool plParticleFlockEffect::MsgReceive(plMessage *msg)
{
plParticleFlockMsg *flockMsg = plParticleFlockMsg::ConvertNoRef(msg);
if (flockMsg)
{
switch (flockMsg->fCmd)
{
case plParticleFlockMsg::kFlockCmdSetDissentPoint:
fDissenterTarget.Set(flockMsg->fX, flockMsg->fY, flockMsg->fZ);
break;
case plParticleFlockMsg::kFlockCmdSetOffset:
fTargetOffset.Set(flockMsg->fX, flockMsg->fY, flockMsg->fZ);
break;
default:
break;
}
return true;
}
return plParticleEffect::MsgReceive(msg);
}
///////////////////////////////////////////////////////////////////////////////////////
plParticleFollowSystemEffect::plParticleFollowSystemEffect() : fEvalThisFrame(true)
{
fOldW2L = hsMatrix44::IdentityMatrix();
}
void plParticleFollowSystemEffect::PrepareEffect(const plEffectTargetInfo& target)
{
fEvalThisFrame = (fOldW2L != target.fContext.fSystem->GetTarget(0)->GetWorldToLocal());
}
hsBool plParticleFollowSystemEffect::ApplyEffect(const plEffectTargetInfo& target, Int32 i)
{
if (fEvalThisFrame)
{
if (i < target.fFirstNewParticle && !fOldW2L.IsIdentity())
{
hsPoint3 &pos = *(hsPoint3*)(target.fPos + i * target.fPosStride);
pos = target.fContext.fSystem->GetTarget(0)->GetLocalToWorld() * fOldW2L * pos;
}
}
return true;
}
void plParticleFollowSystemEffect::EndEffect(const plEffectTargetInfo& target)
{
if (fEvalThisFrame)
fOldW2L = target.fContext.fSystem->GetTarget(0)->GetWorldToLocal();
}

View File

@ -0,0 +1,340 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plParticleEffect_inc
#define plParticleEffect_inc
#include "../pnKeyedObject/hsKeyedObject.h"
#include "hsMatrix44.h"
class plEffectTargetInfo;
class plConvexVolume;
class hsResMgr;
class plSceneObject;
class plParticleEffect : public hsKeyedObject
{
public:
CLASSNAME_REGISTER( plParticleEffect );
GETINTERFACE_ANY( plParticleEffect, hsKeyedObject );
// Order is:
// PrepareEffect is called with a given target (including valid
// ParticleContext).
// ApplyEffect is called some once for each particle (maybe zero times).
// It can return true to kill a particle.
// Target and Context passed in with Prepare will be
// guaranteed to remain valid until,
// EndEffect marks no more particles will be processed with the above
// context (invalidating anything cached).
// Defaults for Prepare and End are no-ops.
virtual void PrepareEffect(const plEffectTargetInfo& target) {}
virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i) = 0;
virtual void EndEffect(const plEffectTargetInfo& target) {}
};
class plParticleCollisionEffect : public plParticleEffect
{
public:
plParticleCollisionEffect();
~plParticleCollisionEffect();
CLASSNAME_REGISTER( plParticleCollisionEffect );
GETINTERFACE_ANY( plParticleCollisionEffect, plParticleEffect );
virtual void PrepareEffect(const plEffectTargetInfo& target);
virtual void Read(hsStream *s, hsResMgr *mgr);
virtual void Write(hsStream *s, hsResMgr *mgr);
virtual hsBool MsgReceive(plMessage *msg);
protected:
plSceneObject *fSceneObj;
plConvexVolume *fBounds;
};
// Default particle blocker. Doesn't affect particle's velocity,
// so it'll keep "beat"ing on the deflector until the velocity
// dotted with the deflector normal slides it off an edge.
class plParticleCollisionEffectBeat : public plParticleCollisionEffect
{
public:
plParticleCollisionEffectBeat();
CLASSNAME_REGISTER( plParticleCollisionEffectBeat );
GETINTERFACE_ANY( plParticleCollisionEffectBeat, plParticleCollisionEffect );
virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);
};
// This particle blocker just kills any particles that hit it.
class plParticleCollisionEffectDie : public plParticleCollisionEffect
{
public:
plParticleCollisionEffectDie();
CLASSNAME_REGISTER( plParticleCollisionEffectDie );
GETINTERFACE_ANY( plParticleCollisionEffectDie, plParticleCollisionEffect );
virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);
};
class plParticleCollisionEffectBounce : public plParticleCollisionEffect
{
protected:
hsScalar fBounce;
hsScalar fFriction;
public:
plParticleCollisionEffectBounce();
CLASSNAME_REGISTER( plParticleCollisionEffectBounce );
GETINTERFACE_ANY( plParticleCollisionEffectBounce, plParticleCollisionEffect );
virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);
virtual void Read(hsStream *s, hsResMgr *mgr);
virtual void Write(hsStream *s, hsResMgr *mgr);
void SetBounce(hsScalar b) { fBounce = b; }
hsScalar GetBounce() const { return fBounce; }
void SetFriction(hsScalar f) { fFriction = f; }
hsScalar GetFriction() const { return fFriction; }
};
class plParticleFadeVolumeEffect : public plParticleEffect
{
protected:
// Some cached properties. These will be the same for all
// particles between matching PrepareEffect and EndEffect calls.
hsPoint3 fMax;
hsPoint3 fMin;
hsPoint3 fNorm;
public:
plParticleFadeVolumeEffect();
~plParticleFadeVolumeEffect();
CLASSNAME_REGISTER( plParticleFadeVolumeEffect );
GETINTERFACE_ANY( plParticleFadeVolumeEffect, plParticleEffect );
virtual void PrepareEffect(const plEffectTargetInfo& target);
virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);
virtual void Read(hsStream *s, hsResMgr *mgr);
virtual void Write(hsStream *s, hsResMgr *mgr);
//virtual hsBool MsgReceive(plMessage *msg);
hsScalar fLength;
hsBool fIgnoreZ;
};
class plParticleWindEffect : public plParticleEffect
{
protected:
// The properties that define the wind. These might/should be animatable.
hsScalar fStrength;
hsScalar fConstancy;
hsScalar fSwirl;
hsBool fHorizontal;
hsVector3 fRefDir;
// Some cached properties. These will be the same for all
// particles between matching PrepareEffect and EndEffect calls.
// These define the current state of the wind.
hsVector3 fWindVec;
hsVector3 fRandDir;
hsVector3 fDir;
double fLastDirSecs;
public:
plParticleWindEffect();
~plParticleWindEffect();
CLASSNAME_REGISTER( plParticleWindEffect );
GETINTERFACE_ANY( plParticleWindEffect, plParticleEffect );
virtual void PrepareEffect(const plEffectTargetInfo& target);
virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i) = 0;
virtual void Read(hsStream *s, hsResMgr *mgr);
virtual void Write(hsStream *s, hsResMgr *mgr);
void SetStrength(hsScalar v) { fStrength = v; }
hsScalar GetStrength() const { return fStrength; }
void SetConstancy(hsScalar c) { fConstancy = c; }
hsScalar GetConstancy() const { return fConstancy; }
void SetSwirl(hsScalar s) { fSwirl = s; }
hsScalar GetSwirl() const { return fSwirl; }
void SetHorizontal(hsBool on) { fHorizontal = on; }
hsBool GetHorizontal() const { return fHorizontal; }
void SetRefDirection(const hsVector3& v);
const hsVector3& GetRefDirection() const { return fRefDir; }
};
class plParticleLocalWind : public plParticleWindEffect
{
protected:
hsVector3 fScale;
hsScalar fSpeed;
hsVector3 fPhase;
hsVector3 fInvScale;
double fLastPhaseSecs;
public:
plParticleLocalWind();
~plParticleLocalWind();
CLASSNAME_REGISTER( plParticleLocalWind );
GETINTERFACE_ANY( plParticleLocalWind, plParticleWindEffect );
virtual void PrepareEffect(const plEffectTargetInfo& target);
virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);
void SetScale(const hsVector3& v) { fScale = v; }
const hsVector3& GetScale() const { return fScale; }
void SetSpeed(hsScalar v) { fSpeed = v; }
hsScalar GetSpeed() const { return fSpeed; }
virtual void Read(hsStream *s, hsResMgr *mgr);
virtual void Write(hsStream *s, hsResMgr *mgr);
};
class plParticleUniformWind : public plParticleWindEffect
{
protected:
hsScalar fFreqMin;
hsScalar fFreqMax;
hsScalar fFreqCurr;
hsScalar fFreqRate;
double fCurrPhase;
double fLastFreqSecs;
hsScalar fCurrentStrength;
public:
plParticleUniformWind();
~plParticleUniformWind();
CLASSNAME_REGISTER( plParticleUniformWind );
GETINTERFACE_ANY( plParticleUniformWind, plParticleWindEffect );
virtual void PrepareEffect(const plEffectTargetInfo& target);
virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);
void SetFrequencyRange(hsScalar minSecsPerCycle, hsScalar maxSecsPerCycle);
void SetFrequencyRate(hsScalar secsPerCycle);
virtual void Read(hsStream *s, hsResMgr *mgr);
virtual void Write(hsStream *s, hsResMgr *mgr);
};
class plParticleInfluenceInfo
{
public:
hsVector3 fAvgVel;
hsVector3 fRepDir;
};
class plParticleFlockEffect : public plParticleEffect
{
//protected:
protected:
hsPoint3 fTargetOffset; // Worldspace offset from our target to get the true goal
hsPoint3 fDissenterTarget; // Where to particles go when they get scared and leave us?
hsScalar fInfAvgRadSq; // Square of the radius of influence for average velocity matching.
hsScalar fInfRepRadSq; // Same, for repelling from neighbors.
hsScalar fAvgVelStr; // How strongly are we influenced by average dir?
hsScalar fRepDirStr; // Same for repelling
hsScalar fGoalOrbitStr; // Same for the goal (when we're within the desired distance)
hsScalar fGoalChaseStr; // Same for the goal (when we're too far away, and chasing it)
hsScalar fGoalDistSq;
hsScalar fFullChaseDistSq;
hsScalar fMaxOrbitSpeed;
hsScalar fMaxChaseSpeed;
UInt16 fMaxParticles;
hsScalar *fDistSq; // Table of distances from particle to particle
plParticleInfluenceInfo *fInfluences;
void IUpdateDistances(const plEffectTargetInfo &target);
void IUpdateInfluences(const plEffectTargetInfo &target);
public:
plParticleFlockEffect();
~plParticleFlockEffect();
CLASSNAME_REGISTER( plParticleFlockEffect );
GETINTERFACE_ANY( plParticleFlockEffect, plParticleEffect );
virtual void PrepareEffect(const plEffectTargetInfo& target);
virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);
void SetTargetOffset(const hsPoint3 &offset) { fTargetOffset = offset; }
void SetDissenterTarget(const hsPoint3 &target) { fDissenterTarget = target; }
void SetInfluenceAvgRadius(hsScalar val) { fInfAvgRadSq = val * val; }
void SetInfluenceRepelRadius(hsScalar val) { fInfRepRadSq = val * val; }
void SetGoalRadius(hsScalar val) { fGoalDistSq = val * val; }
void SetFullChaseRadius(hsScalar val) { fFullChaseDistSq = val * val; }
void SetConformStr(hsScalar val) { fAvgVelStr = val; }
void SetRepelStr(hsScalar val) { fRepDirStr = val; }
void SetGoalOrbitStr(hsScalar val) { fGoalOrbitStr = val; }
void SetGoalChaseStr(hsScalar val) { fGoalChaseStr = val; }
void SetMaxOrbitSpeed(hsScalar val) { fMaxOrbitSpeed = val; }
void SetMaxChaseSpeed(hsScalar val) { fMaxChaseSpeed = val; }
void SetMaxParticles(UInt16 num);
virtual void Read(hsStream *s, hsResMgr *mgr);
virtual void Write(hsStream *s, hsResMgr *mgr);
virtual hsBool MsgReceive(plMessage *msg);
};
class plParticleFollowSystemEffect : public plParticleEffect
{
public:
CLASSNAME_REGISTER( plParticleFollowSystemEffect );
GETINTERFACE_ANY( plParticleFollowSystemEffect, plParticleEffect );
plParticleFollowSystemEffect();
virtual void PrepareEffect(const plEffectTargetInfo& target);
virtual hsBool ApplyEffect(const plEffectTargetInfo& target, Int32 i);
virtual void EndEffect(const plEffectTargetInfo& target);
protected:
hsMatrix44 fOldW2L;
hsBool fEvalThisFrame;
};
#endif

View File

@ -0,0 +1,569 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "hsTypes.h"
#include "hsUtils.h"
#include "hsResMgr.h"
#include "../pnMessage/plRefMsg.h"
#include "../plMessage/plParticleUpdateMsg.h"
#include "plParticleGenerator.h"
#include "plParticleEmitter.h"
#include "plParticleSystem.h"
#include "plParticle.h"
#include "plParticleEffect.h"
#include "../CoreLib/hsColorRGBA.h"
#include "../plInterp/plController.h"
#include "../plSurface/hsGMaterial.h"
#include "../plSurface/plLayerInterface.h"
#include "plProfile.h"
#include "hsFastMath.h"
plProfile_CreateTimer("Update", "Particles", ParticleUpdate);
plProfile_CreateTimer("Generate", "Particles", ParticleGenerate);
plParticleEmitter::plParticleEmitter()
{
fParticleCores = nil;
fParticleExts = nil;
fGenerator = nil;
fLocalToWorld.Reset();
fTimeToLive = 0;
}
void plParticleEmitter::Init(plParticleSystem *system, UInt32 maxParticles, UInt32 spanIndex, UInt32 miscFlags,
plParticleGenerator *gen /* = nil */)
{
IClear();
fSystem = system;
plLayerInterface *layer = system->fTexture->GetLayer(0)->BottomOfStack();
fMiscFlags = miscFlags | kNeedsUpdate;
if( fMiscFlags & kOnReserve )
fTimeToLive = -1.f; // Wait for someone to give us a spurt of life.
if( layer->GetShadeFlags() & hsGMatState::kShadeEmissive )
{
fMiscFlags |= kMatIsEmissive;
fColor = layer->GetAmbientColor();
}
else
{
fColor = layer->GetRuntimeColor();
}
fColor.a = layer->GetOpacity();
fGenerator = gen;
fMaxParticles = maxParticles;
fSpanIndex = spanIndex;
ISetupParticleMem();
}
void plParticleEmitter::Clone(plParticleEmitter* src, UInt32 spanIndex)
{
Init(src->fSystem,
src->fMaxParticles,
spanIndex,
src->fMiscFlags,
src->fGenerator);
fMiscFlags |= kBorrowedGenerator;
fTimeToLive = -1.f;
}
void plParticleEmitter::OverrideLocalToWorld(const hsMatrix44& l2w)
{
fLocalToWorld = l2w;
fMiscFlags |= kOverrideLocalToWorld;
}
plParticleEmitter::~plParticleEmitter()
{
IClear();
}
void plParticleEmitter::IClear()
{
delete [] fParticleCores;
fParticleCores = nil;
delete [] fParticleExts;
fParticleExts = nil;
if( !(fMiscFlags & kBorrowedGenerator) )
delete fGenerator;
fGenerator = nil;
}
void plParticleEmitter::ISetupParticleMem()
{
fNumValidParticles = 0;
fParticleCores = TRACKED_NEW plParticleCore[fMaxParticles];
fParticleExts = TRACKED_NEW plParticleExt[fMaxParticles];
fTargetInfo.fPos = (UInt8 *)fParticleCores;
fTargetInfo.fColor = (UInt8 *)fParticleCores + sizeof(hsPoint3);
fTargetInfo.fPosStride = fTargetInfo.fColorStride = sizeof(plParticleCore);
fTargetInfo.fVelocity = (UInt8 *)fParticleExts;
fTargetInfo.fInvMass = fTargetInfo.fVelocity + sizeof(hsVector3);
fTargetInfo.fAcceleration = fTargetInfo.fInvMass + sizeof(hsScalar);
fTargetInfo.fMiscFlags = (UInt8 *)&(fParticleExts[0].fMiscFlags);
fTargetInfo.fRadsPerSec = (UInt8 *)&(fParticleExts[0].fRadsPerSec);
fTargetInfo.fVelocityStride
= fTargetInfo.fInvMassStride
= fTargetInfo.fAccelerationStride
= fTargetInfo.fRadsPerSecStride
= fTargetInfo.fMiscFlagsStride
= sizeof(plParticleExt);
}
UInt32 plParticleEmitter::GetNumTiles() const
{
return fSystem->GetNumTiles();
}
const hsMatrix44 &plParticleEmitter::GetLocalToWorld() const
{
return fMiscFlags & kOverrideLocalToWorld ? fLocalToWorld : fSystem->GetLocalToWorld();
}
void plParticleEmitter::AddParticle(hsPoint3 &pos, hsVector3 &velocity, UInt32 tileIndex,
hsScalar hSize, hsScalar vSize, hsScalar scale, hsScalar invMass, hsScalar life,
hsPoint3 &orientation, UInt32 miscFlags, hsScalar radsPerSec)
{
plParticleCore *core;
plParticleExt *ext;
UInt32 currParticle;
if (fNumValidParticles == fMaxParticles)
return; // No more room... you lose!
else
currParticle = fNumValidParticles++;
core = &fParticleCores[currParticle];
core->fPos = pos;
core->fOrientation = orientation;
core->fColor = CreateHexColor(fColor);
core->fHSize = hSize * scale;
core->fVSize = vSize * scale;
// even if the kNormalUp flag isn't there, we should initialize it to something sane.
//if (core->fMiscFlags & kNormalUp != 0)
core->fNormal.Set(0, 0, 1);
hsScalar xOff = (tileIndex % fSystem->fXTiles) / (float)fSystem->fXTiles;
hsScalar yOff = (tileIndex / fSystem->fXTiles) / (float)fSystem->fYTiles;
core->fUVCoords[0].fX = xOff;
core->fUVCoords[0].fY = yOff + 1.0f / fSystem->fYTiles;
core->fUVCoords[0].fZ = 1.0f;
core->fUVCoords[1].fX = xOff + 1.0f / fSystem->fXTiles;
core->fUVCoords[1].fY = yOff + 1.0f / fSystem->fYTiles;
core->fUVCoords[1].fZ = 1.0f;
core->fUVCoords[2].fX = xOff + 1.0f / fSystem->fXTiles;
core->fUVCoords[2].fY = yOff;
core->fUVCoords[2].fZ = 1.0f;
core->fUVCoords[3].fX = xOff;
core->fUVCoords[3].fY = yOff;
core->fUVCoords[3].fZ = 1.0f;
ext = &fParticleExts[currParticle];
ext->fVelocity = velocity;
ext->fInvMass = invMass;
ext->fLife = ext->fStartLife = life;
ext->fMiscFlags = miscFlags; // Is this ever NOT zero?
if (life <= 0)
ext->fMiscFlags |= plParticleExt::kImmortal;
ext->fRadsPerSec = radsPerSec;
ext->fAcceleration.Set(0, 0, 0);
ext->fScale = scale;
}
void plParticleEmitter::WipeExistingParticles()
{
fNumValidParticles = 0; // That was easy.
}
// This method is called from a network received message. Don't trust the args without checking.
void plParticleEmitter::KillParticles(hsScalar num, hsScalar timeToDie, UInt8 flags)
{
if (flags & plParticleKillMsg::kParticleKillPercentage)
{
if (num < 0)
num = 0;
if (num > 1)
num = 1;
num *= fNumValidParticles;
}
else
{
if (num < 0)
num = 0;
}
if (timeToDie < 0)
timeToDie = 0;
int i;
for (i = 0; i < fNumValidParticles && num > 0; i++)
{
if ((flags & plParticleKillMsg::kParticleKillImmortalOnly) && !(fParticleExts[i].fMiscFlags & plParticleExt::kImmortal))
continue;
fParticleExts[i].fLife = fParticleExts[i].fStartLife = timeToDie;
fParticleExts[i].fMiscFlags &= ~plParticleExt::kImmortal;
num--;
}
}
void plParticleEmitter::UpdateGenerator(UInt32 paramID, hsScalar paramValue)
{
if (fGenerator != nil)
fGenerator->UpdateParam(paramID, paramValue);
}
UInt16 plParticleEmitter::StealParticlesFrom(plParticleEmitter *victim, UInt16 num)
{
UInt16 spaceAvail = (UInt16)(fMaxParticles - fNumValidParticles);
UInt16 numToCopy = (UInt16)(hsMinimum(num, (victim ? victim->fNumValidParticles : 0)));
if (spaceAvail < numToCopy)
numToCopy = spaceAvail;
if (numToCopy > 0)
{
// copy them over
memcpy(&(fParticleCores[fNumValidParticles]), &(victim->fParticleCores[victim->fNumValidParticles - numToCopy]), numToCopy * sizeof(plParticleCore));
memcpy(&(fParticleExts[fNumValidParticles]), &(victim->fParticleExts[victim->fNumValidParticles- numToCopy]), numToCopy * sizeof(plParticleExt));
fNumValidParticles += numToCopy;
victim->fNumValidParticles -= numToCopy;
}
return numToCopy;
}
void plParticleEmitter::TranslateAllParticles(hsPoint3 &amount)
{
int i;
for (i = 0; i < fNumValidParticles; i++)
fParticleCores[i].fPos += amount;
}
hsBool plParticleEmitter::IUpdate(hsScalar delta)
{
if (fMiscFlags & kNeedsUpdate)
{
plProfile_BeginTiming(ParticleUpdate);
IUpdateParticles(delta);
IUpdateBoundsAndNormals(delta);
plProfile_EndTiming(ParticleUpdate);
}
if (fGenerator == nil && fNumValidParticles <= 0)
return false; // no generator, no particles, let the system decide if this emitter is done
else
return true;
}
void plParticleEmitter::IUpdateParticles(hsScalar delta)
{
int i, j;
// Have to remove particles before adding new ones, or we can run out of room.
for (i = 0; i < fNumValidParticles; i++)
{
fParticleExts[i].fLife -= delta;
if (fParticleExts[i].fLife <= 0 && !(fParticleExts[i].fMiscFlags & plParticleExt::kImmortal))
{
IRemoveParticle(i);
i--; // so that we hit this index again on the next iteration
continue;
}
}
fTargetInfo.fFirstNewParticle = fNumValidParticles;
if ((fGenerator != nil) && (fTimeToLive >= 0))
{
plProfile_BeginLap(ParticleGenerate, fSystem->GetKeyName());
if (!fGenerator->AddAutoParticles(this, delta))
{
delete fGenerator;
fGenerator = nil;
}
if( (fTimeToLive > 0) && ((fTimeToLive -= delta) <= 0) )
fTimeToLive = -1.f;
plProfile_EndLap(ParticleGenerate, fSystem->GetKeyName());
}
fTargetInfo.fContext = fSystem->fContext;
fTargetInfo.fNumValidParticles = fNumValidParticles;
const hsVector3 up(0, 0, 1.0f);
hsVector3 currDirection;
hsPoint3 *currPos;
hsVector3 *currVelocity;
hsVector3 *currAccel;
hsPoint3 color(fColor.r, fColor.g, fColor.b);
hsScalar alpha = fColor.a;
plController *colorCtl;
// Allow effects a chance to cache any upfront calculations
// that will apply to all particles.
for (j = 0; j < fSystem->fForces.GetCount(); j++)
{
fSystem->fForces[j]->PrepareEffect(fTargetInfo);
}
for (j = 0; j < fSystem->fEffects.GetCount(); j++)
{
fSystem->fEffects[j]->PrepareEffect(fTargetInfo);
}
for (j = 0; j < fSystem->fConstraints.GetCount(); j++)
{
fSystem->fConstraints[j]->PrepareEffect(fTargetInfo);
}
for (i = 0; i < fNumValidParticles; i++)
{
if (!( fParticleExts[i].fMiscFlags & plParticleExt::kImmortal ))
{
hsScalar percent = (1.0f - fParticleExts[i].fLife / fParticleExts[i].fStartLife);
colorCtl = (fMiscFlags & kMatIsEmissive ? fSystem->fAmbientCtl : fSystem->fDiffuseCtl);
if (colorCtl != nil)
colorCtl->Interp(colorCtl->GetLength() * percent, &color);
if (fSystem->fOpacityCtl != nil)
{
fSystem->fOpacityCtl->Interp(fSystem->fOpacityCtl->GetLength() * percent, &alpha);
alpha /= 100.0f;
if (alpha < 0)
alpha = 0;
else if (alpha > 1.f)
alpha = 1.f;
}
if (fSystem->fWidthCtl != nil)
{
fSystem->fWidthCtl->Interp(fSystem->fWidthCtl->GetLength() * percent,
&fParticleCores[i].fHSize);
fParticleCores[i].fHSize *= fParticleExts[i].fScale;
}
if (fSystem->fHeightCtl != nil)
{
fSystem->fHeightCtl->Interp(fSystem->fHeightCtl->GetLength() * percent,
&fParticleCores[i].fVSize);
fParticleCores[i].fVSize *= fParticleExts[i].fScale;
}
fParticleCores[i].fColor = CreateHexColor(color.fX, color.fY, color.fZ, alpha);
}
for (j = 0; j < fSystem->fForces.GetCount(); j++)
{
fSystem->fForces[j]->ApplyEffect(fTargetInfo, i);
}
currPos = (hsPoint3 *)(fTargetInfo.fPos + i * fTargetInfo.fPosStride);
currVelocity = (hsVector3 *)(fTargetInfo.fVelocity + i * fTargetInfo.fVelocityStride);
//currAccel = (hsVector3 *)(fTargetInfo.fAcceleration + i * fTargetInfo.fAccelerationStride);
currAccel = &fSystem->fAccel; // Nothing accellerates on a per-particle basis (yet)
*currPos += *currVelocity * delta;
// This is the only orientation option (so far) that requires an update here
if (fMiscFlags & (kOrientationVelocityBased | kOrientationVelocityStretch | kOrientationVelocityFlow))
fParticleCores[i].fOrientation.Set(&(*currVelocity * delta)); // mf - want the orientation to be a delposition
else if( fParticleExts[i].fRadsPerSec != 0 )
{
hsScalar sinX, cosX;
hsFastMath::SinCos(fParticleExts[i].fLife * fParticleExts[i].fRadsPerSec * 2.f * hsScalarPI, sinX, cosX);
fParticleCores[i].fOrientation.Set(sinX, -cosX, 0);
}
// Viscous force F(t) = -k V(t)
// Integral S from t0 to t1 of F(t) is
// = S(-kV(t))[t1..t0]
// = -k(P(t1) - P(t0))
// = -k*(currVelocity * delta)
// or
// V = V + -k*(V * delta)
// V *= (1 + -k * delta)
// Giving the change in velocity.
hsScalar drag = 1.f + fSystem->fDrag * delta;
// Clamp it at 0. Drag should never cause a reversal in velocity direction.
if( drag < 0.f )
drag = 0.f;
*currVelocity *= drag;
*currVelocity += *currAccel * delta;
for (j = 0; j < fSystem->fEffects.GetCount(); j++)
{
fSystem->fEffects[j]->ApplyEffect(fTargetInfo, i);
}
// We may need to do more than one iteration through the constraints. It's a trade-off
// between accurracy and speed (what's new?) but I'm going to go with just one
// for now until we decide things don't "look right"
for (j = 0; j < fSystem->fConstraints.GetCount(); j++)
{
if( fSystem->fConstraints[j]->ApplyEffect(fTargetInfo, i) )
{
IRemoveParticle(i);
i--; // so that we hit this index again on the next iteration
break;
// break will break us out of loop over constraints,
// and since we're last, we move onto next particle.
}
}
}
// Notify the effects that they are done for now.
for (j = 0; j < fSystem->fForces.GetCount(); j++)
{
fSystem->fForces[j]->EndEffect(fTargetInfo);
}
for (j = 0; j < fSystem->fEffects.GetCount(); j++)
{
fSystem->fEffects[j]->EndEffect(fTargetInfo);
}
for (j = 0; j < fSystem->fConstraints.GetCount(); j++)
{
fSystem->fConstraints[j]->EndEffect(fTargetInfo);
}
}
plProfile_CreateTimer("Bound", "Particles", ParticleBound);
plProfile_CreateTimer("Normal", "Particles", ParticleNormal);
void plParticleEmitter::IUpdateBoundsAndNormals(hsScalar delta)
{
plProfile_BeginTiming(ParticleBound);
fBoundBox.MakeEmpty();
int i;
for (i = 0; i < fNumValidParticles; i++)
fBoundBox.Union(&fParticleCores[i].fPos);
hsPoint3 center;
if (fNumValidParticles > 0)
center = fBoundBox.GetCenter();
plProfile_EndTiming(ParticleBound);
plProfile_BeginTiming(ParticleNormal);
hsVector3 normal;
if (fMiscFlags & kNormalVelUpVel)
{
for (i = fNumValidParticles - 1; i >=0; i--)
{
//currDirection.Set(&fParticleCores[i].fPos, &fParticleExts[i].fOldPos);
//normal = (currDirection % up % currDirection);
normal.Set(-fParticleExts[i].fVelocity.fX * fParticleExts[i].fVelocity.fZ,
-fParticleExts[i].fVelocity.fY * fParticleExts[i].fVelocity.fZ,
(fParticleExts[i].fVelocity.fX * fParticleExts[i].fVelocity.fX +
fParticleExts[i].fVelocity.fY * fParticleExts[i].fVelocity.fY));
if (!normal.IsEmpty()) // zero length check
{
normal.Normalize();
fParticleCores[i].fNormal = normal;
}
}
}
else if (fMiscFlags & kNormalFromCenter)
{
for (i = fNumValidParticles - 1; i >=0; i--)
{
normal.Set(&fParticleCores[i].fPos, &center);
if (!normal.IsEmpty()) // zero length check
{
normal.Normalize();
fParticleCores[i].fNormal = normal;
}
}
}
// otherwise we just keep the last normal.
plProfile_EndTiming(ParticleNormal);
}
void plParticleEmitter::IRemoveParticle(UInt32 index)
{
hsAssert(index < fNumValidParticles, "Trying to remove an invalid particle");
fNumValidParticles--;
if (fNumValidParticles <= 0)
{
fNumValidParticles = 0;
return;
}
fParticleCores[index] = fParticleCores[fNumValidParticles];
fParticleExts[index] = fParticleExts[fNumValidParticles];
}
// Reading and writing doesn't transfer individual particle info. We assume those are expendable.
// Only the configuration info necessary to began spawning particles again is saved.
// The particle system that owns this emitter is responsible for setting the pointer back to itself
void plParticleEmitter::Read(hsStream *s, hsResMgr *mgr)
{
plCreatable::Read(s, mgr);
fGenerator = plParticleGenerator::ConvertNoRef(mgr->ReadCreatable(s));
fSpanIndex = s->ReadSwap32();
fMaxParticles = s->ReadSwap32();
fMiscFlags = s->ReadSwap32();
fColor.Read(s);
if( fMiscFlags & kOnReserve )
fTimeToLive = -1.f; // Wait for someone to give us a spurt of life.
ISetupParticleMem();
}
void plParticleEmitter::Write(hsStream *s, hsResMgr *mgr)
{
plCreatable::Write(s, mgr);
mgr->WriteCreatable(s, fGenerator);
s->WriteSwap32(fSpanIndex);
s->WriteSwap32(fMaxParticles);
s->WriteSwap32(fMiscFlags);
fColor.Write(s);
}
UInt32 plParticleEmitter::CreateHexColor(const hsColorRGBA &color)
{
return CreateHexColor(color.r, color.g, color.b, color.a);
}
UInt32 plParticleEmitter::CreateHexColor(const hsScalar r, const hsScalar g, const hsScalar b, const hsScalar a)
{
UInt32 ru, gu, bu, au;
au = (UInt32)(a * 255.0f);
ru = (UInt32)(r * 255.0f);
gu = (UInt32)(g * 255.0f);
bu = (UInt32)(b * 255.0f);
return ( au << 24 ) | ( ru << 16 ) | ( gu << 8 ) | ( bu );
}

View File

@ -0,0 +1,143 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plParticleEmitter_inc
#define plParticleEmitter_inc
#include "hsGeometry3.h"
#include "hsBounds.h"
#include "../pnNetCommon/plSynchedValue.h"
#include "../CoreLib/hsColorRGBA.h"
class hsBounds3Ext;
class plParticleSystem;
class plParticleCore;
class plParticleExt;
class plParticleGenerator;
class plSimpleParticleGenerator;
class hsResMgr;
#include "plEffectTargetInfo.h"
// This just holds a bunch of parameters for an emission location. A particle system can have several of these
class plParticleEmitter : public plCreatable
{
friend plParticleSystem;
friend plSimpleParticleGenerator;
public:
plParticleEmitter();
~plParticleEmitter();
void Init(plParticleSystem *system, UInt32 maxParticles, UInt32 spanIndex, UInt32 miscFlags,
plParticleGenerator *gen = nil);
void Clone(plParticleEmitter* src, UInt32 spanIndex);
plParticleCore *GetParticleArray() const { return fParticleCores; }
UInt32 GetParticleCount() const { return fNumValidParticles; }
UInt32 GetNumTiles() const;
const hsBounds3Ext &GetBoundingBox() const { return fBoundBox; }
UInt32 GetSpanIndex() const { return fSpanIndex; }
const hsMatrix44 &GetLocalToWorld() const;
void AddParticle(hsPoint3 &pos, hsVector3 &velocity, UInt32 tileIndex,
hsScalar hSize, hsScalar vSize, hsScalar scale, hsScalar invMass, hsScalar life,
hsPoint3 &orientation, UInt32 miscFlags, hsScalar radsPerSec=0);
void WipeExistingParticles();
void KillParticles(hsScalar num, hsScalar timeToDie, UInt8 flags);
UInt16 StealParticlesFrom(plParticleEmitter *victim, UInt16 num); // returns the number actually stolen
void TranslateAllParticles(hsPoint3 &amount); // Used to recenter the system when linking between ages.
void UpdateGenerator(UInt32 paramID, hsScalar paramValue);
static UInt32 CreateHexColor(const hsColorRGBA &color);
static UInt32 CreateHexColor(const hsScalar r, const hsScalar g, const hsScalar b, const hsScalar a);
void OverrideLocalToWorld(const hsMatrix44& l2w);
void UnOverrideLocalToWorld() { fMiscFlags &= ~kOverrideLocalToWorld; }
hsBool LocalToWorldOverridden() const { return 0 != (fMiscFlags & kOverrideLocalToWorld); }
void SetTimeToLive(hsScalar dt) { fTimeToLive = dt; }
hsScalar GetTimeToLive() const { return fTimeToLive; } // 0 time to live is never turn off.
CLASSNAME_REGISTER( plParticleEmitter );
GETINTERFACE_ANY( plParticleEmitter, plCreatable);
virtual void Read(hsStream* s, hsResMgr *mgr);
virtual void Write(hsStream* s, hsResMgr *mgr);
enum // Miscellaneous flags
{
kMatIsEmissive = 0x00000001,
kNormalUp = 0x00000010,
kNormalVelUpVel = 0x00000020,
kNormalFromCenter = 0x00000040,
kNormalDynamicMask = kNormalVelUpVel | kNormalFromCenter, // precalc methods that need updating each frame
kNormalPrecalcMask = kNormalDynamicMask | kNormalUp, // All types where emitter precalculates the normal
kNormalViewFacing = 0x00000100,
kNormalNearestLight = 0x00000200,
kNeedsUpdate = 0x01000000,
kBorrowedGenerator = 0x02000000,
kOverrideLocalToWorld = 0x04000000,
kOnReserve = 0x08000000,
kOrientationUp = 0x10000000,
kOrientationVelocityBased = 0x20000000,
kOrientationVelocityStretch = 0x40000000,
kOrientationVelocityFlow = 0x80000000,
kOrientationVelocityMask = kOrientationVelocityBased | kOrientationVelocityStretch | kOrientationVelocityFlow, // Velocity dependent
kOrientationMask = kOrientationUp | kOrientationVelocityMask,
};
UInt32 fMiscFlags;
protected:
plParticleSystem *fSystem; // The particle system this belongs to.
plParticleCore *fParticleCores; // The particle pool, created on init, initialized as needed, and recycled.
plParticleExt *fParticleExts; // Same mapping as the Core pool. Contains extra info the render pipeline
// doesn't need.
plParticleGenerator *fGenerator; // Optional auto generator (have this be nil if you don't want auto-generation)
UInt32 fSpanIndex; // Index of the span that this emitter uses.
UInt32 fNumValidParticles;
UInt32 fMaxParticles;
hsBounds3Ext fBoundBox;
plEffectTargetInfo fTargetInfo; // A collection of pointers and strides that plParticleEffects will manipulate.
hsColorRGBA fColor;
hsMatrix44 fLocalToWorld;
hsScalar fTimeToLive;
void IClear();
void ISetupParticleMem();
void ISetSystem(plParticleSystem *sys) { fSystem = sys; }
hsBool IUpdate(hsScalar delta);
void IUpdateParticles(hsScalar delta);
void IUpdateBoundsAndNormals(hsScalar delta);
void IRemoveParticle(UInt32 index);
};
#endif

View File

@ -0,0 +1,438 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "hsTypes.h"
#include "hsGeometry3.h"
#include "hsStream.h"
#include "hsFastMath.h"
#include "hsUtils.h"
#include "plParticle.h"
#include "plParticleSystem.h"
#include "plParticleEmitter.h"
#include "plParticleGenerator.h"
#include "../CoreLib/hsColorRGBA.h"
#include "../plMessage/plParticleUpdateMsg.h"
#include "../plInterp/plController.h"
#include "hsResMgr.h"
#include "../plMath/plRandom.h"
static const hsScalar DEFAULT_INVERSE_MASS = 1.f;
static plRandom sRandom;
const void plParticleGenerator::ComputeDirection(float pitch, float yaw, hsVector3 &direction)
{
hsScalar cosPitch, sinPitch;
hsScalar cosYaw, sinYaw;
hsFastMath::SinCos(pitch, sinPitch, cosPitch);
hsFastMath::SinCos(yaw, sinYaw, cosYaw);
direction.Set(-sinYaw * cosPitch, sinPitch, cosPitch * cosYaw);
}
// Inverse function of ComputeDirection. Give it a normalized vector, and it will tell you a
// pitch and yaw (angles for the unit Z vector) to get there.
const void plParticleGenerator::ComputePitchYaw(float &pitch, float &yaw, const hsVector3 &dir)
{
const float PI = 3.14159f;
pitch = asin(dir.fY);
float cos_pitch = cos(pitch);
if (cos_pitch == 0)
{
yaw = 0;
return;
}
float inv = -dir.fX / cos_pitch;
if (inv > 1.0f)
inv = 1.0f;
if (inv < -1.0f)
inv = -1.0f;
yaw = asin(inv);
if (dir.fZ < 0)
yaw = PI - yaw;
}
plSimpleParticleGenerator::plSimpleParticleGenerator()
{
}
plSimpleParticleGenerator::~plSimpleParticleGenerator()
{
delete [] fInitPos;
delete [] fInitPitch;
delete [] fInitYaw;
}
void plSimpleParticleGenerator::Init(hsScalar genLife, hsScalar partLifeMin, hsScalar partLifeMax,
hsScalar particlesPerSecond, UInt32 numSources, hsPoint3 *initPos,
hsScalar *initPitch, hsScalar *initYaw, hsScalar angleRange,
hsScalar initVelMin, hsScalar initVelMax,
hsScalar xSize, hsScalar ySize,
hsScalar scaleMin, hsScalar scaleMax,
hsScalar massRange, hsScalar radsPerSecRange)
{
fGenLife = genLife;
fPartLifeMin = partLifeMin;
fPartLifeMax = partLifeMax;
fParticlesPerSecond = particlesPerSecond;
fNumSources = numSources;
fInitPos = initPos;
fInitPitch = initPitch;
fInitYaw = initYaw;
fAngleRange = angleRange;
fVelMin = initVelMin;
fVelMax = initVelMax;
fXSize = xSize;
fYSize = ySize;
fScaleMin = scaleMin;
fScaleMax = scaleMax;
fPartInvMassMin = 1.f / (DEFAULT_INVERSE_MASS + massRange);
fPartInvMassRange = 1.f / DEFAULT_INVERSE_MASS - fPartInvMassMin;
fPartRadsPerSecRange = radsPerSecRange;
fParticleSum = 0;
fMiscFlags = 0;
if (fGenLife < 0) fMiscFlags |= kImmortal;
}
hsBool plSimpleParticleGenerator::AddAutoParticles(plParticleEmitter *emitter, float dt, UInt32 numForced /* = 0 */)
{
Int32 numNewParticles;
if (numForced == 0)
{
fGenLife -= dt;
if ((fGenLife < 0 && !(fMiscFlags & kImmortal)) || (fMiscFlags & kDisabled))
return true; // Leave it around so that a message can bring it back to life.
fParticleSum += fParticlesPerSecond * dt;
numNewParticles = (Int32)fParticleSum;
if (numNewParticles <= 0 || fParticlesPerSecond == 0)
return true;
}
else
{
numNewParticles = numForced;
}
UInt32 miscFlags = 0;
hsPoint3 currStart;
fParticleSum -= numNewParticles;
hsPoint3 orientation;
hsVector3 initDirection;
hsScalar vel = (fVelMax + fVelMin) * 0.5f;
hsScalar velRange = vel - fVelMin;
hsScalar initVelocity;
hsScalar initLife;
hsScalar life = (fPartLifeMax + fPartLifeMin) * 0.5f;
hsScalar lifeRange = life - fPartLifeMin;
hsScalar currSizeVar;
hsScalar scale = (fScaleMax + fScaleMin) * 0.5f;
hsScalar scaleRange = scale - fScaleMin;
hsScalar radsPerSec = 0;
UInt32 tile;
UInt32 sourceIndex;
const hsScalar lifeDiff = dt / numNewParticles;
hsScalar lifeSoFar;
int i;
for (i = 0, lifeSoFar = 0; i < numNewParticles; i++, lifeSoFar += lifeDiff)
{
initLife = life + lifeRange * sRandom.RandMinusOneToOne() - lifeSoFar;
// Careful here... if we're supposed to generate immortal particles, we do so
// by giving them a negative life. This is different that generating one with
// a positive lifetime that is now negative because of "lifeSoFar". The if is
// saying "if it's dead, but it was alive before we took away lifeSoFar, ignore it"
if (initLife <= 0 && initLife + lifeSoFar >= 0)
continue;
sourceIndex = (UInt32)(sRandom.RandZeroToOne() * fNumSources);
ComputeDirection(fInitPitch[sourceIndex] + fAngleRange * sRandom.RandMinusOneToOne(),
fInitYaw[sourceIndex] + fAngleRange * sRandom.RandMinusOneToOne(), initDirection);
initDirection = emitter->GetLocalToWorld() * initDirection;
initVelocity = (vel + velRange * sRandom.RandMinusOneToOne());
currStart = (emitter->GetLocalToWorld() * fInitPos[sourceIndex])
+ (initDirection * initVelocity * lifeSoFar) // Vo * t
+ (emitter->fSystem->fAccel * lifeSoFar * lifeSoFar); // at^2
if (emitter->fMiscFlags & emitter->kOrientationUp)
orientation.Set(0.0f, -1.0f, 0.0f);
else
orientation.Set(&initDirection);
tile = (UInt32)(sRandom.RandZeroToOne() * emitter->GetNumTiles());
currSizeVar = scale + scaleRange * sRandom.RandMinusOneToOne();
hsScalar invMass = fPartInvMassMin;
// Might be faster to just do the math instead of checking for zero...
if( fPartInvMassRange > 0 )
invMass += fPartInvMassRange * sRandom.RandZeroToOne();
if( fPartRadsPerSecRange > 0 )
radsPerSec = fPartRadsPerSecRange * sRandom.RandMinusOneToOne();
emitter->AddParticle(currStart, initDirection * initVelocity, tile, fXSize, fYSize, currSizeVar,
invMass, initLife, orientation, miscFlags, radsPerSec);
}
return true;
}
void plSimpleParticleGenerator::UpdateParam(UInt32 paramID, hsScalar paramValue)
{
switch (paramID)
{
case plParticleUpdateMsg::kParamParticlesPerSecond:
fParticlesPerSecond = paramValue;
break;
case plParticleUpdateMsg::kParamInitPitchRange:
case plParticleUpdateMsg::kParamInitYawRange:
fAngleRange = paramValue;
break;
// case plParticleUpdateMsg::kParamInitVel:
// fInitVel = paramValue;
// break;
// case plParticleUpdateMsg::kParamInitVelRange:
// fInitVelRange = paramValue;
// break;
case plParticleUpdateMsg::kParamVelMin:
fVelMin = paramValue;
break;
case plParticleUpdateMsg::kParamVelMax:
fVelMax = paramValue;
break;
case plParticleUpdateMsg::kParamXSize:
fXSize = paramValue;
break;
case plParticleUpdateMsg::kParamYSize:
fYSize = paramValue;
break;
// case plParticleUpdateMsg::kParamSizeRange:
// fSizeRange = paramValue;
// break;
case plParticleUpdateMsg::kParamScaleMin:
fScaleMin = paramValue;
break;
case plParticleUpdateMsg::kParamScaleMax:
fScaleMax = paramValue;
break;
case plParticleUpdateMsg::kParamGenLife:
fGenLife = paramValue;
if (fGenLife < 0)
fMiscFlags |= kImmortal;
else
fMiscFlags &= ~kImmortal;
break;
// case plParticleUpdateMsg::kParamPartLife:
// fPartLife = paramValue;
// if (fPartLife < 0)
// fPartLifeRange = 0;
// break;
// case plParticleUpdateMsg::kParamPartLifeRange:
// fPartLifeRange = paramValue;
// break;
case plParticleUpdateMsg::kParamPartLifeMin:
fPartLifeMin = paramValue;
break;
case plParticleUpdateMsg::kParamPartLifeMax:
fPartLifeMax = paramValue;
break;
case plParticleUpdateMsg::kParamEnabled:
if (paramValue == 0.f)
fMiscFlags |= kDisabled;
else
fMiscFlags &= ~kDisabled;
break;
default:
break;
}
}
void plSimpleParticleGenerator::Read(hsStream* s, hsResMgr *mgr)
{
hsScalar genLife = s->ReadSwapScalar();
hsScalar partLifeMin = s->ReadSwapScalar();
hsScalar partLifeMax = s->ReadSwapScalar();
hsScalar pps = s->ReadSwapScalar();
UInt32 numSources = s->ReadSwap32();
hsPoint3 *pos = TRACKED_NEW hsPoint3[numSources];
hsScalar *pitch = TRACKED_NEW hsScalar[numSources];
hsScalar *yaw = TRACKED_NEW hsScalar[numSources];
int i;
for (i = 0; i < numSources; i++)
{
pos[i].Read(s);
pitch[i] = s->ReadSwapScalar();
yaw[i] = s->ReadSwapScalar();
}
hsScalar angleRange = s->ReadSwapScalar();
hsScalar velMin = s->ReadSwapScalar();
hsScalar velMax = s->ReadSwapScalar();
hsScalar xSize = s->ReadSwapScalar();
hsScalar ySize = s->ReadSwapScalar();
hsScalar scaleMin = s->ReadSwapScalar();
hsScalar scaleMax = s->ReadSwapScalar();
hsScalar massRange = s->ReadSwapScalar();
hsScalar radsPerSec = s->ReadSwapScalar();
Init(genLife, partLifeMin, partLifeMax, pps, numSources, pos, pitch, yaw, angleRange, velMin, velMax,
xSize, ySize, scaleMin, scaleMax, massRange, radsPerSec);
}
void plSimpleParticleGenerator::Write(hsStream* s, hsResMgr *mgr)
{
s->WriteSwapScalar(fGenLife);
s->WriteSwapScalar(fPartLifeMin);
s->WriteSwapScalar(fPartLifeMax);
s->WriteSwapScalar(fParticlesPerSecond);
s->WriteSwap32(fNumSources);
int i;
for (i = 0; i < fNumSources; i++)
{
fInitPos[i].Write(s);
s->WriteSwapScalar(fInitPitch[i]);
s->WriteSwapScalar(fInitYaw[i]);
}
s->WriteSwapScalar(fAngleRange);
s->WriteSwapScalar(fVelMin);
s->WriteSwapScalar(fVelMax);
s->WriteSwapScalar(fXSize);
s->WriteSwapScalar(fYSize);
s->WriteSwapScalar(fScaleMin);
s->WriteSwapScalar(fScaleMax);
hsScalar massRange = 1.f / fPartInvMassMin - DEFAULT_INVERSE_MASS;
s->WriteSwapScalar(massRange);
s->WriteSwapScalar(fPartRadsPerSecRange);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
plOneTimeParticleGenerator::plOneTimeParticleGenerator()
{
}
plOneTimeParticleGenerator::~plOneTimeParticleGenerator()
{
delete [] fPosition;
delete [] fDirection;
}
void plOneTimeParticleGenerator::Init(hsScalar count, hsPoint3 *pointArray, hsVector3 *dirArray,
hsScalar xSize, hsScalar ySize, hsScalar scaleMin, hsScalar scaleMax, hsScalar radsPerSecRange)
{
fCount = count;
fPosition = pointArray;
fDirection = dirArray;
fXSize = xSize;
fYSize = ySize;
fScaleMin = scaleMin;
fScaleMax = scaleMax;
fPartRadsPerSecRange = radsPerSecRange;
}
// The numForced param is required by the parent class, but ignored by this particular generator
hsBool plOneTimeParticleGenerator::AddAutoParticles(plParticleEmitter *emitter, float dt, UInt32 numForced /* = 0 */)
{
hsScalar currSizeVar;
hsScalar scale = (fScaleMax + fScaleMin) / 2;
hsScalar scaleRange = scale - fScaleMin;
hsScalar tile;
hsPoint3 currStart;
hsPoint3 orientation;
hsVector3 initDirection;
hsVector3 zeroVel(0.f, 0.f, 0.f);
hsScalar radsPerSec = 0;
int i;
for (i = 0; i < fCount; i++)
{
currStart = emitter->GetLocalToWorld() * fPosition[i];
initDirection = emitter->GetLocalToWorld() * fDirection[i];
if (emitter->fMiscFlags & emitter->kOrientationUp)
orientation.Set(0.0f, -1.0f, 0.0f);
else
orientation.Set(&initDirection);
tile = (hsScalar)(sRandom.Rand() % emitter->GetNumTiles());
currSizeVar = scale + scaleRange * sRandom.RandMinusOneToOne();
if( fPartRadsPerSecRange > 0 )
radsPerSec = fPartRadsPerSecRange * sRandom.RandMinusOneToOne();
emitter->AddParticle(currStart, zeroVel, (UInt32)tile, fXSize, fYSize, currSizeVar,
DEFAULT_INVERSE_MASS, -1, orientation, 0, radsPerSec);
}
emitter->fMiscFlags &= ~plParticleEmitter::kNeedsUpdate;
return false; // We've done our one-time job. Let the emitter know to delete us.
}
void plOneTimeParticleGenerator::Read(hsStream* s, hsResMgr *mgr)
{
UInt32 count = s->ReadSwap32();
hsScalar xSize = s->ReadSwapScalar();
hsScalar ySize = s->ReadSwapScalar();
hsScalar scaleMin = s->ReadSwapScalar();
hsScalar scaleMax = s->ReadSwapScalar();
hsScalar radsPerSecRange = s->ReadSwapScalar();
hsPoint3 *pos = TRACKED_NEW hsPoint3[count];
hsVector3 *dir = TRACKED_NEW hsVector3[count];
int i;
for (i = 0; i < count; i++)
{
pos[i].Read(s);
dir[i].Read(s);
}
Init((hsScalar)count, pos, dir, xSize, ySize, scaleMin, scaleMax, radsPerSecRange);
}
void plOneTimeParticleGenerator::Write(hsStream* s, hsResMgr *mgr)
{
s->WriteSwap32((UInt32)fCount);
s->WriteSwapScalar(fXSize);
s->WriteSwapScalar(fYSize);
s->WriteSwapScalar(fScaleMin);
s->WriteSwapScalar(fScaleMax);
s->WriteSwapScalar(fPartRadsPerSecRange);
int i;
for (i = 0; i < fCount; i++)
{
fPosition[i].Write(s);
fDirection[i].Write(s);
}
}

View File

@ -0,0 +1,132 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plParticleGenerator_inc
#define plParticleGenerator_inc
#include "hsGeometry3.h"
#include "../pnNetCommon/plSynchedValue.h"
class plParticleEmitter;
class plScalarController;
// This class is responsible for all the details of automatically generating particles for a plParticleEmitter.
// It gets a change in time, and must do whatever necessary to generate the appropriate number of particles
// for that timespan
class plParticleGenerator : public plCreatable
{
public:
// returns false if it's done generating particles and is safe to delete.
virtual hsBool AddAutoParticles(plParticleEmitter *emitter, float dt, UInt32 numForced = 0) = 0;
virtual void UpdateParam(UInt32 paramID, hsScalar paramValue) = 0;
CLASSNAME_REGISTER( plParticleGenerator );
GETINTERFACE_ANY( plParticleGenerator, plCreatable );
static const void ComputeDirection(float pitch, float yaw, hsVector3 &direction);
static const void ComputePitchYaw(float &pitch, float &yaw, const hsVector3 &dir);
static inline float GetRandomVar() { return 2.0f * (float)hsRand() / RAND_MAX - 1; } // returns a num between +/- 1.0
};
class plSimpleParticleGenerator : public plParticleGenerator
{
public:
plSimpleParticleGenerator();
~plSimpleParticleGenerator();
void Init(hsScalar genLife, hsScalar partLifeMin, hsScalar partLifeMax, hsScalar particlesPerSecond,
UInt32 numSources, hsPoint3 *pos, hsScalar *initPitch, hsScalar *initYaw, hsScalar angleRange,
hsScalar initVelMin, hsScalar initVelMax, hsScalar xSize, hsScalar ySize,
hsScalar scaleMin, hsScalar scaleMax,
hsScalar massRange, hsScalar radsPerSecRange);
CLASSNAME_REGISTER( plSimpleParticleGenerator );
GETINTERFACE_ANY( plSimpleParticleGenerator, plParticleGenerator);
virtual hsBool AddAutoParticles(plParticleEmitter *emitter, float dt, UInt32 numForced);
virtual void UpdateParam(UInt32 paramID, hsScalar paramValue);
virtual void Read(hsStream* s, hsResMgr *mgr);
virtual void Write(hsStream* s, hsResMgr *mgr);
protected:
hsScalar fParticlesPerSecond;
UInt32 fNumSources;
hsPoint3 *fInitPos;
hsScalar *fInitPitch, *fInitYaw;
hsScalar fAngleRange;
hsScalar fVelMin, fVelMax;
hsScalar fXSize, fYSize, fScaleMin, fScaleMax;
hsScalar fGenLife; // How long shall we spit out particles from this location? When this time runs out, we stop
// spitting particles, but we don't actually die until all of our particles die naturally.
// (Even the ones that we feel are suffering needlessly.)
hsScalar fPartLifeMin; // lifespan for the particles we generate
hsScalar fPartLifeMax;
hsScalar fPartInvMassMin; // Doing a uniform variant over the inverse mass range (instead of over the mass range
hsScalar fPartInvMassRange; // and then inverting) will favor the low end of the mass range, but then again,
// it's just a freaking game. Note though that fPartInvMassMin == 1.f / massMAX.
hsScalar fPartRadsPerSecRange; // Zero means no rot, otherwise uniform random between [-range..range]
hsScalar fParticleSum;
enum
{
kImmortal = 0x1,
kDisabled = 0x2,
};
UInt32 fMiscFlags;
};
class plOneTimeParticleGenerator : public plParticleGenerator
{
public:
plOneTimeParticleGenerator();
~plOneTimeParticleGenerator();
void Init(hsScalar count, hsPoint3 *pointArray, hsVector3 *dirArray,
hsScalar xSize, hsScalar ySize, hsScalar scaleMin, hsScalar scaleMax, hsScalar radsPerSec);
CLASSNAME_REGISTER( plOneTimeParticleGenerator );
GETINTERFACE_ANY( plOneTimeParticleGenerator, plParticleGenerator);
virtual hsBool AddAutoParticles(plParticleEmitter *emitter, float dt, UInt32 numForced = 0);
virtual void UpdateParam(UInt32 paramID, hsScalar paramValue) {}
virtual void Read(hsStream* s, hsResMgr *mgr);
virtual void Write(hsStream* s, hsResMgr *mgr);
protected:
hsScalar fCount;
hsPoint3 *fPosition;
hsVector3 *fDirection;
hsScalar fXSize, fYSize, fScaleMin, fScaleMax;
hsScalar fPartRadsPerSecRange; // Zero means no rot, otherwise uniform random between [-range..range]
};
#endif

View File

@ -0,0 +1,76 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "plParticleSDLMod.h"
#include "plParticleSystem.h"
#include "../pnSceneObject/plSceneObject.h"
#include "../plSDL/plSDL.h"
#include "../pnKeyedObject/plKey.h"
// static vars
char plParticleSDLMod::kStrNumParticles[]="numParticles";
void plParticleSDLMod::IPutCurrentStateIn(plStateDataRecord* dstState)
{
plSceneObject* sobj=GetTarget();
if (!sobj)
return;
UInt32 flags = sobj->GetKey()->GetUoid().GetLocation().GetFlags();
const plParticleSystem *sys = plParticleSystem::ConvertNoRef(sobj->GetModifierByType(plParticleSystem::Index()));
if (!sys)
return;
int num = sys->GetNumValidParticles(true);
dstState->FindVar(kStrNumParticles)->Set(num);
}
void plParticleSDLMod::ISetCurrentStateFrom(const plStateDataRecord* srcState)
{
plSceneObject* sobj=GetTarget();
if (!sobj)
return;
plParticleSystem *sys = const_cast<plParticleSystem*>(plParticleSystem::ConvertNoRef(sobj->GetModifierByType(plParticleSystem::Index())));
if (!sys)
return;
int num;
srcState->FindVar(kStrNumParticles)->Get(&num);
if (num > sys->GetMaxTotalParticles())
num = sys->GetMaxTotalParticles();
sys->WipeExistingParticles();
sys->GenerateParticles(num);
}
UInt32 plParticleSDLMod::IApplyModFlags(UInt32 sendFlags)
{
if (fAttachedToAvatar)
return (sendFlags | plSynchedObject::kDontPersistOnServer | plSynchedObject::kIsAvatarState);
return sendFlags;
}

View File

@ -0,0 +1,60 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plParticleSDLMod_inc
#define plParticleSDLMod_inc
#include "../plModifier/plSDLModifier.h"
//
// This modifier is responsible for sending and recving
// particle system state. Few need it. Little state is needed.
//
class plParticleSDLMod : public plSDLModifier
{
private:
bool fAttachedToAvatar;
protected:
void IPutCurrentStateIn(plStateDataRecord* dstState);
void ISetCurrentStateFrom(const plStateDataRecord* srcState);
UInt32 IApplyModFlags(UInt32 sendFlags);
public:
// var labels
static char kStrNumParticles[];
CLASSNAME_REGISTER( plParticleSDLMod );
GETINTERFACE_ANY( plParticleSDLMod, plSDLModifier);
plParticleSDLMod(bool attachedToAvatar = false): fAttachedToAvatar(attachedToAvatar) {}
void PutCurrentStateIn(plStateDataRecord* dstState);
const char* GetSDLName() const { return kSDLParticleSystem; }
void SetAttachedToAvatar(bool attached) {fAttachedToAvatar = attached;}
};
#endif // plParticleSDLMod_inc

View File

@ -0,0 +1,701 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#include "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);
}

View File

@ -0,0 +1,185 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
#ifndef plParticleSystem_inc
#define plParticleSystem_inc
#include "hsTemplates.h"
#include "hsGeometry3.h"
#include "../../NucleusLib/pnModifier/plModifier.h"
#include "../pnNetCommon/plSynchedValue.h"
#include "../CoreLib/hsColorRGBA.h"
#include "../CoreLib/hsMatrix44.h"
#include "plEffectTargetInfo.h"
class plPipeline;
class plParticle;
class plParticleGenerator;
class plSimpleParticleGenerator;
class plParticleEmitter;
class plParticleEffect;
class plParticleSDLMod;
class plController;
class hsGMaterial;
class plDrawableSpans;
class plDrawInterface;
class Mtl;
// This is responsible for creating and maintaining (allocation and update cycle) a related set of particles.
// Automatic particle generation is handled by the plParticleEmitters (one for each spawning location).
class plParticleSystem : public plModifier
{
friend plParticleEmitter;
friend plSimpleParticleGenerator;
protected:
static const hsScalar GRAVITY_ACCEL_FEET_PER_SEC2;
plSceneObject *fTarget;
hsGMaterial *fTexture; // One texture per system (Tiling is your friend!)
UInt32 fXTiles, fYTiles; // Width/height of the texture (in tiles) for determining a particle's UVs
double fCurrTime;
double fLastTime;
hsVector3 fAccel;
hsScalar fPreSim;
hsScalar fDrag;
hsScalar fWindMult;
bool fAttachedToAvatar;
UInt32 fMaxTotalParticles;
UInt32 fMaxTotalParticlesLeft;
UInt32 fNumValidEmitters;
UInt32 fMaxEmitters;
UInt32 fNextEmitterToGo;
plParticleEmitter **fEmitters; // Various locations we're emitting particles from (the first one is
// reserved for particles added explicitly (to keep all the bookkeeping
// in the hands of plParticleEmitter).
hsTArray<plParticleEffect *> fForces; // Global forces (wind/gravity/etc) that affect accelleration.
hsTArray<plParticleEffect *> fEffects; // Any other non-constraint effects.
hsTArray<plParticleEffect *> fConstraints; // Rigid body, collision, connectivity, etc.
plParticleContext fContext; // Rendering context passed to forces/effects/constraints.
hsTArray<plKey> fPermaLights; // Runtime lights assigned to this system. Currently don't support projected lights on particles.
// Material related animations, mapped over the course of a particle's life
plController *fAmbientCtl;
plController *fDiffuseCtl;
plController *fOpacityCtl;
plController *fWidthCtl;
plController *fHeightCtl;
plParticleSDLMod *fParticleSDLMod;
hsBool IShouldUpdate(plPipeline* pipe) const;
virtual hsBool IEval(double secs, hsScalar del, UInt32 dirty); // required by plModifier
void IHandleRenderMsg(plPipeline* pipe);
plDrawInterface* ICheckDrawInterface();
void IAddEffect(plParticleEffect *effect, UInt32 type);
void IReadEffectsArray(hsTArray<plParticleEffect *> &effects, UInt32 type, hsStream *s, hsResMgr *mgr);
void IPreSim();
public:
plParticleSystem();
virtual ~plParticleSystem();
void Init(UInt32 xTiles, UInt32 yTiles, UInt32 maxTotalParticles, UInt32 numEmitters,
plController *ambientCtl, plController *diffuseCtl, plController *opacityCtl,
plController *widthCtl, plController *heightCtl);
enum effectType
{
kEffectForce = 0x1,
kEffectMisc = 0x2,
kEffectConstraint = 0x4,
};
enum miscFlags
{
kParticleSystemAlwaysUpdate = 0x1
};
UInt8 fMiscFlags; // Not read/written (could be, but it's not needed yet.)
// There might not be enough particles available. this function returns the number of maxParticles that
// the emitter actually received.
UInt32 AddEmitter(UInt32 maxParticles, plParticleGenerator *gen, UInt32 emitterFlags);
plParticleEmitter* GetAvailEmitter();
CLASSNAME_REGISTER( plParticleSystem );
GETINTERFACE_ANY( plParticleSystem, plModifier);
// These are just public wrappers to the equivalent plParticleEmitter functions, provided for the purpose
// of adding particles to a system explicitly.
void AddParticle(hsPoint3 &pos, hsVector3 &velocity, UInt32 tileIndex,
hsScalar hSize, hsScalar vSize, hsScalar scale, hsScalar invMass, hsScalar life,
hsPoint3 &orientation, UInt32 miscFlags, hsScalar radsPerSec=0.f);
void GenerateParticles(UInt32 num, hsScalar dt = 0.f);
void WipeExistingParticles(); // Instant nuke
void KillParticles(hsScalar num, hsScalar timeToDie, UInt8 flags); // Sets a death timer. They'll get removed on the next update (if time has run out)
UInt16 StealParticlesFrom(plParticleSystem *victim, UInt16 num); // Returns the number of particles actually stolen
void TranslateAllParticles(hsPoint3 &amount); // Used to recenter the system when linking between ages.
void DisableGenerators();
UInt32 GetNumTiles() const { return fXTiles * fYTiles; }
void SetTileIndex(plParticle &particle, UInt32 index); // Sets the UV coordinates appropriate for the current texture
UInt32 GetNumValidParticles(hsBool immortalOnly = false) const; // Takes a bit longer if we want a count of immortal particles...
UInt32 GetMaxTotalParticles() const { return fMaxTotalParticles; }
const hsMatrix44 &GetLocalToWorld() const;
void SetAccel(const hsVector3& a) { fAccel = GRAVITY_ACCEL_FEET_PER_SEC2 * a; }
void SetGravity(hsScalar pct) { fAccel.Set(0, 0, -GRAVITY_ACCEL_FEET_PER_SEC2 * pct); }
void SetDrag(hsScalar d) { fDrag = -d; }
void SetWindMult(hsScalar m) { fWindMult = m; }
void SetPreSim(hsScalar time) { fPreSim = time; }
void UpdateGenerator(UInt32 paramID, hsScalar value);
plParticleGenerator *GetExportedGenerator() const;
const hsVector3& GetAccel() const { return fAccel; }
hsScalar GetDrag() const { return fDrag; }
hsScalar GetWindMult() const { return fWindMult; }
plParticleEffect *GetEffect(UInt16 type) const;
plParticleSDLMod* GetSDLMod() {return fParticleSDLMod;}
// Functions related to/required by plModifier
virtual int GetNumTargets() const { return fTarget ? 1 : 0; }
virtual plSceneObject* GetTarget(int w) const { hsAssert(w < 1, "Bad target"); return fTarget; }
virtual void AddTarget(plSceneObject* so);
virtual void RemoveTarget(plSceneObject* so);
virtual void Read(hsStream* s, hsResMgr* mgr);
virtual void Write(hsStream* s, hsResMgr* mgr);
virtual hsBool MsgReceive(plMessage* msg);
void SetAttachedToAvatar(bool attached);
// Export only functions for building the system. Not supported at runtime.
// AddLight allows the particle system to remain in ignorant bliss about runtime lights
void AddLight(plKey liKey);
};
#endif