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.
454 lines
15 KiB
454 lines
15 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 "hsStream.h" |
|
#include "hsFastMath.h" |
|
#include "hsUtils.h" |
|
#include "plParticle.h" |
|
#include "plParticleSystem.h" |
|
#include "plParticleEmitter.h" |
|
#include "plParticleGenerator.h" |
|
#include "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); |
|
} |
|
} |