粒子系统
粒子系统本质就是同时渲染很多个quad.它有两种运动模式,一种是重力,一种是旋转。
/********************************************************************
Copyright(C), 2012-2013,
FileName:ParticleSystem.h
Description:
Author:cloud
Created:2014/11/07
history:
7:11:2014 15:41 by
*********************************************************************/
#pragma once
#include "export/Node.h"
#include "base/GameMath.h"
#include "base/Value.h"
#include "base/TextureManager.h"
#include "base/render/Quad.h"
namespace cloud
{
enum kParticleTypes {
kParticleTypeGravity,
kParticleTypeRadial
};
// Structure used to hold particle specific information
typedef struct {
Vec2 position;
Vec2 direction;
Vec2 startPos;
Color color;
Color deltaColor;
float rotation;
float rotationDelta;
float radialAcceleration;
float tangentialAcceleration;
float radius;
float radiusDelta;
float angle;
float degreesPerSecond;
float particleSize;
float particleSizeDelta;
float timeToLive;
} Particle;
class ParticleSystem :public Node
{
public:
ParticleSystem(const char* particleList);
~ParticleSystem();
void update(float dt);
//定时喷射粒子和到点结束粒子
void managerParticleLife( float dt );
void stopSystem();
void reset();
void initParticle(const std::string& dirPath) ;
bool isAutoRemoveOnFinish() const;
void setAutoRemoveOnFinish(bool var);
protected:
virtual void updateParticleQuads(Particle *currentParticle);
void initWithDictionary(ValueMap& dictionary);
// Adds a particle from the particle pool to the emitter
bool addParticle();
// Initialises a particle ready for use
void initParticle(Particle* particle);
// Set up the arrays that are going to store our particles
int _width;
int _height;
std::string imgPath;
std::string imageData;
bool _isAutoRemoveOnFinish;
int emitterType;
//GLKTextureInfo *texture;
//粒子生命周期
int _totalParticles;
int _particleCount;
float _emissionRate;
float _emitCounter;
float _elapsedTime;
float _duration;
Vec2 sourcePosition, sourcePositionVariance;
float angle, angleVariance;
float speed, speedVariance;
float radialAcceleration, tangentialAcceleration;
float radialAccelVariance, tangentialAccelVariance;
Vec2 gravity;
float particleLifespan, particleLifespanVariance;
Color startColor, startColorVariance;
Color finishColor, finishColorVariance;
float startParticleSize, startParticleSizeVariance;
float finishParticleSize, finishParticleSizeVariance;
float rotationStart, rotationStartVariance;
float rotationEnd, rotationEndVariance;
int vertexArrayName;
int blendFuncSource, blendFuncDestination; // to _blendFunc
Particle ivars only used when a maxRadius value is provided. These values are used for
the special purpose of creating the spinning portal emitter
float maxRadius; // Max radius at which particles are drawn when rotating
float maxRadiusVariance; // Variance of the maxRadius
float radiusSpeed; // The speed at which a particle moves from maxRadius to minRadius
float minRadius; // Radius from source below which a particle dies
float minRadiusVariance; // Variance of the minRadius
float rotatePerSecond; // Numeber of degress to rotate a particle around the source pos per second
float rotatePerSecondVariance; // Variance in degrees for rotatePerSecond
Particle Emitter iVars
bool _isActive;
bool useTexture;
int particleIndex; // Stores the number of particles that are going to be rendered
int vertexIndex; // Stores the index of the vertices being used for each particle
Particle *particles; // Array of particles that hold the particle emitters particle details
};
}
/********************************************************************
Copyright(C), 2012-2013,
FileName:ParticleSystem.cpp
Description:
Author:cloud
Created:2014/11/07
history:
7:11:2014 15:42 by
*********************************************************************/
#include "base/fileUtils/FileUtils.h"
#include "base/TextureManager.h"
#include "base/EngineMacro.h"
#include "ParticleSystem.h"
//#define POSITION_TYPE
namespace cloud
{
ParticleSystem::ParticleSystem(const char* particleList):particles(NULL),_isAutoRemoveOnFinish(false)
{
ValueMap dictionary = FileUtils::getInstance()->getDictFormFile(particleList);
initWithDictionary(dictionary);
}
bool ParticleSystem::isAutoRemoveOnFinish() const
{
return _isAutoRemoveOnFinish;
}
void ParticleSystem::setAutoRemoveOnFinish(bool var)
{
_isAutoRemoveOnFinish = var;
}
void ParticleSystem::initParticle(const std::string& dirPath)
{
particles = (Particle*)malloc( sizeof(Particle) * _totalParticles );
// By default the particle emitter is active when created
_isActive = true;
// Set the particle count to zero
_particleCount = 0;
// Reset the elapsed time
_elapsedTime = 0;
scheduleUpdate();
}
ParticleSystem::~ParticleSystem()
{
if(particles)
{
free(particles);
particles = NULL;
}
}
void ParticleSystem::initWithDictionary(ValueMap& dictionary)
{
// Load all of the values from the XML file into the particle emitter. The functions below are using the
// TBXMLAdditions category. This adds convenience methods to TBXML to help cut down on the code in this method.
_totalParticles = dictionary["maxParticles"].asInt();
emitterType = dictionary["emitterType"].asInt();
sourcePosition = Vec2(dictionary["sourcePositionx"].asFloat(),dictionary["sourcePositiony"].asFloat());
sourcePositionVariance = Vec2(dictionary["sourcePositionVariancex"].asFloat(),dictionary["sourcePositionVariancey"].asFloat());
speed = dictionary["speed"].asFloat();
speedVariance = dictionary["speedVariance"].asFloat();
particleLifespan = dictionary["particleLifeSpan"].asFloat();
particleLifespanVariance = dictionary["particleLifespanVariance"].asFloat();
angle = dictionary["angle"].asFloat();
angleVariance = dictionary["angleVariance"].asFloat();
gravity = Vec2(dictionary["gravityx"].asFloat(),dictionary["gravityy"].asFloat());
radialAcceleration = dictionary["radialAcceleration"].asFloat();
radialAccelVariance = dictionary["radialAccelVariance"].asFloat();
tangentialAcceleration = dictionary["tangentialAcceleration"].asFloat();
tangentialAccelVariance = dictionary["tangentialAccelVariance"].asFloat();
startColor = Color(dictionary["startColorRed"].asFloat(),dictionary["startColorGreen"].asFloat(),dictionary["startColorBlue"].asFloat(),dictionary["startColorAlpha"].asFloat());
startColorVariance = Color(dictionary["startColorVarianceRed"].asFloat(),dictionary["startColorVarianceGreen"].asFloat(),dictionary["startColorVarianceBlue"].asFloat(),dictionary["startColorVarianceAlpha"].asFloat());
finishColor = Color(dictionary["finishColorRed"].asFloat(),dictionary["finishColorGreen"].asFloat(),dictionary["finishColorBlue"].asFloat(),dictionary["finishColorAlpha"].asFloat());
finishColorVariance = Color(dictionary["finishColorVarianceRed"].asFloat(),dictionary["finishColorVarianceGreen"].asFloat(),dictionary["finishColorVarianceBlue"].asFloat(),dictionary["finishColorVarianceAlpha"].asFloat());
startParticleSize = dictionary["startParticleSize"].asFloat();
startParticleSizeVariance = dictionary["startParticleSizeVariance"].asFloat();
finishParticleSize = dictionary["finishParticleSize"].asFloat();
finishParticleSizeVariance = dictionary["finishParticleSizeVariance"].asFloat();
_duration = dictionary["duration"].asFloat();
blendFuncSource = dictionary["blendFuncSource"].asInt();
blendFuncDestination = dictionary["blendFuncDestination"].asInt();
// These paramters are used when you want to have the particles spinning around the source location
maxRadius = dictionary["maxRadius"].asFloat();
maxRadiusVariance = dictionary["maxRadiusVariance"].asFloat();
minRadius = dictionary["minRadius"].asFloat();
minRadiusVariance = dictionary["minRadiusVariance"].asFloat();
rotatePerSecond = dictionary["rotatePerSecond"].asFloat();
rotatePerSecondVariance = dictionary["rotatePerSecondVariance"].asFloat();
rotationStart = dictionary["rotationStart"].asFloat();
rotationStartVariance = dictionary["rotationStartVariance"].asFloat();
rotationEnd = dictionary["rotationEnd"].asFloat();
rotationEndVariance = dictionary["rotationEndVariance"].asFloat();
// Calculate the emission rate
_emissionRate = _totalParticles / particleLifespan;
_emitCounter = 0;
imgPath = dictionary["textureFileName"].asString();
//_texture = TextureManager::getInstance()->createTexture(_imgPath.c_str(),_width,_height);
imageData = dictionary["textureImageData"].asString();
}
// Adds a particle from the particle pool to the emitter
bool ParticleSystem::addParticle()
{
// If we have already reached the maximum number of particles then do nothing
if (_particleCount == _totalParticles)
return false;
// Take the next particle out of the particle pool we have created and initialize it
Particle *particle = &particles[_particleCount];
initParticle(particle);
// Increment the particle count
_particleCount++;
// Return YES to show that a particle has been created
return true;
}
// Initialises a particle ready for use
void ParticleSystem::initParticle(Particle* particle)
{
// Init the position of the particle. This is based on the source position of the particle emitter
// plus a configured variance. The RANDOM_MINUS_1_TO_1 macro allows the number to be both positive
// and negative
particle->position.x = sourcePosition.x + sourcePositionVariance.x * RANDOM_MINUS_1_TO_1();
particle->position.y = sourcePosition.y + sourcePositionVariance.y * RANDOM_MINUS_1_TO_1();
particle->startPos.x = sourcePosition.x;
particle->startPos.y = sourcePosition.y;
// Init the direction of the particle. The newAngle is calculated using the angle passed in and the
// angle variance.
GLfloat newAngle = angleToRadian(angle + angleVariance * RANDOM_MINUS_1_TO_1());
// Create a new GLKVector2 using the newAngle
Vec2 vector = Vec2(cosf(newAngle), sinf(newAngle));
// Calculate the vectorSpeed using the speed and speedVariance which has been passed in
GLfloat vectorSpeed = speed + speedVariance * RANDOM_MINUS_1_TO_1();
// The particles direction vector is calculated by taking the vector calculated above and
// multiplying that by the speed
particle->direction = vector * vectorSpeed;
// Calculate the particles life span using the life span and variance passed in
particle->timeToLive = MAX(0, particleLifespan + particleLifespanVariance * RANDOM_MINUS_1_TO_1());
float startRadius = maxRadius + maxRadiusVariance * RANDOM_MINUS_1_TO_1();
float endRadius = minRadius + minRadiusVariance * RANDOM_MINUS_1_TO_1();
// Set the default diameter of the particle from the source position
particle->radius = startRadius;
particle->radiusDelta = (endRadius - startRadius) / particle->timeToLive;
particle->angle = angleToRadian(angle + angleVariance * RANDOM_MINUS_1_TO_1());
particle->degreesPerSecond = angleToRadian(rotatePerSecond + rotatePerSecondVariance * RANDOM_MINUS_1_TO_1());
particle->radialAcceleration = radialAcceleration + radialAccelVariance * RANDOM_MINUS_1_TO_1();
particle->tangentialAcceleration = tangentialAcceleration + tangentialAccelVariance * RANDOM_MINUS_1_TO_1();
// Calculate the particle size using the start and finish particle sizes
GLfloat particleStartSize = startParticleSize + startParticleSizeVariance * RANDOM_MINUS_1_TO_1();
GLfloat particleFinishSize = finishParticleSize + finishParticleSizeVariance * RANDOM_MINUS_1_TO_1();
particle->particleSizeDelta = ((particleFinishSize - particleStartSize) / particle->timeToLive);
particle->particleSize = MAX(0, particleStartSize);
// Calculate the color the particle should have when it starts its life. All the elements
// of the start color passed in along with the variance are used to calculate the star color
Color start = Color(0, 0, 0, 0);
start.r = startColor.r + startColorVariance.r * RANDOM_MINUS_1_TO_1();
start.g = startColor.g + startColorVariance.g * RANDOM_MINUS_1_TO_1();
start.b = startColor.b + startColorVariance.b * RANDOM_MINUS_1_TO_1();
start.a = startColor.a + startColorVariance.a * RANDOM_MINUS_1_TO_1();
// Calculate the color the particle should be when its life is over. This is done the same
// way as the start color above
Color end = Color(0, 0, 0, 0);
end.r = finishColor.r + finishColorVariance.r * RANDOM_MINUS_1_TO_1();
end.g = finishColor.g + finishColorVariance.g * RANDOM_MINUS_1_TO_1();
end.b = finishColor.b + finishColorVariance.b * RANDOM_MINUS_1_TO_1();
end.a = finishColor.a + finishColorVariance.a * RANDOM_MINUS_1_TO_1();
// Calculate the delta which is to be applied to the particles color during each cycle of its
// life. The delta calculation uses the life span of the particle to make sure that the
// particles color will transition from the start to end color during its life time. As the game
// loop is using a fixed delta value we can calculate the delta color once saving cycles in the
// update method
particle->color = start;
particle->deltaColor.r = ((end.r - start.r) / particle->timeToLive);
particle->deltaColor.g = ((end.g - start.g) / particle->timeToLive);
particle->deltaColor.b = ((end.b - start.b) / particle->timeToLive);
particle->deltaColor.a = ((end.a - start.a) / particle->timeToLive);
// Calculate the rotation
GLfloat startA = rotationStart + rotationStartVariance * RANDOM_MINUS_1_TO_1();
GLfloat endA = rotationEnd + rotationEndVariance * RANDOM_MINUS_1_TO_1();
particle->rotation = startA;
particle->rotationDelta = (endA - startA) / particle->timeToLive;
}
void ParticleSystem::stopSystem()
{
_isActive = false;
_elapsedTime = 0;
_emitCounter = 0;
}
void ParticleSystem::reset()
{
_isActive = true;
_elapsedTime = 0;
for (int i = 0; i < _particleCount; i++) {
Particle *p = &particles[i];
p->timeToLive = 0;
}
_emitCounter = 0;
_emissionRate = _totalParticles / particleLifespan;
}
void ParticleSystem::update(float dt)
{
managerParticleLife(dt);
// Reset the particle index before updating the particles in this emitter
particleIndex = 0;
#ifdef POSITION_TYPE
Vec2 currentPosition = Vec2::ZERO;
if (_positionType == PositionType::FREE)
{
currentPosition = this->convertToWorldSpace(Vec2::ZERO);
}
else if (_positionType == PositionType::RELATIVE)
{
currentPosition = _position;
}
#endif
// Loop through all the particles updating their location and color
while (particleIndex < _particleCount) {
// Get the particle for the current particle index
Particle *currentParticle = &particles[particleIndex];
// FIX 1
// Reduce the life span of the particle
currentParticle->timeToLive -= dt;
// If the current particle is alive then update it
if (currentParticle->timeToLive > 0) {
// If maxRadius is greater than 0 then the particles are going to spin otherwise they are effected by speed and gravity
if (emitterType == kParticleTypeRadial) {
// FIX 2
// Update the angle of the particle from the sourcePosition and the radius. This is only done of the particles are rotating
currentParticle->angle += currentParticle->degreesPerSecond * dt;
currentParticle->radius += currentParticle->radiusDelta * dt;
Vec2 tmp;
tmp.x = sourcePosition.x - cosf(currentParticle->angle) * currentParticle->radius;
tmp.y = sourcePosition.y - sinf(currentParticle->angle) * currentParticle->radius;
currentParticle->position = tmp;
} else {
Vec2 tmp, radial, tangential;
radial = Vec2(0,0);
// By default this emitters particles are moved relative to the emitter node position
Vec2 positionDifference = currentParticle->startPos;
currentParticle->position = currentParticle->position - positionDifference;
if (currentParticle->position.x || currentParticle->position.y)
radial = currentParticle->position.getNormalized();
tangential = radial;
radial = radial * currentParticle->radialAcceleration;
float newy = tangential.x;
tangential.x = -tangential.y;
tangential.y = newy;
tangential = tangential * currentParticle->tangentialAcceleration;
tmp = radial+tangential + gravity;
tmp = tmp * dt;
currentParticle->direction = currentParticle->direction + tmp;
tmp = currentParticle->direction * dt;
currentParticle->position = currentParticle->position + tmp;
// Now apply the difference calculated early causing the particles to be relative in position to the emitter position
currentParticle->position = currentParticle->position + positionDifference;
}
// Update the particles color
currentParticle->color.r += currentParticle->deltaColor.r * dt;
currentParticle->color.g += currentParticle->deltaColor.g * dt;
currentParticle->color.b += currentParticle->deltaColor.b * dt;
currentParticle->color.a += currentParticle->deltaColor.a * dt;
// Update the particle size
currentParticle->particleSize += currentParticle->particleSizeDelta * dt;
currentParticle->particleSize = MAX(0, currentParticle->particleSize);
// Update the rotation of the particle
currentParticle->rotation += currentParticle->rotationDelta * dt;
// As we are rendering the particles as quads, we need to define 6 vertices for each particle
// If a rotation has been defined for this particle then apply the rotation to the vertices that define
// the particle
#ifdef POSITION_TYPE
Vec2 newPos;
if (_positionType == PositionType::FREE || _positionType == PositionType::RELATIVE)
{
Vec2 diff = currentPosition - p->startPos;
newPos = p->pos - diff;
}
else
{
newPos = p->pos;
}
#endif
updateParticleQuads(currentParticle);
// Update the particle and vertex counters
particleIndex++;
} else {
// As the particle is not alive anymore replace it with the last active particle
// in the array and reduce the count of particles by one. This causes all active particles
// to be packed together at the start of the array so that a particle which has run out of
// life will only drop into this clause once
if (particleIndex != _particleCount - 1)
particles[particleIndex] = particles[_particleCount - 1];
_particleCount--;
if( _particleCount == 0 && _isAutoRemoveOnFinish )
{
this->unscheduleUpdate();
_parent->removeNodeAndChild(this);
return;
}
}
}
}
void ParticleSystem::updateParticleQuads(Particle *currentParticle)
{
}
void ParticleSystem::managerParticleLife( float dt )
{
// If the emitter is active and the emission rate is greater than zero then emit particles
if (_isActive && _emissionRate)
{
GLfloat rate = 1.0f/_emissionRate;//每个粒子的喷射时间
if (_particleCount < _totalParticles)
_emitCounter += dt;
while (_particleCount < _totalParticles && _emitCounter > rate) {
addParticle();
_emitCounter -= rate;
}
_elapsedTime += dt;
if (_duration != -1 && _duration < _elapsedTime)
stopSystem();
}
}
}
渲染块
/********************************************************************
Copyright(C), 2012-2013,
FileName:ParticleSystem.h
Description:
Author:cloud
Created:2014/11/07
history:
7:11:2014 15:41 by
*********************************************************************/
#pragma once
#include "export/particle/ParticleSystem.h"
#include "export/Sprite.h"
namespace cloud
{
class ParticleSystemQuad :public ParticleSystem
{
public:
ParticleSystemQuad(const char* particleList);
~ParticleSystemQuad();
void initParticleQuads(const std::string& dirPath) ;
void render();
protected:
void updateParticleQuads(Particle *currentParticle);
bool _opacityModifyRGB;
Quad* _quads;
GLuint _textureID;
QuadCommand _quadCommand;
BlendFunc _blendFunc;
};
}
/********************************************************************
Copyright(C), 2012-2013,
FileName:ParticleSystem.cpp
Description:
Author:cloud
Created:2014/11/07
history:
7:11:2014 15:42 by
*********************************************************************/
#include "base/fileUtils/FileUtils.h"
#include "base/TextureManager.h"
#include "base/EngineMacro.h"
#include "export/Particle/ParticleSystemQuad.h"
#include "base/render/Renderer.h"
namespace cloud
{
ParticleSystemQuad::ParticleSystemQuad(const char* particleList):ParticleSystem(particleList),_quads(NULL),_textureID(0)
{
std::string dirPath = particleList;
int pos = dirPath.rfind("/");
if (pos > -1)
{
dirPath = dirPath.substr(0,pos+1);
}
else
{
dirPath = "";
}
initParticleQuads(dirPath);
}
ParticleSystemQuad::~ParticleSystemQuad()
{
if(_quads)
{
free(_quads);
_quads = NULL;
}
}
void ParticleSystemQuad::initParticleQuads(const std::string& dirPath)
{
_quads = (Quad*)malloc( _totalParticles * sizeof(Quad));
memset(_quads, 0, _totalParticles * sizeof(Quad));
for(int i = 0;i < _totalParticles; ++i)
{
_quads[i].tl._texCoord = Vec2(0,1);
_quads[i].bl._texCoord = Vec2(0,0);
_quads[i].tr._texCoord = Vec2(1,1);
_quads[i].br._texCoord = Vec2(1,0);
}
_opacityModifyRGB = false;
//TODO:图片alpha是否预处理二进制数据
if (_textureID == 0 )
{
//搜索路径为plist目录下的
imgPath = dirPath + imgPath;
_textureID = TextureManager::getInstance()->createTexture(imgPath.c_str(),_width,_height);
if (_textureID == 0 && imageData.length() > 0)
{
//TODO:解析图片二进制数据
}
}
_blendFunc = BlendFunc(blendFuncSource,blendFuncDestination);
ParticleSystem::initParticle(dirPath);
}
void ParticleSystemQuad::render()
{
updateMatrix();
_quadCommand.init(_quads,_textureID,particleIndex,_blendFunc,_model);
Renderer::getInstance()->addQuadCommand(&_quadCommand);
}
void ParticleSystemQuad::updateParticleQuads(Particle *currentParticle)
{
float halfSize = currentParticle->particleSize * 0.5f;
if (currentParticle->rotation) {
float x1 = -halfSize;
float y1 = -halfSize;
float x2 = halfSize;
float y2 = halfSize;
float x = currentParticle->position.x;
float y = currentParticle->position.y;
float r = angleToRadian(currentParticle->rotation);
float cr = cosf(r);
float sr = sinf(r);
float ax = x1 * cr - y1 * sr + x;
float ay = x1 * sr + y1 * cr + y;
float bx = x2 * cr - y1 * sr + x;
float by = x2 * sr + y1 * cr + y;
float cx = x2 * cr - y2 * sr + x;
float cy = x2 * sr + y2 * cr + y;
float dx = x1 * cr - y2 * sr + x;
float dy = x1 * sr + y2 * cr + y;
_quads[particleIndex].bl._position.x = ax;
_quads[particleIndex].bl._position.y = ay;
_quads[particleIndex].bl._color = currentParticle->color;
_quads[particleIndex].br._position.x = bx;
_quads[particleIndex].br._position.y = by;
_quads[particleIndex].br._color = currentParticle->color;
_quads[particleIndex].tl._position.x = dx;
_quads[particleIndex].tl._position.y = dy;
_quads[particleIndex].tl._color = currentParticle->color;
_quads[particleIndex].tr._position.x = cx;
_quads[particleIndex].tr._position.y = cy;
_quads[particleIndex].tr._color = currentParticle->color;
} else {
// Using the position of the particle, work out the four vertices for the quad that will hold the particle
// and load those into the _quads array.
_quads[particleIndex].bl._position.x = currentParticle->position.x - halfSize;
_quads[particleIndex].bl._position.y = currentParticle->position.y - halfSize;
_quads[particleIndex].bl._color = currentParticle->color;
_quads[particleIndex].br._position.x = currentParticle->position.x + halfSize;
_quads[particleIndex].br._position.y = currentParticle->position.y - halfSize;
_quads[particleIndex].br._color = currentParticle->color;
_quads[particleIndex].tl._position.x = currentParticle->position.x - halfSize;
_quads[particleIndex].tl._position.y = currentParticle->position.y + halfSize;
_quads[particleIndex].tl._color = currentParticle->color;
_quads[particleIndex].tr._position.x = currentParticle->position.x + halfSize;
_quads[particleIndex].tr._position.y = currentParticle->position.y + halfSize;
_quads[particleIndex].tr._color = currentParticle->color;
}
}
}