|
|
|
/*==LICENSE==*
|
|
|
|
|
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools
|
|
|
|
Copyright (C) 2011 Cyan Worlds, Inc.
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
Additional permissions under GNU GPL version 3 section 7
|
|
|
|
|
|
|
|
If you modify this Program, or any covered work, by linking or
|
|
|
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
|
|
|
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
|
|
|
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
|
|
|
|
(or a modified version of those libraries),
|
|
|
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
|
|
|
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
|
|
|
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
|
|
|
|
licensors of this Program grant you additional
|
|
|
|
permission to convey the resulting work. Corresponding Source for a
|
|
|
|
non-source form of such a combination shall include the source code for
|
|
|
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
|
|
|
|
work.
|
|
|
|
|
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com
|
|
|
|
or by snail mail at:
|
|
|
|
Cyan Worlds, Inc.
|
|
|
|
14617 N Newport Hwy
|
|
|
|
Mead, WA 99021
|
|
|
|
|
|
|
|
*==LICENSE==*/
|
|
|
|
#include "hsTypes.h"
|
|
|
|
#include "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();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|