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 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 const String& ParticleSystem::getName(void) const 00092 { 00093 return mName; 00094 } 00095 //----------------------------------------------------------------------- 00096 ParticleEmitter* ParticleSystem::addEmitter(const String& emitterType) 00097 { 00098 ParticleEmitter* em = ParticleSystemManager::getSingleton()._createEmitter(emitterType); 00099 mEmitters.push_back(em); 00100 return em; 00101 } 00102 //----------------------------------------------------------------------- 00103 ParticleEmitter* ParticleSystem::getEmitter(unsigned short index) const 00104 { 00105 assert(index < mEmitters.size() && "Emitter index out of bounds!"); 00106 return mEmitters[index]; 00107 } 00108 //----------------------------------------------------------------------- 00109 unsigned short ParticleSystem::getNumEmitters(void) const 00110 { 00111 return static_cast< unsigned short >( mEmitters.size() ); 00112 } 00113 //----------------------------------------------------------------------- 00114 void ParticleSystem::removeEmitter(unsigned short index) 00115 { 00116 assert(index < mEmitters.size() && "Emitter index out of bounds!"); 00117 ParticleEmitterList::iterator ei = mEmitters.begin() + index; 00118 ParticleSystemManager::getSingleton()._destroyEmitter(*ei); 00119 mEmitters.erase(ei); 00120 } 00121 //----------------------------------------------------------------------- 00122 void ParticleSystem::removeAllEmitters(void) 00123 { 00124 // DON'T delete directly, we don't know what heap these have been created on 00125 ParticleEmitterList::iterator ei; 00126 for (ei = mEmitters.begin(); ei != mEmitters.end(); ++ei) 00127 { 00128 ParticleSystemManager::getSingleton()._destroyEmitter(*ei); 00129 } 00130 mEmitters.clear(); 00131 } 00132 //----------------------------------------------------------------------- 00133 ParticleAffector* ParticleSystem::addAffector(const String& affectorType) 00134 { 00135 ParticleAffector* af = ParticleSystemManager::getSingleton()._createAffector(affectorType); 00136 mAffectors.push_back(af); 00137 return af; 00138 } 00139 //----------------------------------------------------------------------- 00140 ParticleAffector* ParticleSystem::getAffector(unsigned short index) const 00141 { 00142 assert(index < mAffectors.size() && "Affector index out of bounds!"); 00143 return mAffectors[index]; 00144 } 00145 //----------------------------------------------------------------------- 00146 unsigned short ParticleSystem::getNumAffectors(void) const 00147 { 00148 return static_cast< unsigned short >( mAffectors.size() ); 00149 } 00150 //----------------------------------------------------------------------- 00151 void ParticleSystem::removeAffector(unsigned short index) 00152 { 00153 assert(index < mAffectors.size() && "Affector index out of bounds!"); 00154 ParticleAffectorList::iterator ai = mAffectors.begin() + index; 00155 ParticleSystemManager::getSingleton()._destroyAffector(*ai); 00156 mAffectors.erase(ai); 00157 } 00158 //----------------------------------------------------------------------- 00159 void ParticleSystem::removeAllAffectors(void) 00160 { 00161 // DON'T delete directly, we don't know what heap these have been created on 00162 ParticleAffectorList::iterator ai; 00163 for (ai = mAffectors.begin(); ai != mAffectors.end(); ++ai) 00164 { 00165 ParticleSystemManager::getSingleton()._destroyAffector(*ai); 00166 } 00167 mAffectors.clear(); 00168 } 00169 //----------------------------------------------------------------------- 00170 ParticleSystem& ParticleSystem::operator=(const ParticleSystem& rhs) 00171 { 00172 // Blank this system's emitters & affectors 00173 removeAllEmitters(); 00174 removeAllAffectors(); 00175 00176 // Copy emitters 00177 unsigned int i; 00178 for(i = 0; i < rhs.getNumEmitters(); ++i) 00179 { 00180 ParticleEmitter* rhsEm = rhs.getEmitter(i); 00181 ParticleEmitter* newEm = addEmitter(rhsEm->getType()); 00182 rhsEm->copyParametersTo(newEm); 00183 } 00184 // Copy affectors 00185 for(i = 0; i < rhs.getNumAffectors(); ++i) 00186 { 00187 ParticleAffector* rhsAf = rhs.getAffector(i); 00188 ParticleAffector* newAf = addAffector(rhsAf->getType()); 00189 rhsAf->copyParametersTo(newAf); 00190 } 00191 setPoolSize(rhs.getPoolSize()); 00192 setMaterialName(rhs.mMaterialName); 00193 mOriginType = rhs.mOriginType; 00194 mDefaultHeight = rhs.mDefaultHeight; 00195 mDefaultWidth = rhs.mDefaultWidth; 00196 mCullIndividual = rhs.mCullIndividual; 00197 mBillboardType = rhs.mBillboardType; 00198 mCommonDirection = rhs.mCommonDirection; 00199 00200 00201 return *this; 00202 00203 } 00204 //----------------------------------------------------------------------- 00205 unsigned int ParticleSystem::getNumParticles(void) const 00206 { 00207 return (unsigned int)mActiveBillboards.size(); 00208 } 00209 //----------------------------------------------------------------------- 00210 unsigned int ParticleSystem::getParticleQuota(void) const 00211 { 00212 // This is basically a renamed property 00213 return getPoolSize(); 00214 } 00215 //----------------------------------------------------------------------- 00216 void ParticleSystem::setParticleQuota(unsigned int quota) 00217 { 00218 // This is basically a renamed property 00219 setPoolSize(quota); 00220 } 00221 //----------------------------------------------------------------------- 00222 void ParticleSystem::_update(Real timeElapsed) 00223 { 00224 // Only update if attached to a node 00225 if (mParentNode) 00226 { 00227 // Update existing particles 00228 _expire(timeElapsed); 00229 _triggerAffectors(timeElapsed); 00230 _applyMotion(timeElapsed); 00231 // Emit new particles 00232 _triggerEmitters(timeElapsed); 00233 // Update bounds 00234 _updateBounds(); 00235 } 00236 00237 00238 } 00239 //----------------------------------------------------------------------- 00240 void ParticleSystem::_expire(Real timeElapsed) 00241 { 00242 ActiveBillboardList::iterator i, itEnd; 00243 Particle* pParticle; 00244 00245 itEnd = mActiveBillboards.end(); 00246 00247 for (i = mActiveBillboards.begin(); i != itEnd; ++i) 00248 { 00249 pParticle = static_cast<Particle*>(*i); 00250 if (pParticle->mTimeToLive < timeElapsed) 00251 { 00252 // Destroy this one 00253 mFreeBillboards.push_back( *i ); 00254 i = mActiveBillboards.erase( i ); 00255 } 00256 else 00257 { 00258 // Decrement TTL 00259 pParticle->mTimeToLive -= timeElapsed; 00260 } 00261 00262 } 00263 } 00264 //----------------------------------------------------------------------- 00265 void ParticleSystem::_triggerEmitters(Real timeElapsed) 00266 { 00267 // Add up requests for emission 00268 static std::vector<unsigned> requested; 00269 if( requested.size() != mEmitters.size() ) 00270 requested.resize( mEmitters.size() ); 00271 00272 size_t totalRequested, emitterCount, i, emissionAllowed; 00273 ParticleEmitterList::iterator itEmit, iEmitEnd; 00274 ParticleAffectorList::iterator itAff, itAffEnd; 00275 00276 iEmitEnd = mEmitters.end(); 00277 emitterCount = mEmitters.size(); 00278 emissionAllowed = getParticleQuota() - mActiveBillboards.size(); 00279 totalRequested = 0; 00280 00281 // Count up total requested emissions 00282 for (itEmit = mEmitters.begin(), i = 0; itEmit != iEmitEnd; ++itEmit, ++i) 00283 { 00284 requested[i] = (*itEmit)->_getEmissionCount(timeElapsed); 00285 totalRequested += requested[i]; 00286 } 00287 00288 00289 // Check if the quota will be exceeded, if so reduce demand 00290 if (totalRequested > emissionAllowed) 00291 { 00292 // Apportion down requested values to allotted values 00293 Real ratio = (Real)emissionAllowed / (Real)totalRequested; 00294 for (i = 0; i < emitterCount; ++i) 00295 { 00296 requested[i] *= (unsigned int)ratio; 00297 } 00298 } 00299 00300 // Emit 00301 // For each emission, apply a subset of the motion for the frame 00302 // this ensures an even distribution of particles when many are 00303 // emitted in a single frame 00304 for (itEmit = mEmitters.begin(), i = 0; itEmit != iEmitEnd; ++itEmit, ++i) 00305 { 00306 Real timePoint = 0.0f; 00307 Real timeInc = timeElapsed / requested[i]; 00308 for (unsigned int j = 0; j < requested[i]; ++j) 00309 { 00310 // Create a new particle & init using emitter 00311 Particle* p = addParticle(); 00312 (*itEmit)->_initParticle(p); 00313 00314 // Translate position & direction into world space 00315 // Maybe make emitter do this? 00316 p->mPosition = (mParentNode->_getDerivedOrientation() * p->mPosition) + mParentNode->_getDerivedPosition(); 00317 p->mDirection = (mParentNode->_getDerivedOrientation() * p->mDirection); 00318 00319 // apply partial frame motion to this particle 00320 p->mPosition += (p->mDirection * timePoint); 00321 00322 // apply particle initialization by the affectors 00323 itAffEnd = mAffectors.end(); 00324 for (itAff = mAffectors.begin(); itAff != itAffEnd; ++itAff) 00325 (*itAff)->_initParticle(p); 00326 00327 // Increment time fragment 00328 timePoint += timeInc; 00329 } 00330 } 00331 00332 00333 } 00334 //----------------------------------------------------------------------- 00335 void ParticleSystem::_applyMotion(Real timeElapsed) 00336 { 00337 ActiveBillboardList::iterator i, itEnd; 00338 Particle* pParticle; 00339 00340 itEnd = mActiveBillboards.end(); 00341 for (i = mActiveBillboards.begin(); i != itEnd; ++i) 00342 { 00343 pParticle = static_cast<Particle*>(*i); 00344 pParticle->mPosition += (pParticle->mDirection * timeElapsed); 00345 } 00346 00347 } 00348 //----------------------------------------------------------------------- 00349 void ParticleSystem::_triggerAffectors(Real timeElapsed) 00350 { 00351 ParticleAffectorList::iterator i, itEnd; 00352 00353 itEnd = mAffectors.end(); 00354 for (i = mAffectors.begin(); i != itEnd; ++i) 00355 { 00356 (*i)->_affectParticles(this, timeElapsed); 00357 } 00358 00359 } 00360 //----------------------------------------------------------------------- 00361 void ParticleSystem::increasePool(unsigned int size) 00362 { 00363 size_t oldSize = mBillboardPool.size(); 00364 00365 // Increase size 00366 mBillboardPool.reserve(size); 00367 mBillboardPool.resize(size); 00368 00369 // Create new particles 00370 for( size_t i = oldSize; i < size; i++ ) 00371 mBillboardPool[i] = new Particle(); 00372 00373 } 00374 //----------------------------------------------------------------------- 00375 ParticleIterator ParticleSystem::_getIterator(void) 00376 { 00377 return ParticleIterator(mActiveBillboards.begin(), mActiveBillboards.end()); 00378 } 00379 //----------------------------------------------------------------------- 00380 Particle* ParticleSystem::addParticle(void) 00381 { 00382 // Fast creation (don't use superclass since emitter will init) 00383 Billboard* newBill = mFreeBillboards.front(); 00384 mFreeBillboards.pop_front(); 00385 mActiveBillboards.push_back(newBill); 00386 00387 newBill->_notifyOwner(this); 00388 00389 // Because we're creating objects here we know this is a Particle 00390 return static_cast<Particle*>(newBill); 00391 00392 } 00393 //----------------------------------------------------------------------- 00394 void ParticleSystem::genBillboardAxes(Camera& cam, Vector3* pX, Vector3 *pY, const Billboard* pBill) 00395 { 00396 // Orientation different from BillboardSet 00397 // Billboards are in world space (to decouple them from emitters in node space) 00398 Quaternion camQ; 00399 00400 switch (mBillboardType) 00401 { 00402 case BBT_POINT: 00403 // Get camera world axes for X and Y (depth is irrelevant) 00404 // No inverse transform 00405 camQ = cam.getDerivedOrientation(); 00406 *pX = camQ * Vector3::UNIT_X; 00407 *pY = camQ * Vector3::UNIT_Y; 00408 00409 break; 00410 case BBT_ORIENTED_COMMON: 00411 // Y-axis is common direction 00412 // X-axis is cross with camera direction 00413 *pY = mCommonDirection; 00414 *pX = cam.getDerivedDirection().crossProduct(*pY); 00415 00416 break; 00417 case BBT_ORIENTED_SELF: 00418 // Y-axis is direction 00419 // X-axis is cross with camera direction 00420 00421 // Scale direction first 00422 *pY = (pBill->mDirection * 0.01); 00423 *pX = cam.getDerivedDirection().crossProduct(*pY); 00424 00425 break; 00426 } 00427 00428 } 00429 //----------------------------------------------------------------------- 00430 void ParticleSystem::getWorldTransforms(Matrix4* xform) const 00431 { 00432 // Particles are already in world space 00433 *xform = Matrix4::IDENTITY; 00434 00435 } 00436 //----------------------------------------------------------------------- 00437 const Quaternion& ParticleSystem::getWorldOrientation(void) const 00438 { 00439 return mParentNode->_getDerivedOrientation(); 00440 } 00441 //----------------------------------------------------------------------- 00442 const Vector3& ParticleSystem::getWorldPosition(void) const 00443 { 00444 return mParentNode->_getDerivedPosition(); 00445 } 00446 //----------------------------------------------------------------------- 00447 void ParticleSystem::initParameters(void) 00448 { 00449 if (createParamDictionary("ParticleSystem")) 00450 { 00451 ParamDictionary* dict = getParamDictionary(); 00452 00453 dict->addParameter(ParameterDef("quota", 00454 "The maximum number of particle allowed at once in this system.", 00455 PT_UNSIGNED_INT), 00456 &msQuotaCmd); 00457 00458 dict->addParameter(ParameterDef("material", 00459 "The name of the material to be used to render all particles in this system.", 00460 PT_STRING), 00461 &msMaterialCmd); 00462 00463 dict->addParameter(ParameterDef("particle_width", 00464 "The width of particles in world units.", 00465 PT_REAL), 00466 &msWidthCmd); 00467 00468 dict->addParameter(ParameterDef("particle_height", 00469 "The height of particles in world units.", 00470 PT_REAL), 00471 &msHeightCmd); 00472 00473 dict->addParameter(ParameterDef("cull_each", 00474 "If true, each particle is culled in it's own right. If false, the entire system is culled as a whole.", 00475 PT_BOOL), 00476 &msCullCmd); 00477 00478 dict->addParameter(ParameterDef("billboard_type", 00479 "The type of billboard to use. 'point' means a simulated spherical particle, " 00480 "'oriented_common' means all particles in the set are oriented around common_direction, " 00481 "and 'oriented_self' means particles are oriented around their own direction.", 00482 PT_UNSIGNED_INT), 00483 &msBillboardTypeCmd); 00484 00485 dict->addParameter(ParameterDef("common_direction", 00486 "Only useful when billboard_type is oriented_common. This parameter sets the common " 00487 "orientation for all particles in the set (e.g. raindrops may all be oriented downwards).", 00488 PT_VECTOR3), 00489 &msCommonDirectionCmd); 00490 00491 } 00492 } 00493 //----------------------------------------------------------------------- 00494 void ParticleSystem::_updateBounds() 00495 { 00496 // Call superclass 00497 BillboardSet::_updateBounds(); 00498 00499 if (mParentNode && !mAABB.isNull()) 00500 { 00501 // Have to override because bounds are supposed to be in local node space 00502 // but we've already put particles in world space to decouple them from the 00503 // node transform, so reverse transform back 00504 00505 Vector3 min( Math::POS_INFINITY, Math::POS_INFINITY, Math::POS_INFINITY ); 00506 Vector3 max( Math::NEG_INFINITY, Math::NEG_INFINITY, Math::NEG_INFINITY ); 00507 Vector3 temp; 00508 const Vector3 *corner = mAABB.getAllCorners(); 00509 Quaternion invQ = mParentNode->_getDerivedOrientation().Inverse(); 00510 Vector3 t = mParentNode->_getDerivedPosition(); 00511 00512 for (int i = 0; i < 8; ++i) 00513 { 00514 // Reverse transform corner 00515 temp = invQ * (corner[i] - t); 00516 min.makeFloor(temp); 00517 max.makeCeil(temp); 00518 } 00519 mAABB.setExtents(min, max); 00520 } 00521 } 00522 //----------------------------------------------------------------------- 00523 00524 void ParticleSystem::fastForward(Real time, Real interval) 00525 { 00526 // First make sure all transforms are up to date 00527 00528 for (Real ftime = 0; ftime < time; ftime += interval) 00529 { 00530 _update(interval); 00531 } 00532 } 00533 //----------------------------------------------------------------------- 00534 const String& ParticleSystem::getMovableType(void) const 00535 { 00536 static String mType = "ParticleSystem"; 00537 return mType; 00538 } 00539 00540 //----------------------------------------------------------------------- 00541 String ParticleSystem::CmdCull::doGet(const void* target) const 00542 { 00543 return StringConverter::toString( 00544 static_cast<const ParticleSystem*>(target)->getCullIndividually() ); 00545 } 00546 void ParticleSystem::CmdCull::doSet(void* target, const String& val) 00547 { 00548 static_cast<ParticleSystem*>(target)->setCullIndividually( 00549 StringConverter::parseBool(val)); 00550 } 00551 //----------------------------------------------------------------------- 00552 String ParticleSystem::CmdHeight::doGet(const void* target) const 00553 { 00554 return StringConverter::toString( 00555 static_cast<const ParticleSystem*>(target)->getDefaultHeight() ); 00556 } 00557 void ParticleSystem::CmdHeight::doSet(void* target, const String& val) 00558 { 00559 static_cast<ParticleSystem*>(target)->setDefaultHeight( 00560 StringConverter::parseReal(val)); 00561 } 00562 //----------------------------------------------------------------------- 00563 String ParticleSystem::CmdWidth::doGet(const void* target) const 00564 { 00565 return StringConverter::toString( 00566 static_cast<const ParticleSystem*>(target)->getDefaultWidth() ); 00567 } 00568 void ParticleSystem::CmdWidth::doSet(void* target, const String& val) 00569 { 00570 static_cast<ParticleSystem*>(target)->setDefaultWidth( 00571 StringConverter::parseReal(val)); 00572 } 00573 //----------------------------------------------------------------------- 00574 String ParticleSystem::CmdMaterial::doGet(const void* target) const 00575 { 00576 return static_cast<const ParticleSystem*>(target)->getMaterialName(); 00577 } 00578 void ParticleSystem::CmdMaterial::doSet(void* target, const String& val) 00579 { 00580 static_cast<ParticleSystem*>(target)->setMaterialName(val); 00581 } 00582 //----------------------------------------------------------------------- 00583 String ParticleSystem::CmdQuota::doGet(const void* target) const 00584 { 00585 return StringConverter::toString( 00586 static_cast<const ParticleSystem*>(target)->getParticleQuota() ); 00587 } 00588 void ParticleSystem::CmdQuota::doSet(void* target, const String& val) 00589 { 00590 static_cast<ParticleSystem*>(target)->setParticleQuota( 00591 StringConverter::parseUnsignedInt(val)); 00592 } 00593 //----------------------------------------------------------------------- 00594 String ParticleSystem::CmdBillboardType::doGet(const void* target) const 00595 { 00596 BillboardType t = static_cast<const ParticleSystem*>(target)->getBillboardType(); 00597 switch(t) 00598 { 00599 case BBT_POINT: 00600 return "point"; 00601 break; 00602 case BBT_ORIENTED_COMMON: 00603 return "oriented_common"; 00604 break; 00605 case BBT_ORIENTED_SELF: 00606 return "oriented_self"; 00607 break; 00608 } 00609 // Compiler nicety 00610 return ""; 00611 } 00612 void ParticleSystem::CmdBillboardType::doSet(void* target, const String& val) 00613 { 00614 BillboardType t; 00615 if (val == "point") 00616 { 00617 t = BBT_POINT; 00618 } 00619 else if (val == "oriented_common") 00620 { 00621 t = BBT_ORIENTED_COMMON; 00622 } 00623 else if (val == "oriented_self") 00624 { 00625 t = BBT_ORIENTED_SELF; 00626 } 00627 static_cast<ParticleSystem*>(target)->setBillboardType(t); 00628 } 00629 //----------------------------------------------------------------------- 00630 String ParticleSystem::CmdCommonDirection::doGet(const void* target) const 00631 { 00632 return StringConverter::toString( 00633 static_cast<const ParticleSystem*>(target)->getCommonDirection() ); 00634 } 00635 void ParticleSystem::CmdCommonDirection::doSet(void* target, const String& val) 00636 { 00637 static_cast<ParticleSystem*>(target)->setCommonDirection( 00638 StringConverter::parseVector3(val)); 00639 } 00640 00641 }
Copyright © 2002-2003 by The OGRE Team
Last modified Wed Jan 21 00:10:21 2004