/* ----------------------------------------------------------------------------- This source file is part of OGRE (Object-oriented Graphics Rendering Engine) For the latest info, see http://www.ogre3d.org/ Copyright (c) 2000-2006 Torus Knot Software Ltd Also see acknowledgements in Readme.html This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA, or go to http://www.gnu.org/copyleft/lesser.txt. You may alternatively use this source under the terms of a specific version of the OGRE Unrestricted License provided you have obtained such a license from Torus Knot Software Ltd. ----------------------------------------------------------------------------- */ #include "OgreStableHeaders.h" #include "OgreParticleSystem.h" #include "OgreParticleSystemManager.h" #include "OgreRenderQueue.h" #include "OgreBillboardSet.h" #include "OgreParticleEmitter.h" #include "OgreParticleAffector.h" #include "OgreParticle.h" #include "OgreSceneNode.h" #include "OgreCamera.h" #include "OgreStringConverter.h" #include "OgreLogManager.h" #include "OgreException.h" #include "OgreParticleAffectorFactory.h" #include "OgreParticleSystemRenderer.h" #include "OgreMaterialManager.h" #include "OgreSceneManager.h" #include "OgreControllerManager.h" #include "OgreRoot.h" namespace Ogre { // Init statics ParticleSystem::CmdCull ParticleSystem::msCullCmd; ParticleSystem::CmdHeight ParticleSystem::msHeightCmd; ParticleSystem::CmdMaterial ParticleSystem::msMaterialCmd; ParticleSystem::CmdQuota ParticleSystem::msQuotaCmd; ParticleSystem::CmdEmittedEmitterQuota ParticleSystem::msEmittedEmitterQuotaCmd; ParticleSystem::CmdWidth ParticleSystem::msWidthCmd; ParticleSystem::CmdRenderer ParticleSystem::msRendererCmd; ParticleSystem::CmdSorted ParticleSystem::msSortedCmd; ParticleSystem::CmdLocalSpace ParticleSystem::msLocalSpaceCmd; ParticleSystem::CmdIterationInterval ParticleSystem::msIterationIntervalCmd; ParticleSystem::CmdNonvisibleTimeout ParticleSystem::msNonvisibleTimeoutCmd; RadixSort ParticleSystem::mRadixSorter; Real ParticleSystem::msDefaultIterationInterval = 0; Real ParticleSystem::msDefaultNonvisibleTimeout = 0; //----------------------------------------------------------------------- // Local class for updating based on time class ParticleSystemUpdateValue : public ControllerValue { protected: ParticleSystem* mTarget; public: ParticleSystemUpdateValue(ParticleSystem* target) : mTarget(target) {} Real getValue(void) const { return 0; } // N/A void setValue(Real value) { mTarget->_update(value); } }; //----------------------------------------------------------------------- ParticleSystem::ParticleSystem() : mAABB(), mBoundingRadius(1.0f), mBoundsAutoUpdate(true), mBoundsUpdateTime(10.0f), mUpdateRemainTime(0), mWorldAABB(), mResourceGroupName(ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME), mIsRendererConfigured(false), mSpeedFactor(1.0f), mIterationInterval(0), mIterationIntervalSet(false), mSorted(false), mLocalSpace(false), mNonvisibleTimeout(0), mNonvisibleTimeoutSet(false), mTimeSinceLastVisible(0), mLastVisibleFrame(0), mTimeController(0), mEmittedEmitterPoolInitialised(false), mRenderer(0), mCullIndividual(false), mPoolSize(0), mEmittedEmitterPoolSize(0) { initParameters(); // Default to billboard renderer setRenderer("billboard"); } //----------------------------------------------------------------------- ParticleSystem::ParticleSystem(const String& name, const String& resourceGroup) : MovableObject(name), mAABB(), mBoundingRadius(1.0f), mBoundsAutoUpdate(true), mBoundsUpdateTime(10.0f), mUpdateRemainTime(0), mWorldAABB(), mResourceGroupName(resourceGroup), mIsRendererConfigured(false), mSpeedFactor(1.0f), mIterationInterval(0), mIterationIntervalSet(false), mSorted(false), mLocalSpace(false), mNonvisibleTimeout(0), mNonvisibleTimeoutSet(false), mTimeSinceLastVisible(0), mLastVisibleFrame(Root::getSingleton().getCurrentFrameNumber()), mTimeController(0), mEmittedEmitterPoolInitialised(false), mRenderer(0), mCullIndividual(false), mPoolSize(0), mEmittedEmitterPoolSize(0) { setDefaultDimensions( 100, 100 ); setMaterialName( "BaseWhite" ); // Default to 10 particles, expect app to specify (will only be increased, not decreased) setParticleQuota( 10 ); setEmittedEmitterQuota( 3 ); initParameters(); // Default to billboard renderer setRenderer("billboard"); } //----------------------------------------------------------------------- ParticleSystem::~ParticleSystem() { if (mTimeController) { // Destroy controller ControllerManager::getSingleton().destroyController(mTimeController); mTimeController = 0; } // Arrange for the deletion of emitters & affectors removeAllEmitters(); removeAllEmittedEmitters(); removeAllAffectors(); // Deallocate all particles destroyVisualParticles(0, mParticlePool.size()); // Free pool items ParticlePool::iterator i; for (i = mParticlePool.begin(); i != mParticlePool.end(); ++i) { delete *i; } if (mRenderer) { ParticleSystemManager::getSingleton()._destroyRenderer(mRenderer); mRenderer = 0; } } //----------------------------------------------------------------------- ParticleEmitter* ParticleSystem::addEmitter(const String& emitterType) { ParticleEmitter* em = ParticleSystemManager::getSingleton()._createEmitter(emitterType, this); mEmitters.push_back(em); return em; } //----------------------------------------------------------------------- ParticleEmitter* ParticleSystem::getEmitter(unsigned short index) const { assert(index < mEmitters.size() && "Emitter index out of bounds!"); return mEmitters[index]; } //----------------------------------------------------------------------- unsigned short ParticleSystem::getNumEmitters(void) const { return static_cast< unsigned short >( mEmitters.size() ); } //----------------------------------------------------------------------- void ParticleSystem::removeEmitter(unsigned short index) { assert(index < mEmitters.size() && "Emitter index out of bounds!"); ParticleEmitterList::iterator ei = mEmitters.begin() + index; ParticleSystemManager::getSingleton()._destroyEmitter(*ei); mEmitters.erase(ei); } //----------------------------------------------------------------------- void ParticleSystem::removeAllEmitters(void) { // DON'T delete directly, we don't know what heap these have been created on ParticleEmitterList::iterator ei; for (ei = mEmitters.begin(); ei != mEmitters.end(); ++ei) { ParticleSystemManager::getSingleton()._destroyEmitter(*ei); } mEmitters.clear(); } //----------------------------------------------------------------------- ParticleAffector* ParticleSystem::addAffector(const String& affectorType) { ParticleAffector* af = ParticleSystemManager::getSingleton()._createAffector(affectorType, this); mAffectors.push_back(af); return af; } //----------------------------------------------------------------------- ParticleAffector* ParticleSystem::getAffector(unsigned short index) const { assert(index < mAffectors.size() && "Affector index out of bounds!"); return mAffectors[index]; } //----------------------------------------------------------------------- unsigned short ParticleSystem::getNumAffectors(void) const { return static_cast< unsigned short >( mAffectors.size() ); } //----------------------------------------------------------------------- void ParticleSystem::removeAffector(unsigned short index) { assert(index < mAffectors.size() && "Affector index out of bounds!"); ParticleAffectorList::iterator ai = mAffectors.begin() + index; ParticleSystemManager::getSingleton()._destroyAffector(*ai); mAffectors.erase(ai); } //----------------------------------------------------------------------- void ParticleSystem::removeAllAffectors(void) { // DON'T delete directly, we don't know what heap these have been created on ParticleAffectorList::iterator ai; for (ai = mAffectors.begin(); ai != mAffectors.end(); ++ai) { ParticleSystemManager::getSingleton()._destroyAffector(*ai); } mAffectors.clear(); } //----------------------------------------------------------------------- ParticleSystem& ParticleSystem::operator=(const ParticleSystem& rhs) { // Blank this system's emitters & affectors removeAllEmitters(); removeAllEmittedEmitters(); removeAllAffectors(); // Copy emitters unsigned int i; for(i = 0; i < rhs.getNumEmitters(); ++i) { ParticleEmitter* rhsEm = rhs.getEmitter(i); ParticleEmitter* newEm = addEmitter(rhsEm->getType()); rhsEm->copyParametersTo(newEm); } // Copy affectors for(i = 0; i < rhs.getNumAffectors(); ++i) { ParticleAffector* rhsAf = rhs.getAffector(i); ParticleAffector* newAf = addAffector(rhsAf->getType()); rhsAf->copyParametersTo(newAf); } setParticleQuota(rhs.getParticleQuota()); setEmittedEmitterQuota(rhs.getEmittedEmitterQuota()); setMaterialName(rhs.mMaterialName); setDefaultDimensions(rhs.mDefaultWidth, rhs.mDefaultHeight); mCullIndividual = rhs.mCullIndividual; mSorted = rhs.mSorted; mLocalSpace = rhs.mLocalSpace; mIterationInterval = rhs.mIterationInterval; mIterationIntervalSet = rhs.mIterationIntervalSet; mNonvisibleTimeout = rhs.mNonvisibleTimeout; mNonvisibleTimeoutSet = rhs.mNonvisibleTimeoutSet; // last frame visible and time since last visible should be left default setRenderer(rhs.getRendererName()); // Copy settings if (mRenderer && rhs.getRenderer()) { rhs.getRenderer()->copyParametersTo(mRenderer); } return *this; } //----------------------------------------------------------------------- size_t ParticleSystem::getNumParticles(void) const { return mActiveParticles.size(); } //----------------------------------------------------------------------- size_t ParticleSystem::getParticleQuota(void) const { return mPoolSize; } //----------------------------------------------------------------------- void ParticleSystem::setParticleQuota(size_t size) { // Never shrink below size() size_t currSize = mParticlePool.size(); if( currSize < size ) { // Will allocate particles on demand mPoolSize = size; } } //----------------------------------------------------------------------- size_t ParticleSystem::getEmittedEmitterQuota(void) const { return mEmittedEmitterPoolSize; } //----------------------------------------------------------------------- void ParticleSystem::setEmittedEmitterQuota(size_t size) { // Never shrink below size() EmittedEmitterPool::iterator i; size_t currSize = 0; for (i = mEmittedEmitterPool.begin(); i != mEmittedEmitterPool.end(); ++i) { currSize += i->second.size(); } if( currSize < size ) { // Will allocate emitted emitters on demand mEmittedEmitterPoolSize = size; } } //----------------------------------------------------------------------- void ParticleSystem::setNonVisibleUpdateTimeout(Real timeout) { mNonvisibleTimeout = timeout; mNonvisibleTimeoutSet = true; } //----------------------------------------------------------------------- void ParticleSystem::setIterationInterval(Real interval) { mIterationInterval = interval; mIterationIntervalSet = true; } //----------------------------------------------------------------------- void ParticleSystem::_update(Real timeElapsed) { // Only update if attached to a node if (!mParentNode) return; Real nonvisibleTimeout = mNonvisibleTimeoutSet ? mNonvisibleTimeout : msDefaultNonvisibleTimeout; if (nonvisibleTimeout > 0) { // Check whether it's been more than one frame (update is ahead of // camera notification by one frame because of the ordering) long frameDiff = Root::getSingleton().getCurrentFrameNumber() - mLastVisibleFrame; if (frameDiff > 1 || frameDiff < 0) // < 0 if wrap only { mTimeSinceLastVisible += timeElapsed; if (mTimeSinceLastVisible >= nonvisibleTimeout) { // No update return; } } } // Scale incoming speed for the rest of the calculation timeElapsed *= mSpeedFactor; // Init renderer if not done already configureRenderer(); // Initialise emitted emitters list if not done already initialiseEmittedEmitters(); Real iterationInterval = mIterationIntervalSet ? mIterationInterval : msDefaultIterationInterval; if (iterationInterval > 0) { mUpdateRemainTime += timeElapsed; while (mUpdateRemainTime >= iterationInterval) { // Update existing particles _expire(iterationInterval); _triggerAffectors(iterationInterval); _applyMotion(iterationInterval); // Emit new particles _triggerEmitters(iterationInterval); mUpdateRemainTime -= iterationInterval; } } else { // Update existing particles _expire(timeElapsed); _triggerAffectors(timeElapsed); _applyMotion(timeElapsed); // Emit new particles _triggerEmitters(timeElapsed); } if (!mBoundsAutoUpdate && mBoundsUpdateTime > 0.0f) mBoundsUpdateTime -= timeElapsed; // count down _updateBounds(); } //----------------------------------------------------------------------- void ParticleSystem::_expire(Real timeElapsed) { ActiveParticleList::iterator i, itEnd; Particle* pParticle; ParticleEmitter* pParticleEmitter; itEnd = mActiveParticles.end(); for (i = mActiveParticles.begin(); i != itEnd; ) { pParticle = static_cast(*i); if (pParticle->timeToLive < timeElapsed) { // Notify renderer mRenderer->_notifyParticleExpired(pParticle); // Identify the particle type if (pParticle->particleType == Particle::Visual) { // Destroy this one mFreeParticles.splice(mFreeParticles.end(), mActiveParticles, i++); } else { // For now, it can only be an emitted emitter pParticleEmitter = static_cast(*i); std::list* fee = findFreeEmittedEmitter(pParticleEmitter->getName()); fee->push_back(pParticleEmitter); // Also erase from mActiveEmittedEmitters removeFromActiveEmittedEmitters (pParticleEmitter); // And erase from mActiveParticles i = mActiveParticles.erase( i ); } } else { // Decrement TTL pParticle->timeToLive -= timeElapsed; ++i; } } } //----------------------------------------------------------------------- void ParticleSystem::_triggerEmitters(Real timeElapsed) { // Add up requests for emission static std::vector requested; if( requested.size() != mEmitters.size() ) requested.resize( mEmitters.size() ); size_t totalRequested, emitterCount, i, emissionAllowed; ParticleEmitterList::iterator itEmit, iEmitEnd; ActiveEmittedEmitterList::iterator itActiveEmit; iEmitEnd = mEmitters.end(); emitterCount = mEmitters.size(); emissionAllowed = mFreeParticles.size(); totalRequested = 0; // Count up total requested emissions for regular emitters (and exclude the ones that are used as // a template for emitted emitters) for (itEmit = mEmitters.begin(), i = 0; itEmit != iEmitEnd; ++itEmit, ++i) { if (!(*itEmit)->isEmitted()) { requested[i] = (*itEmit)->_getEmissionCount(timeElapsed); totalRequested += requested[i]; } } // Add up total requested emissions for (active) emitted emitters for (itActiveEmit = mActiveEmittedEmitters.begin(); itActiveEmit != mActiveEmittedEmitters.end(); ++itActiveEmit) { totalRequested += (*itActiveEmit)->_getEmissionCount(timeElapsed); } // Check if the quota will be exceeded, if so reduce demand Real ratio = 1.0f; if (totalRequested > emissionAllowed) { // Apportion down requested values to allotted values ratio = (Real)emissionAllowed / (Real)totalRequested; for (i = 0; i < emitterCount; ++i) { requested[i] = static_cast(requested[i] * ratio); } } // Emit // For each emission, apply a subset of the motion for the frame // this ensures an even distribution of particles when many are // emitted in a single frame for (itEmit = mEmitters.begin(), i = 0; itEmit != iEmitEnd; ++itEmit, ++i) { // Trigger the emitters, but exclude the emitters that are already in the emitted emitters list; // they are handled in a separate loop if (!(*itEmit)->isEmitted()) _executeTriggerEmitters (*itEmit, static_cast(requested[i]), timeElapsed); } // Do the same with all active emitted emitters for (itActiveEmit = mActiveEmittedEmitters.begin(), i = 0; itActiveEmit != mActiveEmittedEmitters.end(); ++itActiveEmit, ++i) _executeTriggerEmitters (*itActiveEmit, static_cast((*itActiveEmit)->_getEmissionCount(timeElapsed) * ratio), timeElapsed); } //----------------------------------------------------------------------- void ParticleSystem::_executeTriggerEmitters(ParticleEmitter* emitter, unsigned requested, Real timeElapsed) { ParticleAffectorList::iterator itAff, itAffEnd; Real timePoint = 0.0f; Real timeInc = timeElapsed / requested; for (unsigned int j = 0; j < requested; ++j) { // Create a new particle & init using emitter // The particle is a visual particle if the emit_emitter property of the emitter isn't set Particle* p = 0; String emitterName = emitter->getEmittedEmitter(); if (emitterName == StringUtil::BLANK) p = createParticle(); else p = createEmitterParticle(emitterName); // Only continue if the particle was really created (not null) if (!p) return; emitter->_initParticle(p); // Translate position & direction into world space if (!mLocalSpace) { p->position = (mParentNode->_getDerivedOrientation() * (mParentNode->_getDerivedScale() * p->position)) + mParentNode->_getDerivedPosition(); p->direction = (mParentNode->_getDerivedOrientation() * p->direction); } // apply partial frame motion to this particle p->position += (p->direction * timePoint); // apply particle initialization by the affectors itAffEnd = mAffectors.end(); for (itAff = mAffectors.begin(); itAff != itAffEnd; ++itAff) (*itAff)->_initParticle(p); // Increment time fragment timePoint += timeInc; if (p->particleType == Particle::Emitter) { // If the particle is an emitter, the position on the emitter side must also be initialised // Note, that position of the emitter becomes a position in worldspace if mLocalSpace is set // to false (will this become a problem?) ParticleEmitter* pParticleEmitter = static_cast(p); pParticleEmitter->setPosition(p->position); } // Notify renderer mRenderer->_notifyParticleEmitted(p); } } //----------------------------------------------------------------------- void ParticleSystem::_applyMotion(Real timeElapsed) { ActiveParticleList::iterator i, itEnd; Particle* pParticle; ParticleEmitter* pParticleEmitter; itEnd = mActiveParticles.end(); for (i = mActiveParticles.begin(); i != itEnd; ++i) { pParticle = static_cast(*i); pParticle->position += (pParticle->direction * timeElapsed); if (pParticle->particleType == Particle::Emitter) { // If it is an emitter, the emitter position must also be updated // Note, that position of the emitter becomes a position in worldspace if mLocalSpace is set // to false (will this become a problem?) pParticleEmitter = static_cast(*i); pParticleEmitter->setPosition(pParticle->position); } } // Notify renderer mRenderer->_notifyParticleMoved(mActiveParticles); } //----------------------------------------------------------------------- void ParticleSystem::_triggerAffectors(Real timeElapsed) { ParticleAffectorList::iterator i, itEnd; itEnd = mAffectors.end(); for (i = mAffectors.begin(); i != itEnd; ++i) { (*i)->_affectParticles(this, timeElapsed); } } //----------------------------------------------------------------------- void ParticleSystem::increasePool(size_t size) { size_t oldSize = mParticlePool.size(); // Increase size mParticlePool.reserve(size); mParticlePool.resize(size); // Create new particles for( size_t i = oldSize; i < size; i++ ) { mParticlePool[i] = new Particle(); } if (mIsRendererConfigured) { createVisualParticles(oldSize, size); } } //----------------------------------------------------------------------- ParticleIterator ParticleSystem::_getIterator(void) { return ParticleIterator(mActiveParticles.begin(), mActiveParticles.end()); } //----------------------------------------------------------------------- Particle* ParticleSystem::getParticle(size_t index) { assert (index < mActiveParticles.size() && "Index out of bounds!"); ActiveParticleList::iterator i = mActiveParticles.begin(); std::advance(i, index); return *i; } //----------------------------------------------------------------------- Particle* ParticleSystem::createParticle(void) { Particle* p = 0; if (!mFreeParticles.empty()) { // Fast creation (don't use superclass since emitter will init) p = mFreeParticles.front(); mActiveParticles.splice(mActiveParticles.end(), mFreeParticles, mFreeParticles.begin()); p->_notifyOwner(this); } return p; } //----------------------------------------------------------------------- Particle* ParticleSystem::createEmitterParticle(const String& emitterName) { // Get the appropriate list and retrieve an emitter Particle* p = 0; std::list* fee = findFreeEmittedEmitter(emitterName); if (fee && !fee->empty()) { p = fee->front(); p->particleType = Particle::Emitter; fee->pop_front(); mActiveParticles.push_back(p); // Also add to mActiveEmittedEmitters. This is needed to traverse through all active emitters // that are emitted. Don't use mActiveParticles for that (although they are added to // mActiveParticles also), because it would take too long to traverse. mActiveEmittedEmitters.push_back(static_cast(p)); p->_notifyOwner(this); } return p; } //----------------------------------------------------------------------- void ParticleSystem::_updateRenderQueue(RenderQueue* queue) { if (mRenderer) { mRenderer->_updateRenderQueue(queue, mActiveParticles, mCullIndividual); } } void ParticleSystem::initParameters(void) { if (createParamDictionary("ParticleSystem")) { ParamDictionary* dict = getParamDictionary(); dict->addParameter(ParameterDef("quota", "The maximum number of particle allowed at once in this system.", PT_UNSIGNED_INT), &msQuotaCmd); dict->addParameter(ParameterDef("emit_emitter_quota", "The maximum number of emitters to be emitted at once in this system.", PT_UNSIGNED_INT), &msEmittedEmitterQuotaCmd); dict->addParameter(ParameterDef("material", "The name of the material to be used to render all particles in this system.", PT_STRING), &msMaterialCmd); dict->addParameter(ParameterDef("particle_width", "The width of particles in world units.", PT_REAL), &msWidthCmd); dict->addParameter(ParameterDef("particle_height", "The height of particles in world units.", PT_REAL), &msHeightCmd); dict->addParameter(ParameterDef("cull_each", "If true, each particle is culled in it's own right. If false, the entire system is culled as a whole.", PT_BOOL), &msCullCmd); dict->addParameter(ParameterDef("renderer", "Sets the particle system renderer to use (default 'billboard').", PT_STRING), &msRendererCmd); dict->addParameter(ParameterDef("sorted", "Sets whether particles should be sorted relative to the camera. ", PT_BOOL), &msSortedCmd); dict->addParameter(ParameterDef("local_space", "Sets whether particles should be kept in local space rather than " "emitted into world space. ", PT_BOOL), &msLocalSpaceCmd); dict->addParameter(ParameterDef("iteration_interval", "Sets a fixed update interval for the system, or 0 for the frame rate. ", PT_REAL), &msIterationIntervalCmd); dict->addParameter(ParameterDef("nonvisible_update_timeout", "Sets a timeout on updates to the system if the system is not visible " "for the given number of seconds (0 to always update)", PT_REAL), &msNonvisibleTimeoutCmd); } } //----------------------------------------------------------------------- void ParticleSystem::_updateBounds() { if (mParentNode && (mBoundsAutoUpdate || mBoundsUpdateTime > 0.0f)) { if (mActiveParticles.empty()) { // No particles, reset to null if auto update bounds if (mBoundsAutoUpdate) { mWorldAABB.setNull(); } } else { Vector3 min; Vector3 max; if (!mBoundsAutoUpdate && mWorldAABB.isFinite()) { // We're on a limit, grow rather than reset each time // so that we pick up the worst case scenario min = mWorldAABB.getMinimum(); max = mWorldAABB.getMaximum(); } else { min.x = min.y = min.z = Math::POS_INFINITY; max.x = max.y = max.z = Math::NEG_INFINITY; } ActiveParticleList::iterator p; Vector3 halfScale = Vector3::UNIT_SCALE * 0.5; Vector3 defaultPadding = halfScale * std::max(mDefaultHeight, mDefaultWidth); for (p = mActiveParticles.begin(); p != mActiveParticles.end(); ++p) { if ((*p)->mOwnDimensions) { Vector3 padding = halfScale * std::max((*p)->mWidth, (*p)->mHeight); min.makeFloor((*p)->position - padding); max.makeCeil((*p)->position + padding); } else { min.makeFloor((*p)->position - defaultPadding); max.makeCeil((*p)->position + defaultPadding); } } mWorldAABB.setExtents(min, max); } if (mLocalSpace) { // Merge calculated box with current AABB to preserve any user-set AABB mAABB.merge(mWorldAABB); } else { // We've already put particles in world space to decouple them from the // node transform, so reverse transform back since we're expected to // provide a local AABB AxisAlignedBox newAABB(mWorldAABB); newAABB.transformAffine(mParentNode->_getFullTransform().inverseAffine()); // Merge calculated box with current AABB to preserve any user-set AABB mAABB.merge(newAABB); } mParentNode->needUpdate(); } } //----------------------------------------------------------------------- void ParticleSystem::fastForward(Real time, Real interval) { // First make sure all transforms are up to date for (Real ftime = 0; ftime < time; ftime += interval) { _update(interval); } } //----------------------------------------------------------------------- const String& ParticleSystem::getMovableType(void) const { return ParticleSystemFactory::FACTORY_TYPE_NAME; } //----------------------------------------------------------------------- void ParticleSystem::_notifyParticleResized(void) { if (mRenderer) { mRenderer->_notifyParticleResized(); } } //----------------------------------------------------------------------- void ParticleSystem::_notifyParticleRotated(void) { if (mRenderer) { mRenderer->_notifyParticleRotated(); } } //----------------------------------------------------------------------- void ParticleSystem::setDefaultDimensions( Real width, Real height ) { mDefaultWidth = width; mDefaultHeight = height; if (mRenderer) { mRenderer->_notifyDefaultDimensions(width, height); } } //----------------------------------------------------------------------- void ParticleSystem::setDefaultWidth(Real width) { mDefaultWidth = width; if (mRenderer) { mRenderer->_notifyDefaultDimensions(mDefaultWidth, mDefaultHeight); } } //----------------------------------------------------------------------- Real ParticleSystem::getDefaultWidth(void) const { return mDefaultWidth; } //----------------------------------------------------------------------- void ParticleSystem::setDefaultHeight(Real height) { mDefaultHeight = height; if (mRenderer) { mRenderer->_notifyDefaultDimensions(mDefaultWidth, mDefaultHeight); } } //----------------------------------------------------------------------- Real ParticleSystem::getDefaultHeight(void) const { return mDefaultHeight; } //----------------------------------------------------------------------- void ParticleSystem::_notifyCurrentCamera(Camera* cam) { MovableObject::_notifyCurrentCamera(cam); // Record visible mLastVisibleFrame = Root::getSingleton().getCurrentFrameNumber(); mTimeSinceLastVisible = 0.0f; if (mSorted) { _sortParticles(cam); } if (mRenderer) { if (!mIsRendererConfigured) configureRenderer(); mRenderer->_notifyCurrentCamera(cam); } } //----------------------------------------------------------------------- void ParticleSystem::_notifyAttached(Node* parent, bool isTagPoint) { MovableObject::_notifyAttached(parent, isTagPoint); if (mRenderer && mIsRendererConfigured) { mRenderer->_notifyAttached(parent, isTagPoint); } if (parent && !mTimeController) { // Assume visible mTimeSinceLastVisible = 0; mLastVisibleFrame = Root::getSingleton().getCurrentFrameNumber(); // Create time controller when attached ControllerManager& mgr = ControllerManager::getSingleton(); ControllerValueRealPtr updValue(new ParticleSystemUpdateValue(this)); mTimeController = mgr.createFrameTimePassthroughController(updValue); } else if (!parent && mTimeController) { // Destroy controller ControllerManager::getSingleton().destroyController(mTimeController); mTimeController = 0; } } //----------------------------------------------------------------------- void ParticleSystem::setMaterialName(const String& name) { mMaterialName = name; if (mIsRendererConfigured) { MaterialPtr mat = MaterialManager::getSingleton().load( mMaterialName, mResourceGroupName); mRenderer->_setMaterial(mat); } } //----------------------------------------------------------------------- const String& ParticleSystem::getMaterialName(void) const { return mMaterialName; } //----------------------------------------------------------------------- void ParticleSystem::clear() { // Notify renderer if exists if (mRenderer) { mRenderer->_notifyParticleCleared(mActiveParticles); } // Move actives to free list mFreeParticles.splice(mFreeParticles.end(), mActiveParticles); // Add active emitted emitters to free list addActiveEmittedEmittersToFreeList(); // Remove all active emitted emitter instances mActiveEmittedEmitters.clear(); // Reset update remain time mUpdateRemainTime = 0; } //----------------------------------------------------------------------- void ParticleSystem::setRenderer(const String& rendererName) { if (mRenderer) { // Destroy existing destroyVisualParticles(0, mParticlePool.size()); ParticleSystemManager::getSingleton()._destroyRenderer(mRenderer); mRenderer = 0; } if (!rendererName.empty()) { mRenderer = ParticleSystemManager::getSingleton()._createRenderer(rendererName); mIsRendererConfigured = false; } } //----------------------------------------------------------------------- void ParticleSystem::configureRenderer(void) { // Actual allocate particles size_t currSize = mParticlePool.size(); size_t size = mPoolSize; if( currSize < size ) { this->increasePool(size); for( size_t i = currSize; i < size; ++i ) { // Add new items to the queue mFreeParticles.push_back( mParticlePool[i] ); } // Tell the renderer, if already configured if (mRenderer && mIsRendererConfigured) { mRenderer->_notifyParticleQuota(size); } } if (mRenderer && !mIsRendererConfigured) { mRenderer->_notifyParticleQuota(mParticlePool.size()); mRenderer->_notifyAttached(mParentNode, mParentIsTagPoint); mRenderer->_notifyDefaultDimensions(mDefaultWidth, mDefaultHeight); createVisualParticles(0, mParticlePool.size()); MaterialPtr mat = MaterialManager::getSingleton().load( mMaterialName, mResourceGroupName); mRenderer->_setMaterial(mat); if (mRenderQueueIDSet) mRenderer->setRenderQueueGroup(mRenderQueueID); mRenderer->setKeepParticlesInLocalSpace(mLocalSpace); mIsRendererConfigured = true; } } //----------------------------------------------------------------------- ParticleSystemRenderer* ParticleSystem::getRenderer(void) const { return mRenderer; } //----------------------------------------------------------------------- const String& ParticleSystem::getRendererName(void) const { if (mRenderer) { return mRenderer->getType(); } else { return StringUtil::BLANK; } } //----------------------------------------------------------------------- bool ParticleSystem::getCullIndividually(void) const { return mCullIndividual; } //----------------------------------------------------------------------- void ParticleSystem::setCullIndividually(bool cullIndividual) { mCullIndividual = cullIndividual; } //----------------------------------------------------------------------- void ParticleSystem::createVisualParticles(size_t poolstart, size_t poolend) { ParticlePool::iterator i = mParticlePool.begin(); ParticlePool::iterator iend = mParticlePool.begin(); std::advance(i, poolstart); std::advance(iend, poolend); for (; i != iend; ++i) { (*i)->_notifyVisualData( mRenderer->_createVisualData()); } } //----------------------------------------------------------------------- void ParticleSystem::destroyVisualParticles(size_t poolstart, size_t poolend) { ParticlePool::iterator i = mParticlePool.begin(); ParticlePool::iterator iend = mParticlePool.begin(); std::advance(i, poolstart); std::advance(iend, poolend); for (; i != iend; ++i) { mRenderer->_destroyVisualData((*i)->getVisualData()); (*i)->_notifyVisualData(0); } } //----------------------------------------------------------------------- void ParticleSystem::setBounds(const AxisAlignedBox& aabb) { mAABB = aabb; Real sqDist = std::max(mAABB.getMinimum().squaredLength(), mAABB.getMaximum().squaredLength()); mBoundingRadius = Math::Sqrt(sqDist); } //----------------------------------------------------------------------- void ParticleSystem::setBoundsAutoUpdated(bool autoUpdate, Real stopIn) { mBoundsAutoUpdate = autoUpdate; mBoundsUpdateTime = stopIn; } //----------------------------------------------------------------------- void ParticleSystem::setRenderQueueGroup(uint8 queueID) { MovableObject::setRenderQueueGroup(queueID); if (mRenderer) { mRenderer->setRenderQueueGroup(queueID); } } //----------------------------------------------------------------------- void ParticleSystem::setKeepParticlesInLocalSpace(bool keepLocal) { mLocalSpace = keepLocal; if (mRenderer) { mRenderer->setKeepParticlesInLocalSpace(keepLocal); } } //----------------------------------------------------------------------- void ParticleSystem::_sortParticles(Camera* cam) { if (mRenderer) { SortMode sortMode = mRenderer->_getSortMode(); if (sortMode == SM_DIRECTION) { Vector3 camDir = cam->getDerivedDirection(); if (mLocalSpace) { // transform the camera direction into local space camDir = mParentNode->_getDerivedOrientation().UnitInverse() * camDir; } mRadixSorter.sort(mActiveParticles, SortByDirectionFunctor(- camDir)); } else if (sortMode == SM_DISTANCE) { Vector3 camPos = cam->getDerivedPosition(); if (mLocalSpace) { // transform the camera position into local space camPos = mParentNode->_getDerivedOrientation().UnitInverse() * (camPos - mParentNode->_getDerivedPosition()) / mParentNode->_getDerivedScale(); } mRadixSorter.sort(mActiveParticles, SortByDistanceFunctor(camPos)); } } } ParticleSystem::SortByDirectionFunctor::SortByDirectionFunctor(const Vector3& dir) : sortDir(dir) { } float ParticleSystem::SortByDirectionFunctor::operator()(Particle* p) const { return sortDir.dotProduct(p->position); } ParticleSystem::SortByDistanceFunctor::SortByDistanceFunctor(const Vector3& pos) : sortPos(pos) { } float ParticleSystem::SortByDistanceFunctor::operator()(Particle* p) const { // Sort descending by squared distance return - (sortPos - p->position).squaredLength(); } //----------------------------------------------------------------------- uint32 ParticleSystem::getTypeFlags(void) const { return SceneManager::FX_TYPE_MASK; } //----------------------------------------------------------------------- void ParticleSystem::initialiseEmittedEmitters(void) { // Initialise the pool if needed size_t currSize = 0; if (mEmittedEmitterPool.empty()) { if (mEmittedEmitterPoolInitialised) { // It was already initialised, but apparently no emitted emitters were used return; } else { initialiseEmittedEmitterPool(); } } else { EmittedEmitterPool::iterator i; for (i = mEmittedEmitterPool.begin(); i != mEmittedEmitterPool.end(); ++i) { currSize += i->second.size(); } } size_t size = mEmittedEmitterPoolSize; if( currSize < size && !mEmittedEmitterPool.empty()) { // Increase the pool. Equally distribute over all vectors in the map increaseEmittedEmitterPool(size); // Add new items to the free list addFreeEmittedEmitters(); } } //----------------------------------------------------------------------- void ParticleSystem::initialiseEmittedEmitterPool(void) { if (mEmittedEmitterPoolInitialised) return; // Run through mEmitters and add keys to the pool ParticleEmitterList::iterator emitterIterator; ParticleEmitterList::iterator emitterIteratorInner; ParticleEmitter* emitter = 0; ParticleEmitter* emitterInner = 0; for (emitterIterator = mEmitters.begin(); emitterIterator != mEmitters.end(); ++emitterIterator) { // Determine the names of all emitters that are emitted emitter = *emitterIterator ; if (emitter && emitter->getEmittedEmitter() != StringUtil::BLANK) { // This one will be emitted, register its name and leave the vector empty! EmittedEmitterList empty; mEmittedEmitterPool.insert(make_pair(emitter->getEmittedEmitter(), empty)); } // Determine whether the emitter itself will be emitted and set the 'mEmitted' attribute for (emitterIteratorInner = mEmitters.begin(); emitterIteratorInner != mEmitters.end(); ++emitterIteratorInner) { emitterInner = *emitterIteratorInner; if (emitter && emitterInner && emitter->getName() != StringUtil::BLANK && emitter->getName() == emitterInner->getEmittedEmitter()) { emitter->setEmitted(true); break; } else { // Set explicitly to 'false' although the default value is already 'false' emitter->setEmitted(false); } } } mEmittedEmitterPoolInitialised = true; } //----------------------------------------------------------------------- void ParticleSystem::increaseEmittedEmitterPool(size_t size) { // Don't proceed if the pool doesn't contain any keys of emitted emitters if (mEmittedEmitterPool.empty()) return; EmittedEmitterPool::iterator emittedEmitterPoolIterator; ParticleEmitterList::iterator emitterIterator; ParticleEmitter* emitter = 0; ParticleEmitter* clonedEmitter = 0; String name = StringUtil::BLANK; EmittedEmitterList* e = 0; size_t maxNumberOfEmitters = size / mEmittedEmitterPool.size(); // equally distribute the number for each emitted emitter list size_t oldSize = 0; // Run through mEmittedEmitterPool and search for every key (=name) its corresponding emitter in mEmitters for (emittedEmitterPoolIterator = mEmittedEmitterPool.begin(); emittedEmitterPoolIterator != mEmittedEmitterPool.end(); ++emittedEmitterPoolIterator) { name = emittedEmitterPoolIterator->first; e = &emittedEmitterPoolIterator->second; // Search the correct emitter in the mEmitters vector emitter = 0; for (emitterIterator = mEmitters.begin(); emitterIterator != mEmitters.end(); ++emitterIterator) { emitter = *emitterIterator; if (emitter && name != StringUtil::BLANK && name == emitter->getName()) { // Found the right emitter, clone each emitter a number of times oldSize = e->size(); for (size_t t = oldSize; t < maxNumberOfEmitters; ++t) { clonedEmitter = ParticleSystemManager::getSingleton()._createEmitter(emitter->getType(), this); emitter->copyParametersTo(clonedEmitter); clonedEmitter->setEmitted(emitter->isEmitted()); // is always 'true' by the way, but just in case // Initially deactivate the emitted emitter if duration/repeat_delay are set if (clonedEmitter->getDuration() > 0.0f && (clonedEmitter->getRepeatDelay() > 0.0f || clonedEmitter->getMinRepeatDelay() > 0.0f || clonedEmitter->getMinRepeatDelay() > 0.0f)) clonedEmitter->setEnabled(false); // Add cloned emitters to the pool e->push_back(clonedEmitter); } } } } } //----------------------------------------------------------------------- void ParticleSystem::addFreeEmittedEmitters(void) { // Don't proceed if the EmittedEmitterPool is empty if (mEmittedEmitterPool.empty()) return; // Copy all pooled emitters to the free list EmittedEmitterPool::iterator emittedEmitterPoolIterator; EmittedEmitterList::iterator emittedEmitterIterator; EmittedEmitterList* emittedEmitters = 0; std::list* fee = 0; String name = StringUtil::BLANK; // Run through the emittedEmitterPool map for (emittedEmitterPoolIterator = mEmittedEmitterPool.begin(); emittedEmitterPoolIterator != mEmittedEmitterPool.end(); ++emittedEmitterPoolIterator) { name = emittedEmitterPoolIterator->first; emittedEmitters = &emittedEmitterPoolIterator->second; fee = findFreeEmittedEmitter(name); // If itīs not in the map, create an empty one if (!fee) { FreeEmittedEmitterList empty; mFreeEmittedEmitters.insert(make_pair(name, empty)); fee = findFreeEmittedEmitter(name); } // Check anyway if itīs ok now if (!fee) return; // forget it! // Add all emitted emitters from the pool to the free list for(emittedEmitterIterator = emittedEmitters->begin(); emittedEmitterIterator != emittedEmitters->end(); ++emittedEmitterIterator) { fee->push_back(*emittedEmitterIterator); } } } //----------------------------------------------------------------------- void ParticleSystem::removeAllEmittedEmitters(void) { EmittedEmitterPool::iterator emittedEmitterPoolIterator; EmittedEmitterList::iterator emittedEmitterListIterator; EmittedEmitterList* e = 0; for (emittedEmitterPoolIterator = mEmittedEmitterPool.begin(); emittedEmitterPoolIterator != mEmittedEmitterPool.end(); ++emittedEmitterPoolIterator) { e = &emittedEmitterPoolIterator->second; for (emittedEmitterListIterator = e->begin(); emittedEmitterListIterator != e->end(); ++emittedEmitterListIterator) { ParticleSystemManager::getSingleton()._destroyEmitter(*emittedEmitterListIterator); } e->clear(); } // Donīt leave any references behind mEmittedEmitterPool.clear(); mFreeEmittedEmitters.clear(); mActiveEmittedEmitters.clear(); } //----------------------------------------------------------------------- std::list* ParticleSystem::findFreeEmittedEmitter (const String& name) { FreeEmittedEmitterMap::iterator it; it = mFreeEmittedEmitters.find (name); if (it != mFreeEmittedEmitters.end()) { // Found it return &it->second; } return 0; } //----------------------------------------------------------------------- void ParticleSystem::removeFromActiveEmittedEmitters (ParticleEmitter* emitter) { assert(emitter && "Emitter to be removed is 0!"); ActiveEmittedEmitterList::iterator itActiveEmit; for (itActiveEmit = mActiveEmittedEmitters.begin(); itActiveEmit != mActiveEmittedEmitters.end(); ++itActiveEmit) { if (emitter == (*itActiveEmit)) { mActiveEmittedEmitters.erase(itActiveEmit); break; } } } //----------------------------------------------------------------------- void ParticleSystem::addActiveEmittedEmittersToFreeList (void) { ActiveEmittedEmitterList::iterator itActiveEmit; for (itActiveEmit = mActiveEmittedEmitters.begin(); itActiveEmit != mActiveEmittedEmitters.end(); ++itActiveEmit) { std::list* fee = findFreeEmittedEmitter ((*itActiveEmit)->getName()); if (fee) fee->push_back(*itActiveEmit); } } //----------------------------------------------------------------------- void ParticleSystem::_notifyReorganiseEmittedEmitterData (void) { removeAllEmittedEmitters(); mEmittedEmitterPoolInitialised = false; // Donīt rearrange immediately; it will be performed in the regular flow } //----------------------------------------------------------------------- String ParticleSystem::CmdCull::doGet(const void* target) const { return StringConverter::toString( static_cast(target)->getCullIndividually() ); } void ParticleSystem::CmdCull::doSet(void* target, const String& val) { static_cast(target)->setCullIndividually( StringConverter::parseBool(val)); } //----------------------------------------------------------------------- String ParticleSystem::CmdHeight::doGet(const void* target) const { return StringConverter::toString( static_cast(target)->getDefaultHeight() ); } void ParticleSystem::CmdHeight::doSet(void* target, const String& val) { static_cast(target)->setDefaultHeight( StringConverter::parseReal(val)); } //----------------------------------------------------------------------- String ParticleSystem::CmdWidth::doGet(const void* target) const { return StringConverter::toString( static_cast(target)->getDefaultWidth() ); } void ParticleSystem::CmdWidth::doSet(void* target, const String& val) { static_cast(target)->setDefaultWidth( StringConverter::parseReal(val)); } //----------------------------------------------------------------------- String ParticleSystem::CmdMaterial::doGet(const void* target) const { return static_cast(target)->getMaterialName(); } void ParticleSystem::CmdMaterial::doSet(void* target, const String& val) { static_cast(target)->setMaterialName(val); } //----------------------------------------------------------------------- String ParticleSystem::CmdQuota::doGet(const void* target) const { return StringConverter::toString( static_cast(target)->getParticleQuota() ); } void ParticleSystem::CmdQuota::doSet(void* target, const String& val) { static_cast(target)->setParticleQuota( StringConverter::parseUnsignedInt(val)); } //----------------------------------------------------------------------- String ParticleSystem::CmdEmittedEmitterQuota::doGet(const void* target) const { return StringConverter::toString( static_cast(target)->getEmittedEmitterQuota() ); } void ParticleSystem::CmdEmittedEmitterQuota::doSet(void* target, const String& val) { static_cast(target)->setEmittedEmitterQuota( StringConverter::parseUnsignedInt(val)); } //----------------------------------------------------------------------- String ParticleSystem::CmdRenderer::doGet(const void* target) const { return static_cast(target)->getRendererName(); } void ParticleSystem::CmdRenderer::doSet(void* target, const String& val) { static_cast(target)->setRenderer(val); } //----------------------------------------------------------------------- String ParticleSystem::CmdSorted::doGet(const void* target) const { return StringConverter::toString( static_cast(target)->getSortingEnabled()); } void ParticleSystem::CmdSorted::doSet(void* target, const String& val) { static_cast(target)->setSortingEnabled( StringConverter::parseBool(val)); } //----------------------------------------------------------------------- String ParticleSystem::CmdLocalSpace::doGet(const void* target) const { return StringConverter::toString( static_cast(target)->getKeepParticlesInLocalSpace()); } void ParticleSystem::CmdLocalSpace::doSet(void* target, const String& val) { static_cast(target)->setKeepParticlesInLocalSpace( StringConverter::parseBool(val)); } //----------------------------------------------------------------------- String ParticleSystem::CmdIterationInterval::doGet(const void* target) const { return StringConverter::toString( static_cast(target)->getIterationInterval()); } void ParticleSystem::CmdIterationInterval::doSet(void* target, const String& val) { static_cast(target)->setIterationInterval( StringConverter::parseReal(val)); } //----------------------------------------------------------------------- String ParticleSystem::CmdNonvisibleTimeout::doGet(const void* target) const { return StringConverter::toString( static_cast(target)->getNonVisibleUpdateTimeout()); } void ParticleSystem::CmdNonvisibleTimeout::doSet(void* target, const String& val) { static_cast(target)->setNonVisibleUpdateTimeout( StringConverter::parseReal(val)); } //----------------------------------------------------------------------- ParticleAffector::~ParticleAffector() { } //----------------------------------------------------------------------- ParticleAffectorFactory::~ParticleAffectorFactory() { // Destroy all affectors std::vector::iterator i; for (i = mAffectors.begin(); i != mAffectors.end(); ++i) { delete (*i); } mAffectors.clear(); } //----------------------------------------------------------------------- void ParticleAffectorFactory::destroyAffector(ParticleAffector* e) { std::vector::iterator i; for (i = mAffectors.begin(); i != mAffectors.end(); ++i) { if ((*i) == e) { mAffectors.erase(i); delete e; break; } } } }