00001 /* 00002 ----------------------------------------------------------------------------- 00003 This source file is part of OGRE 00004 (Object-oriented Graphics Rendering Engine) 00005 For the latest info, see http://www.ogre3d.org/ 00006 00007 Copyright © 2000-2002 The OGRE Team 00008 Also see acknowledgements in Readme.html 00009 00010 This program is free software; you can redistribute it and/or modify it under 00011 the terms of the GNU Lesser General Public License as published by the Free Software 00012 Foundation; either version 2 of the License, or (at your option) any later 00013 version. 00014 00015 This program is distributed in the hope that it will be useful, but WITHOUT 00016 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 00017 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. 00018 00019 You should have received a copy of the GNU Lesser General Public License along with 00020 this program; if not, write to the Free Software Foundation, Inc., 59 Temple 00021 Place - Suite 330, Boston, MA 02111-1307, USA, or go to 00022 http://www.gnu.org/copyleft/lesser.txt. 00023 ----------------------------------------------------------------------------- 00024 */ 00025 #include "OgreStableHeaders.h" 00026 00027 #include "OgreParticleSystem.h" 00028 #include "OgreParticleSystemManager.h" 00029 #include "OgreRenderQueue.h" 00030 #include "OgreBillboardSet.h" 00031 #include "OgreParticleEmitter.h" 00032 #include "OgreParticleAffector.h" 00033 #include "OgreParticle.h" 00034 #include "OgreSceneNode.h" 00035 #include "OgreCamera.h" 00036 #include "OgreStringConverter.h" 00037 #include "OgreLogManager.h" 00038 #include "OgreException.h" 00039 00040 00041 00042 namespace Ogre { 00043 // Init statics 00044 ParticleSystem::CmdCull ParticleSystem::msCullCmd; 00045 ParticleSystem::CmdHeight ParticleSystem::msHeightCmd; 00046 ParticleSystem::CmdMaterial ParticleSystem::msMaterialCmd; 00047 ParticleSystem::CmdQuota ParticleSystem::msQuotaCmd; 00048 ParticleSystem::CmdWidth ParticleSystem::msWidthCmd; 00049 ParticleSystem::CmdBillboardType ParticleSystem::msBillboardTypeCmd; 00050 ParticleSystem::CmdCommonDirection ParticleSystem::msCommonDirectionCmd; 00051 00052 //----------------------------------------------------------------------- 00053 ParticleSystem::ParticleSystem() 00054 { 00055 initParameters(); 00056 } 00057 //----------------------------------------------------------------------- 00058 ParticleSystem::ParticleSystem(const String& name) 00059 { 00060 // DO NOT use superclass constructor 00061 // This will call setPoolSize in the BillboardSet context and create Billboard objects 00062 // instead of Particle objects 00063 // Unavoidable due to C++ funky virtualisation rules & constructors 00064 //mpPositions = 0; 00065 //mpColours = 0; 00066 //mpIndexes = 0; 00067 mVertexData = 0; 00068 mIndexData = 0; 00069 //mpTexCoords = 0; 00070 mAutoExtendPool = true; 00071 mAllDefaultSize = true; 00072 mOriginType = BBO_CENTER; 00073 mName = name; 00074 mCullIndividual = true; 00075 setDefaultDimensions( 100, 100 ); 00076 setMaterialName( "BaseWhite" ); 00077 // Default to 10 particles, expect app to specify (will only be increased, not decreased) 00078 setPoolSize( 10 ); 00079 00080 initParameters(); 00081 00082 } 00083 //----------------------------------------------------------------------- 00084 ParticleSystem::~ParticleSystem() 00085 { 00086 // Arrange for the deletion of emitters & affectors 00087 removeAllEmitters(); 00088 removeAllAffectors(); 00089 } 00090 //----------------------------------------------------------------------- 00091 ParticleEmitter* ParticleSystem::addEmitter(const String& emitterType) 00092 { 00093 ParticleEmitter* em = ParticleSystemManager::getSingleton()._createEmitter(emitterType); 00094 mEmitters.push_back(em); 00095 return em; 00096 } 00097 //----------------------------------------------------------------------- 00098 ParticleEmitter* ParticleSystem::getEmitter(unsigned short index) const 00099 { 00100 assert(index < mEmitters.size() && "Emitter index out of bounds!"); 00101 return mEmitters[index]; 00102 } 00103 //----------------------------------------------------------------------- 00104 unsigned short ParticleSystem::getNumEmitters(void) const 00105 { 00106 return static_cast< unsigned short >( mEmitters.size() ); 00107 } 00108 //----------------------------------------------------------------------- 00109 void ParticleSystem::removeEmitter(unsigned short index) 00110 { 00111 assert(index < mEmitters.size() && "Emitter index out of bounds!"); 00112 ParticleEmitterList::iterator ei = mEmitters.begin() + index; 00113 ParticleSystemManager::getSingleton()._destroyEmitter(*ei); 00114 mEmitters.erase(ei); 00115 } 00116 //----------------------------------------------------------------------- 00117 void ParticleSystem::removeAllEmitters(void) 00118 { 00119 // DON'T delete directly, we don't know what heap these have been created on 00120 ParticleEmitterList::iterator ei; 00121 for (ei = mEmitters.begin(); ei != mEmitters.end(); ++ei) 00122 { 00123 ParticleSystemManager::getSingleton()._destroyEmitter(*ei); 00124 } 00125 mEmitters.clear(); 00126 } 00127 //----------------------------------------------------------------------- 00128 ParticleAffector* ParticleSystem::addAffector(const String& affectorType) 00129 { 00130 ParticleAffector* af = ParticleSystemManager::getSingleton()._createAffector(affectorType); 00131 mAffectors.push_back(af); 00132 return af; 00133 } 00134 //----------------------------------------------------------------------- 00135 ParticleAffector* ParticleSystem::getAffector(unsigned short index) const 00136 { 00137 assert(index < mAffectors.size() && "Affector index out of bounds!"); 00138 return mAffectors[index]; 00139 } 00140 //----------------------------------------------------------------------- 00141 unsigned short ParticleSystem::getNumAffectors(void) const 00142 { 00143 return static_cast< unsigned short >( mAffectors.size() ); 00144 } 00145 //----------------------------------------------------------------------- 00146 void ParticleSystem::removeAffector(unsigned short index) 00147 { 00148 assert(index < mAffectors.size() && "Affector index out of bounds!"); 00149 ParticleAffectorList::iterator ai = mAffectors.begin() + index; 00150 ParticleSystemManager::getSingleton()._destroyAffector(*ai); 00151 mAffectors.erase(ai); 00152 } 00153 //----------------------------------------------------------------------- 00154 void ParticleSystem::removeAllAffectors(void) 00155 { 00156 // DON'T delete directly, we don't know what heap these have been created on 00157 ParticleAffectorList::iterator ai; 00158 for (ai = mAffectors.begin(); ai != mAffectors.end(); ++ai) 00159 { 00160 ParticleSystemManager::getSingleton()._destroyAffector(*ai); 00161 } 00162 mAffectors.clear(); 00163 } 00164 //----------------------------------------------------------------------- 00165 ParticleSystem& ParticleSystem::operator=(const ParticleSystem& rhs) 00166 { 00167 // Blank this system's emitters & affectors 00168 removeAllEmitters(); 00169 removeAllAffectors(); 00170 00171 // Copy emitters 00172 unsigned int i; 00173 for(i = 0; i < rhs.getNumEmitters(); ++i) 00174 { 00175 ParticleEmitter* rhsEm = rhs.getEmitter(i); 00176 ParticleEmitter* newEm = addEmitter(rhsEm->getType()); 00177 rhsEm->copyParametersTo(newEm); 00178 } 00179 // Copy affectors 00180 for(i = 0; i < rhs.getNumAffectors(); ++i) 00181 { 00182 ParticleAffector* rhsAf = rhs.getAffector(i); 00183 ParticleAffector* newAf = addAffector(rhsAf->getType()); 00184 rhsAf->copyParametersTo(newAf); 00185 } 00186 setPoolSize(rhs.getPoolSize()); 00187 setMaterialName(rhs.mMaterialName); 00188 mOriginType = rhs.mOriginType; 00189 mDefaultHeight = rhs.mDefaultHeight; 00190 mDefaultWidth = rhs.mDefaultWidth; 00191 mCullIndividual = rhs.mCullIndividual; 00192 mBillboardType = rhs.mBillboardType; 00193 mCommonDirection = rhs.mCommonDirection; 00194 00195 00196 return *this; 00197 00198 } 00199 //----------------------------------------------------------------------- 00200 unsigned int ParticleSystem::getNumParticles(void) const 00201 { 00202 return (unsigned int)mActiveBillboards.size(); 00203 } 00204 //----------------------------------------------------------------------- 00205 unsigned int ParticleSystem::getParticleQuota(void) const 00206 { 00207 // This is basically a renamed property 00208 return getPoolSize(); 00209 } 00210 //----------------------------------------------------------------------- 00211 void ParticleSystem::setParticleQuota(unsigned int quota) 00212 { 00213 // This is basically a renamed property 00214 setPoolSize(quota); 00215 } 00216 //----------------------------------------------------------------------- 00217 void ParticleSystem::_update(Real timeElapsed) 00218 { 00219 // Only update if attached to a node 00220 if (mParentNode) 00221 { 00222 // Update existing particles 00223 _expire(timeElapsed); 00224 _triggerAffectors(timeElapsed); 00225 _applyMotion(timeElapsed); 00226 // Emit new particles 00227 _triggerEmitters(timeElapsed); 00228 // Update bounds 00229 _updateBounds(); 00230 } 00231 00232 00233 } 00234 //----------------------------------------------------------------------- 00235 void ParticleSystem::_expire(Real timeElapsed) 00236 { 00237 ActiveBillboardList::iterator i, itEnd; 00238 Particle* pParticle; 00239 00240 itEnd = mActiveBillboards.end(); 00241 00242 for (i = mActiveBillboards.begin(); i != itEnd; ) 00243 { 00244 pParticle = static_cast<Particle*>(*i); 00245 if (pParticle->mTimeToLive < timeElapsed) 00246 { 00247 // Destroy this one 00248 mFreeBillboards.push_back( *i ); 00249 i = mActiveBillboards.erase( i ); 00250 } 00251 else 00252 { 00253 // Decrement TTL 00254 pParticle->mTimeToLive -= timeElapsed; 00255 ++i; 00256 } 00257 00258 } 00259 } 00260 //----------------------------------------------------------------------- 00261 void ParticleSystem::_triggerEmitters(Real timeElapsed) 00262 { 00263 // Add up requests for emission 00264 static std::vector<unsigned> requested; 00265 if( requested.size() != mEmitters.size() ) 00266 requested.resize( mEmitters.size() ); 00267 00268 size_t totalRequested, emitterCount, i, emissionAllowed; 00269 ParticleEmitterList::iterator itEmit, iEmitEnd; 00270 ParticleAffectorList::iterator itAff, itAffEnd; 00271 00272 iEmitEnd = mEmitters.end(); 00273 emitterCount = mEmitters.size(); 00274 emissionAllowed = getParticleQuota() - mActiveBillboards.size(); 00275 totalRequested = 0; 00276 00277 // Count up total requested emissions 00278 for (itEmit = mEmitters.begin(), i = 0; itEmit != iEmitEnd; ++itEmit, ++i) 00279 { 00280 requested[i] = (*itEmit)->_getEmissionCount(timeElapsed); 00281 totalRequested += requested[i]; 00282 } 00283 00284 00285 // Check if the quota will be exceeded, if so reduce demand 00286 if (totalRequested > emissionAllowed) 00287 { 00288 // Apportion down requested values to allotted values 00289 Real ratio = (Real)emissionAllowed / (Real)totalRequested; 00290 for (i = 0; i < emitterCount; ++i) 00291 { 00292 requested[i] *= (unsigned int)ratio; 00293 } 00294 } 00295 00296 // Emit 00297 // For each emission, apply a subset of the motion for the frame 00298 // this ensures an even distribution of particles when many are 00299 // emitted in a single frame 00300 for (itEmit = mEmitters.begin(), i = 0; itEmit != iEmitEnd; ++itEmit, ++i) 00301 { 00302 Real timePoint = 0.0f; 00303 Real timeInc = timeElapsed / requested[i]; 00304 for (unsigned int j = 0; j < requested[i]; ++j) 00305 { 00306 // Create a new particle & init using emitter 00307 Particle* p = addParticle(); 00308 (*itEmit)->_initParticle(p); 00309 00310 // Translate position & direction into world space 00311 // Maybe make emitter do this? 00312 p->mPosition = (mParentNode->_getDerivedOrientation() * p->mPosition) + mParentNode->_getDerivedPosition(); 00313 p->mDirection = (mParentNode->_getDerivedOrientation() * p->mDirection); 00314 00315 // apply partial frame motion to this particle 00316 p->mPosition += (p->mDirection * timePoint); 00317 00318 // apply particle initialization by the affectors 00319 itAffEnd = mAffectors.end(); 00320 for (itAff = mAffectors.begin(); itAff != itAffEnd; ++itAff) 00321 (*itAff)->_initParticle(p); 00322 00323 // Increment time fragment 00324 timePoint += timeInc; 00325 } 00326 } 00327 00328 00329 } 00330 //----------------------------------------------------------------------- 00331 void ParticleSystem::_applyMotion(Real timeElapsed) 00332 { 00333 ActiveBillboardList::iterator i, itEnd; 00334 Particle* pParticle; 00335 00336 itEnd = mActiveBillboards.end(); 00337 for (i = mActiveBillboards.begin(); i != itEnd; ++i) 00338 { 00339 pParticle = static_cast<Particle*>(*i); 00340 pParticle->mPosition += (pParticle->mDirection * timeElapsed); 00341 } 00342 00343 } 00344 //----------------------------------------------------------------------- 00345 void ParticleSystem::_triggerAffectors(Real timeElapsed) 00346 { 00347 ParticleAffectorList::iterator i, itEnd; 00348 00349 itEnd = mAffectors.end(); 00350 for (i = mAffectors.begin(); i != itEnd; ++i) 00351 { 00352 (*i)->_affectParticles(this, timeElapsed); 00353 } 00354 00355 } 00356 //----------------------------------------------------------------------- 00357 void ParticleSystem::increasePool(unsigned int size) 00358 { 00359 size_t oldSize = mBillboardPool.size(); 00360 00361 // Increase size 00362 mBillboardPool.reserve(size); 00363 mBillboardPool.resize(size); 00364 00365 // Create new particles 00366 for( size_t i = oldSize; i < size; i++ ) 00367 mBillboardPool[i] = new Particle(); 00368 00369 } 00370 //----------------------------------------------------------------------- 00371 ParticleIterator ParticleSystem::_getIterator(void) 00372 { 00373 return ParticleIterator(mActiveBillboards.begin(), mActiveBillboards.end()); 00374 } 00375 //----------------------------------------------------------------------- 00376 Particle* ParticleSystem::addParticle(void) 00377 { 00378 // Fast creation (don't use superclass since emitter will init) 00379 Billboard* newBill = mFreeBillboards.front(); 00380 mFreeBillboards.pop_front(); 00381 mActiveBillboards.push_back(newBill); 00382 00383 newBill->_notifyOwner(this); 00384 00385 // Because we're creating objects here we know this is a Particle 00386 return static_cast<Particle*>(newBill); 00387 00388 } 00389 //----------------------------------------------------------------------- 00390 void ParticleSystem::genBillboardAxes(const Camera& cam, Vector3* pX, Vector3 *pY, const Billboard* pBill) 00391 { 00392 // Orientation different from BillboardSet 00393 // Billboards are in world space (to decouple them from emitters in node space) 00394 Quaternion camQ; 00395 00396 switch (mBillboardType) 00397 { 00398 case BBT_POINT: 00399 // Get camera world axes for X and Y (depth is irrelevant) 00400 // No inverse transform 00401 camQ = cam.getDerivedOrientation(); 00402 *pX = camQ * Vector3::UNIT_X; 00403 *pY = camQ * Vector3::UNIT_Y; 00404 00405 break; 00406 case BBT_ORIENTED_COMMON: 00407 // Y-axis is common direction 00408 // X-axis is cross with camera direction 00409 *pY = mCommonDirection; 00410 *pX = cam.getDerivedDirection().crossProduct(*pY); 00411 00412 break; 00413 case BBT_ORIENTED_SELF: 00414 // Y-axis is direction 00415 // X-axis is cross with camera direction 00416 00417 // Scale direction first 00418 *pY = (pBill->mDirection * 0.01); 00419 *pX = cam.getDerivedDirection().crossProduct(*pY); 00420 00421 break; 00422 } 00423 00424 } 00425 //----------------------------------------------------------------------- 00426 void ParticleSystem::getWorldTransforms(Matrix4* xform) const 00427 { 00428 // Particles are already in world space 00429 *xform = Matrix4::IDENTITY; 00430 00431 } 00432 //----------------------------------------------------------------------- 00433 const Quaternion& ParticleSystem::getWorldOrientation(void) const 00434 { 00435 return mParentNode->_getDerivedOrientation(); 00436 } 00437 //----------------------------------------------------------------------- 00438 const Vector3& ParticleSystem::getWorldPosition(void) const 00439 { 00440 return mParentNode->_getDerivedPosition(); 00441 } 00442 //----------------------------------------------------------------------- 00443 void ParticleSystem::initParameters(void) 00444 { 00445 if (createParamDictionary("ParticleSystem")) 00446 { 00447 ParamDictionary* dict = getParamDictionary(); 00448 00449 dict->addParameter(ParameterDef("quota", 00450 "The maximum number of particle allowed at once in this system.", 00451 PT_UNSIGNED_INT), 00452 &msQuotaCmd); 00453 00454 dict->addParameter(ParameterDef("material", 00455 "The name of the material to be used to render all particles in this system.", 00456 PT_STRING), 00457 &msMaterialCmd); 00458 00459 dict->addParameter(ParameterDef("particle_width", 00460 "The width of particles in world units.", 00461 PT_REAL), 00462 &msWidthCmd); 00463 00464 dict->addParameter(ParameterDef("particle_height", 00465 "The height of particles in world units.", 00466 PT_REAL), 00467 &msHeightCmd); 00468 00469 dict->addParameter(ParameterDef("cull_each", 00470 "If true, each particle is culled in it's own right. If false, the entire system is culled as a whole.", 00471 PT_BOOL), 00472 &msCullCmd); 00473 00474 dict->addParameter(ParameterDef("billboard_type", 00475 "The type of billboard to use. 'point' means a simulated spherical particle, " 00476 "'oriented_common' means all particles in the set are oriented around common_direction, " 00477 "and 'oriented_self' means particles are oriented around their own direction.", 00478 PT_STRING), 00479 &msBillboardTypeCmd); 00480 00481 dict->addParameter(ParameterDef("common_direction", 00482 "Only useful when billboard_type is oriented_common. This parameter sets the common " 00483 "orientation for all particles in the set (e.g. raindrops may all be oriented downwards).", 00484 PT_VECTOR3), 00485 &msCommonDirectionCmd); 00486 00487 } 00488 } 00489 //----------------------------------------------------------------------- 00490 void ParticleSystem::_updateBounds() 00491 { 00492 // Call superclass 00493 BillboardSet::_updateBounds(); 00494 00495 if (mParentNode && !mAABB.isNull()) 00496 { 00497 // Have to override because bounds are supposed to be in local node space 00498 // but we've already put particles in world space to decouple them from the 00499 // node transform, so reverse transform back 00500 00501 Vector3 min( Math::POS_INFINITY, Math::POS_INFINITY, Math::POS_INFINITY ); 00502 Vector3 max( Math::NEG_INFINITY, Math::NEG_INFINITY, Math::NEG_INFINITY ); 00503 Vector3 temp; 00504 const Vector3 *corner = mAABB.getAllCorners(); 00505 Quaternion invQ = mParentNode->_getDerivedOrientation().Inverse(); 00506 Vector3 t = mParentNode->_getDerivedPosition(); 00507 00508 for (int i = 0; i < 8; ++i) 00509 { 00510 // Reverse transform corner 00511 temp = invQ * (corner[i] - t); 00512 min.makeFloor(temp); 00513 max.makeCeil(temp); 00514 } 00515 mAABB.setExtents(min, max); 00516 } 00517 } 00518 //----------------------------------------------------------------------- 00519 00520 void ParticleSystem::fastForward(Real time, Real interval) 00521 { 00522 // First make sure all transforms are up to date 00523 00524 for (Real ftime = 0; ftime < time; ftime += interval) 00525 { 00526 _update(interval); 00527 } 00528 } 00529 //----------------------------------------------------------------------- 00530 const String& ParticleSystem::getMovableType(void) const 00531 { 00532 static String mType = "ParticleSystem"; 00533 return mType; 00534 } 00535 00536 //----------------------------------------------------------------------- 00537 String ParticleSystem::CmdCull::doGet(const void* target) const 00538 { 00539 return StringConverter::toString( 00540 static_cast<const ParticleSystem*>(target)->getCullIndividually() ); 00541 } 00542 void ParticleSystem::CmdCull::doSet(void* target, const String& val) 00543 { 00544 static_cast<ParticleSystem*>(target)->setCullIndividually( 00545 StringConverter::parseBool(val)); 00546 } 00547 //----------------------------------------------------------------------- 00548 String ParticleSystem::CmdHeight::doGet(const void* target) const 00549 { 00550 return StringConverter::toString( 00551 static_cast<const ParticleSystem*>(target)->getDefaultHeight() ); 00552 } 00553 void ParticleSystem::CmdHeight::doSet(void* target, const String& val) 00554 { 00555 static_cast<ParticleSystem*>(target)->setDefaultHeight( 00556 StringConverter::parseReal(val)); 00557 } 00558 //----------------------------------------------------------------------- 00559 String ParticleSystem::CmdWidth::doGet(const void* target) const 00560 { 00561 return StringConverter::toString( 00562 static_cast<const ParticleSystem*>(target)->getDefaultWidth() ); 00563 } 00564 void ParticleSystem::CmdWidth::doSet(void* target, const String& val) 00565 { 00566 static_cast<ParticleSystem*>(target)->setDefaultWidth( 00567 StringConverter::parseReal(val)); 00568 } 00569 //----------------------------------------------------------------------- 00570 String ParticleSystem::CmdMaterial::doGet(const void* target) const 00571 { 00572 return static_cast<const ParticleSystem*>(target)->getMaterialName(); 00573 } 00574 void ParticleSystem::CmdMaterial::doSet(void* target, const String& val) 00575 { 00576 static_cast<ParticleSystem*>(target)->setMaterialName(val); 00577 } 00578 //----------------------------------------------------------------------- 00579 String ParticleSystem::CmdQuota::doGet(const void* target) const 00580 { 00581 return StringConverter::toString( 00582 static_cast<const ParticleSystem*>(target)->getParticleQuota() ); 00583 } 00584 void ParticleSystem::CmdQuota::doSet(void* target, const String& val) 00585 { 00586 static_cast<ParticleSystem*>(target)->setParticleQuota( 00587 StringConverter::parseUnsignedInt(val)); 00588 } 00589 //----------------------------------------------------------------------- 00590 String ParticleSystem::CmdBillboardType::doGet(const void* target) const 00591 { 00592 BillboardType t = static_cast<const ParticleSystem*>(target)->getBillboardType(); 00593 switch(t) 00594 { 00595 case BBT_POINT: 00596 return "point"; 00597 break; 00598 case BBT_ORIENTED_COMMON: 00599 return "oriented_common"; 00600 break; 00601 case BBT_ORIENTED_SELF: 00602 return "oriented_self"; 00603 break; 00604 } 00605 // Compiler nicety 00606 return ""; 00607 } 00608 void ParticleSystem::CmdBillboardType::doSet(void* target, const String& val) 00609 { 00610 BillboardType t; 00611 if (val == "point") 00612 { 00613 t = BBT_POINT; 00614 } 00615 else if (val == "oriented_common") 00616 { 00617 t = BBT_ORIENTED_COMMON; 00618 } 00619 else if (val == "oriented_self") 00620 { 00621 t = BBT_ORIENTED_SELF; 00622 } 00623 else 00624 { 00625 Except(Exception::ERR_INVALIDPARAMS, 00626 "Invalid billboard_type '" + val + "'", 00627 "ParticleSystem::CmdBillboardType::doSet"); 00628 } 00629 00630 static_cast<ParticleSystem*>(target)->setBillboardType(t); 00631 } 00632 //----------------------------------------------------------------------- 00633 String ParticleSystem::CmdCommonDirection::doGet(const void* target) const 00634 { 00635 return StringConverter::toString( 00636 static_cast<const ParticleSystem*>(target)->getCommonDirection() ); 00637 } 00638 void ParticleSystem::CmdCommonDirection::doSet(void* target, const String& val) 00639 { 00640 static_cast<ParticleSystem*>(target)->setCommonDirection( 00641 StringConverter::parseVector3(val)); 00642 } 00643 00644 }
Copyright © 2002-2003 by The OGRE Team
Last modified Sun Nov 28 19:48:38 2004