InterpolationAffector         A particle affector to vary size, rotation and velocity of particles

Intro

This article describes a custom particle affector which can be used to affect the size, rotation and velocity of particles in a particle system. The affector can be assigned to a particle system via a particle script, see the manual section 3.3 Particle Scripts for more details about that.

I found that the standard particle affectors don't quite give enough control over particles, they generally only allow you to modify particle properties in a linear fashion. This is why I have written the Interpolation Affector. It works similarly to the ColourInterpolator Affector but it is used to control size, rotation and velocity rather than colour. There are also a few differences in the way the Affector parameters are entered. Here is an example showing the use of the script:

Script

affector Interpolation
 {
     relative_velocity true
     values   0 1   0.5 2   1 0
 }

Using this will cause the velocity of each particle to double at half their life-time then go to zero at the end of their life. The values flag is a series of pairs of values where the first number represents the time and the second represents the relative velocity in this case (since the relative_velocity is set to true). The above line values 0 1 0.5 2 1 0 can be read as:

Time Value
0 1
0.5 2
1 0
The Interpolation affector can also be used to set the spin velocity and relative size by setting the spin_velocity and/or relative_size flags to true:
affector Interpolation
 {
     spin_velocity true
     values   0 10   1 -10
 }
 affector Interpolation
 {
     relative_size true
     values   0 0.1   0.2 1   1 1
 }

Note that several instances of the affector can be used in the same particle system to control size, spin and velocity independently. If on the other hand, you wish to change size, spin and or velocity with the same values then it is more efficient to use one instance of the affector with the relevant flags set. For example:

affector Interpolation
 {
     relative_velocity true
     relative_size true
     values   0 0.1   0.2 1   1 1
 }

This will vary velocity and size at the same time.

Limitations

Relative Velocity

If relative velocity is set to zero at some point during the particle's lifetime it is no longer possible to increase velocity again. The reason for this is that the particle stores speed and direction of a particle together in the form of a single vector. When the speed goes to 0, the velocity vector becomes zero and hence the direction the particle was travelling in is also lost. If you really want your particle to stop moving and start again then set the velocity to something very small so that the direction of the particle is kept. Something like 0.001 ought to do it, don't make it to low or numericals error may cause the velocity to be in-accurate (speed and direction) once it speeds up again.

Particle Script

The values parameter can be quite cryptic, this is due to a limitation in the way the particle script works. An affector gets input from a dictionary of parameters. The number of affector parameters must be specified at compile time. This leaves two options, one is to hard code a limited number of interpolation values (this is what is done with the ColourInterpolator). The second option is to provide all interpolation values in one long string, this is what has been done here. The advantage with this method is that you are not limited a hard coded number of interpolation values at the cost of a rather messy input string.

Manually created particles

If a particle is created manually, the affector doesn't get a chance to initialise itself. This can cause problems when relative velocity is used and the relative velocity at time = 0 is not 1. For example if you set the initial relative velocity to 2. Then the affector scales the velocity by 2 in the _initParticle function. But since this function is never called with manually created particles this will not happen. The knock on effect is that the particle will have half the velocity you expected for the rest of its lifetime. This problem only occurs with velocity however.

Applications

Personally I have used the affector to create explosion effects where the velocity of a particle is initally very large and then reduces to zero after a short period. The size of particle also starts out at 0 then grows to the desired size over a short period of time. Using this affector in conjunction with the standard particle affects you should be able to create particle effects similar to those created by Particle Illusion. I have found that Particle Illusion is a good tool for rapidly creating and viewing particle effects. I would have liked to be able to import effects directly from Particle Illusion, however they are stored as binary files. And I have found no option to export effects from it.

Code

Here is the code, place the files alongside the rest of your source code and compile it into the project. In order for the affector to be recognised by Ogre and used in particle scripts you will need to register it with ParticleSystemManager. First an instance of InterpolationAffectorFactory must be created then call the function ParticleSystemManager::addAffectorFactory() with a pointer to the instance created. Make sure the instance remains valid until Ogre cleans itself up or your program will likely crash. Here is an example of how to register the class:

