/*==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 . 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); } }