2
0
Эх сурвалжийг харах

Fixed an issue with particle emitters where they would update incorrectly with very high frame rates.
Exposed some missing properties from ParticleEmitter.
Implemented ParticleEmitter cloning.

Steve Grenier 12 жил өмнө
parent
commit
4ac3960398

+ 6 - 0
gameplay/src/Node.cpp

@@ -1037,6 +1037,12 @@ void Node::cloneInto(Node* node, NodeCloneContext &context) const
         node->setModel(modelClone);
         modelClone->release();
     }
+    if (ParticleEmitter* emitter = getParticleEmitter())
+    {
+        ParticleEmitter* emitterClone = emitter->clone();
+        node->setParticleEmitter(emitterClone);
+        emitterClone->release();
+    }
     node->_world = _world;
     node->_bounds = _bounds;
 

+ 166 - 59
gameplay/src/ParticleEmitter.cpp

@@ -9,11 +9,12 @@
 #define PARTICLE_COUNT_MAX                       100
 #define PARTICLE_EMISSION_RATE                   10
 #define PARTICLE_EMISSION_RATE_TIME_INTERVAL     1000.0f / (float)PARTICLE_EMISSION_RATE
+#define PARTICLE_UPDATE_RATE_MAX                 16
 
 namespace gameplay
 {
 
-ParticleEmitter::ParticleEmitter(SpriteBatch* batch, unsigned int particleCountMax) :
+ParticleEmitter::ParticleEmitter(unsigned int particleCountMax) :
     _particleCountMax(particleCountMax), _particleCount(0), _particles(NULL),
     _emissionRate(PARTICLE_EMISSION_RATE), _started(false), _ellipsoid(false),
     _sizeStartMin(1.0f), _sizeStartMax(1.0f), _sizeEndMin(1.0f), _sizeEndMax(1.0f),
@@ -25,18 +26,13 @@ ParticleEmitter::ParticleEmitter(SpriteBatch* batch, unsigned int particleCountM
     _rotationPerParticleSpeedMin(0.0f), _rotationPerParticleSpeedMax(0.0f),
     _rotationSpeedMin(0.0f), _rotationSpeedMax(0.0f),
     _rotationAxis(Vector3::zero()), _rotation(Matrix::identity()),
-    _spriteBatch(batch), _spriteTextureBlending(BLEND_TRANSPARENT),  _spriteTextureWidth(0), _spriteTextureHeight(0), _spriteTextureWidthRatio(0), _spriteTextureHeightRatio(0), _spriteTextureCoords(NULL),
+    _spriteBatch(NULL), _spriteTextureBlending(BLEND_TRANSPARENT),  _spriteTextureWidth(0), _spriteTextureHeight(0), _spriteTextureWidthRatio(0), _spriteTextureHeightRatio(0), _spriteTextureCoords(NULL),
     _spriteAnimated(false),  _spriteLooped(false), _spriteFrameCount(1), _spriteFrameRandomOffset(0),_spriteFrameDuration(0L), _spriteFrameDurationSecs(0.0f), _spritePercentPerFrame(0.0f),
     _node(NULL), _orbitPosition(false), _orbitVelocity(false), _orbitAcceleration(false),
-    _timePerEmission(PARTICLE_EMISSION_RATE_TIME_INTERVAL), _timeRunning(0)
+    _timePerEmission(PARTICLE_EMISSION_RATE_TIME_INTERVAL), _emitTime(0), _lastUpdated(0)
 {
     GP_ASSERT(particleCountMax);
     _particles = new Particle[particleCountMax];
-
-    GP_ASSERT(_spriteBatch);
-    GP_ASSERT(_spriteBatch->getStateBlock());
-    _spriteBatch->getStateBlock()->setDepthWrite(false);
-    _spriteBatch->getStateBlock()->setDepthTest(true);
 }
 
 ParticleEmitter::~ParticleEmitter()
@@ -48,7 +44,7 @@ ParticleEmitter::~ParticleEmitter()
 
 ParticleEmitter* ParticleEmitter::create(const char* textureFile, TextureBlending textureBlending, unsigned int particleCountMax)
 {
-    Texture* texture = Texture::create(textureFile, false);
+    Texture* texture = Texture::create(textureFile, true);
 
     if (!texture)
     {
@@ -58,23 +54,15 @@ ParticleEmitter* ParticleEmitter::create(const char* textureFile, TextureBlendin
     GP_ASSERT(texture->getWidth());
     GP_ASSERT(texture->getHeight());
 
-    // Use default SpriteBatch material.
-    SpriteBatch* batch =  SpriteBatch::create(texture, NULL, particleCountMax);
-    texture->release(); // batch owns the texture.
-    GP_ASSERT(batch);
+    return ParticleEmitter::create(texture, textureBlending, particleCountMax);
+}
 
-    ParticleEmitter* emitter = new ParticleEmitter(batch, particleCountMax);
+ParticleEmitter* ParticleEmitter::create(Texture* texture, TextureBlending textureBlending,  unsigned int particleCountMax)
+{
+    ParticleEmitter* emitter = new ParticleEmitter(particleCountMax);
     GP_ASSERT(emitter);
 
-    // By default assume only one frame which uses the entire texture.
-    emitter->setTextureBlending(textureBlending);
-    emitter->_spriteTextureWidth = texture->getWidth();
-    emitter->_spriteTextureHeight = texture->getHeight();
-    emitter->_spriteTextureWidthRatio = 1.0f / (float)texture->getWidth();
-    emitter->_spriteTextureHeightRatio = 1.0f / (float)texture->getHeight();
-
-    Rectangle texCoord((float)texture->getWidth(), (float)texture->getHeight());
-    emitter->setSpriteFrameCoords(1, &texCoord);
+    emitter->setTexture(texture, textureBlending);
 
     return emitter;
 }
@@ -212,6 +200,61 @@ ParticleEmitter* ParticleEmitter::create(Properties* properties)
     return emitter;
 }
 
+void ParticleEmitter::setTexture(const char* texturePath, TextureBlending textureBlending)
+{
+    Texture* texture = Texture::create(texturePath, true);
+    if (texture)
+    {
+        setTexture(texture, textureBlending);
+        texture->release();
+    }
+    else
+    {
+        GP_WARN("Failed set new texture on particle emitter: %s", texturePath);
+    }
+}
+
+void ParticleEmitter::setTexture(Texture* texture, TextureBlending textureBlending)
+{
+    // Create new batch before releasing old one, in case the same texture
+    // is used for both (so it's not released before passing to the new batch).
+    SpriteBatch* batch =  SpriteBatch::create(texture, NULL, _particleCountMax);
+    batch->getSampler()->setFilterMode(Texture::LINEAR_MIPMAP_LINEAR, Texture::LINEAR);
+
+    // Free existing batch
+    SAFE_DELETE(_spriteBatch);
+
+    _spriteBatch = batch;
+    _spriteBatch->getStateBlock()->setDepthWrite(false);
+    _spriteBatch->getStateBlock()->setDepthTest(true);
+
+    setTextureBlending(textureBlending);
+    _spriteTextureWidth = texture->getWidth();
+    _spriteTextureHeight = texture->getHeight();
+    _spriteTextureWidthRatio = 1.0f / (float)texture->getWidth();
+    _spriteTextureHeightRatio = 1.0f / (float)texture->getHeight();
+
+    // By default assume only one frame which uses the entire texture.
+    Rectangle texCoord((float)texture->getWidth(), (float)texture->getHeight());
+    setSpriteFrameCoords(1, &texCoord);
+}
+
+Texture* ParticleEmitter::getTexture() const
+{
+    Texture::Sampler* sampler = _spriteBatch ? _spriteBatch->getSampler() : NULL;
+    return sampler? sampler->getTexture() : NULL;
+}
+
+void ParticleEmitter::setParticleCountMax(unsigned int max)
+{
+    _particleCountMax = max;
+}
+
+unsigned int ParticleEmitter::getParticleCountMax() const
+{
+    return _particleCountMax;
+}
+
 unsigned int ParticleEmitter::getEmissionRate() const
 {
     return _emissionRate;
@@ -227,6 +270,7 @@ void ParticleEmitter::setEmissionRate(unsigned int rate)
 void ParticleEmitter::start()
 {
     _started = true;
+    _lastUpdated = 0;
 }
 
 void ParticleEmitter::stop()
@@ -247,18 +291,7 @@ bool ParticleEmitter::isActive() const
     if (!_node)
         return false;
 
-    GP_ASSERT(_particles);
-    bool active = false;
-    for (unsigned int i = 0; i < _particleCount; i++)
-    {
-        if (_particles[i]._energy > 0)
-        {
-            active = true;
-            break;
-        }
-    }
-
-    return active;
+    return (_particleCount > 0);
 }
 
 void ParticleEmitter::emitOnce(unsigned int particleCount)
@@ -285,7 +318,6 @@ void ParticleEmitter::emitOnce(unsigned int particleCount)
     for (unsigned int i = 0; i < particleCount; i++)
     {
         Particle* p = &_particles[_particleCount];
-        p->_visible = true;
 
         generateColor(_colorStart, _colorStartVar, &p->_colorStart);
         generateColor(_colorEnd, _colorEndVar, &p->_colorEnd);
@@ -553,6 +585,13 @@ void ParticleEmitter::setTextureBlending(TextureBlending textureBlending)
             GP_ERROR("Unsupported texture blending mode (%d).", textureBlending);
             break;
     }
+
+    _spriteTextureBlending = textureBlending;
+}
+
+ParticleEmitter::TextureBlending ParticleEmitter::getTextureBlending() const
+{
+    return _spriteTextureBlending;
 }
 
 void ParticleEmitter::setSpriteAnimated(bool animated)
@@ -597,6 +636,16 @@ long ParticleEmitter::getSpriteFrameDuration() const
     return _spriteFrameDuration;
 }
 
+unsigned int ParticleEmitter::getSpriteWidth() const
+{
+    return (unsigned int)fabs(_spriteTextureWidth * (_spriteTextureCoords[2] - _spriteTextureCoords[0]));
+}
+
+unsigned int ParticleEmitter::getSpriteHeight() const
+{
+    return (unsigned int)fabs(_spriteTextureHeight * (_spriteTextureCoords[3] - _spriteTextureCoords[1]));
+}
+
 void ParticleEmitter::setSpriteTexCoords(unsigned int frameCount, float* texCoords)
 {
     GP_ASSERT(frameCount);
@@ -667,6 +716,11 @@ void ParticleEmitter::setSpriteFrameCoords(unsigned int frameCount, int width, i
     SAFE_DELETE_ARRAY(frameCoords);
 }
 
+unsigned int ParticleEmitter::getSpriteFrameCount() const
+{
+    return _spriteFrameCount;
+}
+
 Node* ParticleEmitter::getNode() const
 {
     return _node;
@@ -685,6 +739,21 @@ void ParticleEmitter::setOrbit(bool orbitPosition, bool orbitVelocity, bool orbi
     _orbitAcceleration = orbitAcceleration;
 }
 
+bool ParticleEmitter::getOrbitPosition() const
+{
+    return _orbitPosition;
+}
+
+bool ParticleEmitter::getOrbitVelocity() const
+{
+    return _orbitVelocity;
+}
+
+bool ParticleEmitter::getOrbitAcceleration() const
+{
+    return _orbitAcceleration;
+}
+
 long ParticleEmitter::generateScalar(long min, long max)
 {
     // Note: this is not a very good RNG, but it should be suitable for our purposes.
@@ -792,41 +861,46 @@ ParticleEmitter::TextureBlending ParticleEmitter::getTextureBlendingFromString(c
 void ParticleEmitter::update(float elapsedTime)
 {
     if (!isActive())
-    {
         return;
-    }
 
-    // Calculate the time passed since last update.
-    float elapsedSecs = elapsedTime * 0.001f;
+    // Cap particle updates at a maximum rate. This saves processing
+    // and also improves precision since updating with very small
+    // time increments is more lossy.
+    static double runningTime = 0;
+    runningTime += elapsedTime;
+    if (runningTime < PARTICLE_UPDATE_RATE_MAX)
+        return;    
+
+    float elapsedMs = runningTime;
+    runningTime = 0;
+
+    float elapsedSecs = elapsedMs * 0.001f;
 
     if (_started && _emissionRate)
     {
         // Calculate how much time has passed since we last emitted particles.
-        _timeRunning += elapsedTime;
+        _emitTime += elapsedMs; //+= elapsedTime;
 
         // How many particles should we emit this frame?
         GP_ASSERT(_timePerEmission);
-        unsigned int emitCount = (unsigned int)(_timeRunning / _timePerEmission);
+        unsigned int emitCount = (unsigned int)(_emitTime / _timePerEmission);
 
         if (emitCount)
         {
             if ((int)_timePerEmission > 0)
             {
-                _timeRunning = fmod(_timeRunning, (double)_timePerEmission);
+                _emitTime = fmod(_emitTime, (double)_timePerEmission);
             }
             emitOnce(emitCount);
         }
     }
 
-    GP_ASSERT(_node && _node->getScene() && _node->getScene()->getActiveCamera());
-    const Frustum& frustum = _node->getScene()->getActiveCamera()->getFrustum();
-
     // Now update all currently living particles.
     GP_ASSERT(_particles);
     for (unsigned int particlesIndex = 0; particlesIndex < _particleCount; ++particlesIndex)
     {
         Particle* p = &_particles[particlesIndex];
-        p->_energy -= elapsedTime;
+        p->_energy -= elapsedMs;
 
         if (p->_energy > 0L)
         {
@@ -847,12 +921,6 @@ void ParticleEmitter::update(float elapsedTime)
             p->_position.y += p->_velocity.y * elapsedSecs;
             p->_position.z += p->_velocity.z * elapsedSecs;
 
-            if (!frustum.intersects(p->_position))
-            {
-                p->_visible = false;
-                continue;
-            }
-
             p->_angle += p->_rotationPerParticleSpeed * elapsedSecs;
 
             // Simple linear interpolation of color and size.
@@ -951,12 +1019,9 @@ void ParticleEmitter::draw()
         {
             Particle* p = &_particles[i];
 
-            if (p->_visible)
-            {
-                _spriteBatch->draw(p->_position, right, up, p->_size, p->_size,
-                                   _spriteTextureCoords[p->_frame * 4], _spriteTextureCoords[p->_frame * 4 + 1], _spriteTextureCoords[p->_frame * 4 + 2], _spriteTextureCoords[p->_frame * 4 + 3],
-                                   p->_color, pivot, p->_angle);
-            }
+            _spriteBatch->draw(p->_position, right, up, p->_size, p->_size,
+                                _spriteTextureCoords[p->_frame * 4], _spriteTextureCoords[p->_frame * 4 + 1], _spriteTextureCoords[p->_frame * 4 + 2], _spriteTextureCoords[p->_frame * 4 + 3],
+                                p->_color, pivot, p->_angle);
         }
 
         // Render.
@@ -964,4 +1029,46 @@ void ParticleEmitter::draw()
     }
 }
 
+ParticleEmitter* ParticleEmitter::clone()
+{
+    // Create a clone of this emitter
+    ParticleEmitter* emitter = ParticleEmitter::create(_spriteBatch->getSampler()->getTexture(), _spriteTextureBlending, _particleCountMax);
+
+    // Copy all properties to the clone
+    emitter->setEmissionRate(_emissionRate);
+    emitter->_ellipsoid = _ellipsoid;
+    emitter->_sizeStartMin = _sizeStartMin;
+    emitter->_sizeStartMax = _sizeStartMax;
+    emitter->_sizeEndMin = _sizeEndMin;
+    emitter->_sizeEndMax = _sizeEndMax;
+    emitter->_energyMin = _energyMin;
+    emitter->_energyMax = _energyMax;
+    emitter->_colorStart = _colorStart;
+    emitter->_colorStartVar = _colorStartVar;
+    emitter->_colorEnd = _colorEnd;
+    emitter->_colorEndVar = _colorEndVar;
+    emitter->_position = _position;
+    emitter->_positionVar = _positionVar;
+    emitter->_velocity = _velocity;
+    emitter->_velocityVar = _velocityVar;
+    emitter->_acceleration = _acceleration;
+    emitter->_accelerationVar = _accelerationVar;
+    emitter->_rotationPerParticleSpeedMin = _rotationPerParticleSpeedMin;
+    emitter->_rotationPerParticleSpeedMax = _rotationPerParticleSpeedMax;
+    emitter->_rotationSpeedMin = _rotationSpeedMin;
+    emitter->_rotationSpeedMax = _rotationSpeedMax;
+    emitter->_rotationAxis = _rotationAxis;
+    emitter->_rotationAxisVar = _rotationAxisVar;
+    emitter->setSpriteTexCoords(_spriteFrameCount, _spriteTextureCoords);
+    emitter->_spriteAnimated = _spriteAnimated;
+    emitter->_spriteLooped = _spriteLooped;
+    emitter->_spriteFrameRandomOffset = _spriteFrameRandomOffset;
+    emitter->setSpriteFrameDuration(_spriteFrameDuration);
+    emitter->_orbitPosition = _orbitPosition;
+    emitter->_orbitVelocity = _orbitVelocity;
+    emitter->_orbitAcceleration = _orbitAcceleration;
+
+    return emitter;
+}
+
 }

+ 114 - 4
gameplay/src/ParticleEmitter.h

@@ -185,6 +185,48 @@ public:
      */
     static ParticleEmitter* create(const char* texturePath, TextureBlending textureBlending,  unsigned int particleCountMax);
 
+    /**
+     * Sets a new texture for this particle emitter.
+     *
+     * The current texture's reference count is decreased.
+     *
+     * @param texturePath Path to the new texture to set.
+     * @param textureBlending Blending mode for the new texture.
+     */
+    void setTexture(const char* texturePath, TextureBlending textureBlending);
+
+    /**
+     * Sets a new texture for this particle emitter.
+     *
+     * The reference count of the specified texture is increased, and the 
+     * current texture's reference count is decreased.
+     *
+     * @param The new texture to set.
+     * @param textureBlending Blending mode for the new texture.
+     */
+    void setTexture(Texture* texture, TextureBlending textureBlending);
+
+    /**
+     * Returns the texture currently set for this particle emitter.
+     *
+     * @return The current texture.
+     */
+    Texture* getTexture() const;
+
+    /**
+     * Sets the maximum number of particles that can be emitted.
+     *
+     * @param max The maximum number of particles that can be emitted.
+     */
+    void setParticleCountMax(unsigned int max);
+
+    /**
+     * Returns the maximum number of particles that can be emitted.
+     *
+     * @return The maximum number of particles that can be emitted.
+     */
+    unsigned int getParticleCountMax() const;
+
     /**
      * Sets the emission rate, measured in particles per second.
      *
@@ -559,6 +601,20 @@ public:
      */
     long getSpriteFrameDuration() const;
 
+    /**
+     * Returns the width of the first frame this particle emitter's sprite.
+     *
+     * @return The width of the first frame of the sprite.
+     */
+    unsigned int getSpriteWidth() const;
+
+    /**
+     * Returns the height of the first frame this particle emitter's sprite.
+     *
+     * @return The height of the first frame of the sprite.
+     */
+    unsigned int getSpriteHeight() const;
+
     /**
      * Sets the sprite's texture coordinates in texture space.
      *
@@ -589,6 +645,13 @@ public:
      */
     void setSpriteFrameCoords(unsigned int frameCount, int width, int height);
 
+    /**
+     * Returns the current number of frames for the particle emitter's sprite.
+     *
+     * @return The current frame count.
+     */
+    unsigned int getSpriteFrameCount() const;
+
     /**
      * Gets the node that this emitter is attached to.
      *
@@ -606,6 +669,27 @@ public:
      */
     void setOrbit(bool orbitPosition, bool orbitVelocity, bool orbitAcceleration);
 
+    /**
+     * Whether new particle positions are rotated by the node's rotation matrix.
+     *
+     * @return True if orbiting positions, false otherwise.
+     */
+    bool getOrbitPosition() const;
+
+    /**
+     * Whether new particle velocities are rotated by the node's rotation matrix.
+     *
+     * @return True if orbiting velocities, false otherwise.
+     */
+    bool getOrbitVelocity() const;
+
+    /**
+     * Whether new particle accelerations are rotated by the node's rotation matrix.
+     *
+     * @return True if orbiting accelerations, false otherwise.
+     */
+    bool getOrbitAcceleration() const;
+
     /**
      * Updates the particles currently being emitted.
      *
@@ -624,22 +708,48 @@ public:
     static TextureBlending getTextureBlendingFromString(const char* src);
 
     /**
-     * Sets a TextureBlending enum from a corresponding string.
+     * Sets the texture blend mode for this particle emitter.
+     *
+     * @param blending The new blend mode.
      */
     void setTextureBlending(TextureBlending blending);
 
+    /**
+     * Gets the current texture blend mode for this particle emitter.
+     *
+     * @return The current blend mode.
+     */
+    TextureBlending getTextureBlending() const;
+
+    /**
+     * Clones the particle emitter and returns a new emitter.
+     * 
+     * @return The new cloned particle emitter.
+     */
+    ParticleEmitter* clone();
+
 private:
 
     /**
      * Constructor.
      */
-    ParticleEmitter(SpriteBatch* batch, unsigned int particlesCount);
+    ParticleEmitter(unsigned int particlesCount);
 
     /**
      * Destructor.
      */
     ~ParticleEmitter();
 
+    /**
+     * Creates an uninitialized ParticleEmitter.
+     *
+     * @param texture the texture to use.
+     * @param textureBlending The type of texture blending to be used for the particles emitted.
+     * @param particleCountMax The maximum number of particles that can be alive at one time in this ParticleEmitter's system.
+     * @script{create}
+     */
+    static ParticleEmitter* create(Texture* texture, TextureBlending textureBlending,  unsigned int particleCountMax);
+
     /**
      * Hidden copy assignment operator.
      */
@@ -690,7 +800,6 @@ private:
         float _size;
         unsigned int _frame;
         float _timeOnCurrentFrame;
-        bool _visible;
     };
 
     unsigned int _particleCountMax;
@@ -741,7 +850,8 @@ private:
     bool _orbitVelocity;
     bool _orbitAcceleration;
     float _timePerEmission;
-    double _timeRunning;
+    float _emitTime;
+    double _lastUpdated;
 };
 
 }