InterpolationAffectorFactory* InterpFactory = new InterpolationAffectorFactory();
 ParticleSystemManager::getSingleton().addAffectorFactory(InterpFactory);

Insert this code to free memory after Ogre has cleaned up:

delete InterpFactory;

InterpolationAffectorFactory.h

#ifndef __InterpolationAffectorFactory_h__
 #define __InterpolationAffectorFactory_h__
 
 #include "OgreParticleFXPrerequisites.h"
 #include "OgreParticleAffectorFactory.h"
 #include "InterpolationAffector.h"
 
 using namespace Ogre;
 
 /** Factory class for InterpolationAffector. */
 class InterpolationAffectorFactory  : public ParticleAffectorFactory
 {
     /** See ParticleAffectorFactory */
     String getName() const { return "Interpolation"; }
 
     /** See ParticleAffectorFactory */
     ParticleAffector* createAffector(ParticleSystem* psys)
     {
         ParticleAffector* p = new InterpolationAffector(psys);
         mAffectors.push_back(p);
         return p;
     }
 };
 
 #endif

InterpolationAffector.h

#ifndef __InterpolationAffector_h__
 #define __InterpolationAffector_h__
 
 #include "OgreParticleFXPrerequisites.h"
 #include "OgreParticleAffector.h"
 #include "OgreVector3.h"
 
 using namespace Ogre;
 
 typedef std::vector<std::pair<Real, Real> > RealPairVector;
 
 class InterpolationAffector : public ParticleAffector
 {
 public:
     class CmdRelativeVelocity : public ParamCommand
     {
     public:
         String doGet(const void* target) const;
         void doSet(void* target, const String& val);
     };
 
     class CmdSpinVelocity : public ParamCommand
     {
     public:
         String doGet(const void* target) const;
         void doSet(void* target, const String& val);
     };
 
     class CmdRelativeSize : public ParamCommand
     {
     public:
         String doGet(const void* target) const;
         void doSet(void* target, const String& val);
     };
 
     class CmdInterpValues : public ParamCommand
     {
     public:
         String doGet(const void* target) const;
         void doSet(void* target, const String& val);
     };
 
     /// Default constructor
     InterpolationAffector(ParticleSystem* psys);
 
     /** See ParticleAffector. */
     void _affectParticles(ParticleSystem* pSystem, Real timeElapsed);
     /** See ParticleAffector. */
     void _initParticle(Particle*  pParticle);
 
 
     void setRelativeVelocity(bool relativeVelocity);
     void setSpinVelocity(bool spinVelocity);
     void setRelativeSize(bool relativeSize);
     void setInterpValues(RealPairVector &interpValues);
 
     bool getRelativeVelocity(void) const;
     bool getSpinVelocity(void) const;
     bool getRelativeSize(void) const;
     const RealPairVector &getInterpValues() const;
 
 
     /// Command objects
     static CmdRelativeVelocity msRelativeVelocityCmd;
     static CmdSpinVelocity msSpinVelocityCmd;
     static CmdRelativeSize msRelativeSizeCmd;
     static CmdInterpValues msInterpValuesCmd;
 
 protected:
     Real getValue(Real time);
 
     RealPairVector mInterpValues;
     bool mRelativeVelocity;
     bool mSpinVelocity;
     bool mRelativeSize;
 };
 
 #endif

InterpolationAffector.cpp

