You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
911 lines
26 KiB
911 lines
26 KiB
/*==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(); |
|
} |
|
|
|
|
|
|
|
|