#include "InterpolationAffector.h"
 #include "OgreParticleSystem.h"
 #include "OgreParticle.h"
 #include "OgreStringConverter.h"
 
 using namespace Ogre; 
 
 // Instantiate statics
 InterpolationAffector::CmdRelativeVelocity InterpolationAffector::msRelativeVelocityCmd;
 InterpolationAffector::CmdSpinVelocity InterpolationAffector::msSpinVelocityCmd;
 InterpolationAffector::CmdRelativeSize InterpolationAffector::msRelativeSizeCmd;
 InterpolationAffector::CmdInterpValues InterpolationAffector::msInterpValuesCmd;
 
 //-----------------------------------------------------------------------
 InterpolationAffector::InterpolationAffector(ParticleSystem* psys)
     : ParticleAffector(psys)
 {
     mType = "Interpolation";
 
     // defaults
     mRelativeVelocity = false;
     mSpinVelocity = false;
     mRelativeSize = false;
 
     // Set up parameters
     if (createParamDictionary("Interpolation"))
     {
         addBaseParameters();
         // Add extra paramaters
         ParamDictionary* dict = getParamDictionary();
         dict->addParameter(ParameterDef("relative_velocity",
             "Detemines whether the velocity of the particles is scaled.",
             PT_BOOL), &msRelativeVelocityCmd);
         dict->addParameter(ParameterDef("spin_velocity",
             "Detemines whether the spin velocity of the particles is scaled.",
             PT_BOOL), &msSpinVelocityCmd);
         dict->addParameter(ParameterDef("relative_size",
             "Detemines whether the size of the particles is scaled.",
             PT_BOOL), &msRelativeSizeCmd);
         dict->addParameter(ParameterDef("values",
             "Values to interpolate between.",
             PT_REAL), &msInterpValuesCmd);
     }
 }
 //-----------------------------------------------------------------------
 void InterpolationAffector::_initParticle(Particle* pParticle)
 {
     if (mSpinVelocity)
         pParticle->rotationSpeed = getValue(0);
     if (mRelativeVelocity)
         pParticle->direction *= getValue(0);
 }
 //-----------------------------------------------------------------------
 void InterpolationAffector::_affectParticles(ParticleSystem* pSystem, Real  timeElapsed)
 {
     ParticleIterator pi = pSystem->_getIterator();
     Particle *p;
     Real prevLifeFrac, lifeFrac;
     Real prevValue, value;
     Real scale;
     Real Width = pSystem->getDefaultWidth(), Height = pSystem->getDefaultHeight();
 
     while (!pi.end())
     {
         p = pi.getNext();
         lifeFrac = 1-(p->timeToLive / p->totalTimeToLive);
         value = getValue(lifeFrac);
         if (mSpinVelocity)
         {
             p->rotationSpeed = Degree(value);
             p->setRotation(p->rotation+p->rotationSpeed*timeElapsed);
         }
         if (mRelativeSize)
         {
             p->setDimensions(Width*value, Height*value);
         }
 
         if (mRelativeVelocity)
         {
             prevLifeFrac = 1-((p->timeToLive+timeElapsed) / p->totalTimeToLive);
             prevValue = getValue(prevLifeFrac);
             // if prevValue is zero then this particle cannot be affected (division by 0)
             if (prevValue != 0)
             {
                 scale = value/prevValue;
                 p->direction *= scale;
             }
         }
     }
 }
 //-----------------------------------------------------------------------
 Real InterpolationAffector::getValue(Real time)
 {
     if (mInterpValues.empty())
         return 1;
     if (time <= mInterpValues.front().first)
         return mInterpValues.front().second;
     if (time >= mInterpValues.back().first)
         return mInterpValues.back().second;
     RealPairVector::iterator itCurrentValue;
     RealPairVector::iterator itPrevValue = mInterpValues.end();
     for (itCurrentValue = mInterpValues.begin(); itCurrentValue != mInterpValues.end(); ++itCurrentValue)
     {
         if (itPrevValue != mInterpValues.end())
         {
             if (time <= itCurrentValue->first)
             {
                 Real u = (time-itPrevValue->first)/(itCurrentValue->first-itPrevValue->first);
                 Real value = itPrevValue->second + u*(itCurrentValue->second-itPrevValue->second);
                 return value;
             }
         }
         itPrevValue = itCurrentValue;
     }
     return 1;   // Should never get here
 }
 //-----------------------------------------------------------------------
 void InterpolationAffector::setRelativeVelocity(bool relativeVelocity)
 {
     mRelativeVelocity = relativeVelocity;
 }
 //-----------------------------------------------------------------------
 void InterpolationAffector::setSpinVelocity(bool spinVelocity)
 {
     mSpinVelocity = spinVelocity;
 }
 //-----------------------------------------------------------------------
 void InterpolationAffector::setRelativeSize(bool relativeSize)
 {
     mRelativeSize = relativeSize;
 }
 //-----------------------------------------------------------------------
 void InterpolationAffector::setInterpValues(RealPairVector &interpValues)
 {
     mInterpValues = interpValues;
 }
 //-----------------------------------------------------------------------
 bool InterpolationAffector::getRelativeVelocity(void) const
 {
     return mRelativeVelocity;
 }
 //-----------------------------------------------------------------------
 bool InterpolationAffector::getSpinVelocity(void) const
 {
     return mSpinVelocity;
 }
 //-----------------------------------------------------------------------
 bool InterpolationAffector::getRelativeSize(void) const
 {
     return mRelativeSize;
 }
 //-----------------------------------------------------------------------
 const RealPairVector &InterpolationAffector::getInterpValues() const
 {
     return mInterpValues;
 }
 
 
 //-----------------------------------------------------------------------
 //-----------------------------------------------------------------------
 // Command objects
 //-----------------------------------------------------------------------
 //-----------------------------------------------------------------------
 String InterpolationAffector::CmdRelativeVelocity::doGet(const void* target) const
 {
     return StringConverter::toString(
         static_cast<const InterpolationAffector*>(target)->getRelativeVelocity());
 }
 void InterpolationAffector::CmdRelativeVelocity::doSet(void* target, const String& val)
 {
     static_cast<InterpolationAffector*>(target)->setRelativeVelocity(
         StringConverter::parseBool(val));
 }
 String InterpolationAffector::CmdSpinVelocity::doGet(const void* target) const
 {
     return StringConverter::toString(
         static_cast<const InterpolationAffector*>(target)->getSpinVelocity());
 }
 void InterpolationAffector::CmdSpinVelocity::doSet(void* target, const String& val)
 {
     static_cast<InterpolationAffector*>(target)->setSpinVelocity(
         StringConverter::parseBool(val));
 }
 String InterpolationAffector::CmdRelativeSize::doGet(const void* target) const
 {
     return StringConverter::toString(
         static_cast<const InterpolationAffector*>(target)->getRelativeSize());
 }
 void InterpolationAffector::CmdRelativeSize::doSet(void* target, const String& val)
 {
     static_cast<InterpolationAffector*>(target)->setRelativeSize(
         StringConverter::parseBool(val));
 }
 String InterpolationAffector::CmdInterpValues::doGet(const  void* target) const
 {
     const RealPairVector &InterpValues =
         static_cast<const InterpolationAffector*>(target)->getInterpValues();
     String params;
     size_t numParams = InterpValues.size();
     unsigned int i;
     for (i=0; i<numParams; ++i)
     {
         params += StringConverter::toString(InterpValues[i].first);
         params += " ";
         params += StringConverter::toString(InterpValues[i].second);
         params += " ";
     }
     return params;
 }
 void InterpolationAffector::CmdInterpValues::doSet(void* target, const  String& val)
 {
     StringVector vecParams = StringUtil::split(val, " \t");
     size_t numParams = vecParams.size();
     unsigned int i;
     Real time;
     Real value;
     RealPairVector InterpValues;
     for (i=0; i<numParams-1; )
     {
         time = StringConverter::parseReal(vecParams[i++]);
         value = StringConverter::parseReal(vecParams[i++]);
         InterpValues.push_back(std::make_pair(time, value));
     }
     static_cast<InterpolationAffector*>(target)->setInterpValues(InterpValues);
 }